Skip to content

Commit ae4df22

Browse files
authored
Merge branch 'main' into fix/path_resolution_for_esm
2 parents 5926949 + 67b48be commit ae4df22

1 file changed

Lines changed: 200 additions & 0 deletions

File tree

tests/test_function_discovery.py

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -950,3 +950,203 @@ def test_filter_functions_non_overlapping_tests_root():
950950

951951
# Strict check: exactly 2 functions remaining
952952
assert count == 2, f"Expected exactly 2 functions, got {count}"
953+
954+
955+
def test_filter_functions_project_inside_tests_folder():
956+
"""Test that source files are not filtered when project is inside a folder named 'tests'.
957+
958+
This is a critical regression test for projects located at paths like:
959+
- /home/user/tests/myproject/
960+
- /Users/dev/tests/n8n/
961+
962+
The fix ensures that directory pattern matching (e.g., /tests/) is only checked
963+
on the relative path from project_root, not on the full absolute path.
964+
"""
965+
with tempfile.TemporaryDirectory() as outer_temp_dir_str:
966+
outer_temp_dir = Path(outer_temp_dir_str)
967+
968+
# Create a "tests" folder to simulate /home/user/tests/
969+
tests_parent_folder = outer_temp_dir / "tests"
970+
tests_parent_folder.mkdir()
971+
972+
# Create project inside the "tests" folder - simulates /home/user/tests/myproject/
973+
project_dir = tests_parent_folder / "myproject"
974+
project_dir.mkdir()
975+
976+
# Create source file inside the project
977+
src_dir = project_dir / "src"
978+
src_dir.mkdir()
979+
source_file = src_dir / "utils.py"
980+
with source_file.open("w") as f:
981+
f.write("""
982+
def deep_copy(obj):
983+
\"\"\"Deep copy an object.\"\"\"
984+
import copy
985+
return copy.deepcopy(obj)
986+
987+
def compare_values(a, b):
988+
\"\"\"Compare two values.\"\"\"
989+
return a == b
990+
""")
991+
992+
# Create another source file directly in project root
993+
root_source_file = project_dir / "main.py"
994+
with root_source_file.open("w") as f:
995+
f.write("""
996+
def main():
997+
\"\"\"Main entry point.\"\"\"
998+
return 0
999+
""")
1000+
1001+
# Create actual test files that should be filtered
1002+
project_tests_dir = project_dir / "test"
1003+
project_tests_dir.mkdir()
1004+
test_file = project_tests_dir / "test_utils.py"
1005+
with test_file.open("w") as f:
1006+
f.write("""
1007+
def test_deep_copy():
1008+
return True
1009+
""")
1010+
1011+
# Discover functions
1012+
all_functions = {}
1013+
for file_path in [source_file, root_source_file, test_file]:
1014+
discovered = find_all_functions_in_file(file_path)
1015+
all_functions.update(discovered)
1016+
1017+
# Test: project at /outer/tests/myproject with tests_root overlapping
1018+
# This simulates: /home/user/tests/n8n with tests_root = /home/user/tests/n8n
1019+
with unittest.mock.patch(
1020+
"codeflash.discovery.functions_to_optimize.get_blocklisted_functions", return_value={}
1021+
):
1022+
filtered, count = filter_functions(
1023+
all_functions,
1024+
tests_root=project_dir, # Same as project_root (overlapping)
1025+
ignore_paths=[],
1026+
project_root=project_dir, # /outer/tests/myproject
1027+
module_root=project_dir,
1028+
)
1029+
1030+
# Strict check: source files should NOT be filtered even though
1031+
# the full path contains "/tests/" in the parent directory
1032+
expected_files = {source_file, root_source_file}
1033+
actual_files = set(filtered.keys())
1034+
1035+
assert actual_files == expected_files, (
1036+
f"Source files were incorrectly filtered when project is inside 'tests' folder.\n"
1037+
f"Expected files: {expected_files}\n"
1038+
f"Got files: {actual_files}\n"
1039+
f"Project path: {project_dir}\n"
1040+
f"This indicates the /tests/ pattern matched the parent directory path."
1041+
)
1042+
1043+
# Verify the correct functions are present
1044+
source_functions = sorted([fn.function_name for fn in filtered.get(source_file, [])])
1045+
assert source_functions == ["compare_values", "deep_copy"], (
1046+
f"Expected ['compare_values', 'deep_copy'], got {source_functions}"
1047+
)
1048+
1049+
root_functions = [fn.function_name for fn in filtered.get(root_source_file, [])]
1050+
assert root_functions == ["main"], (
1051+
f"Expected ['main'], got {root_functions}"
1052+
)
1053+
1054+
# Strict check: exactly 3 functions (2 from utils.py + 1 from main.py)
1055+
assert count == 3, (
1056+
f"Expected exactly 3 functions, got {count}. "
1057+
f"Some source files may have been incorrectly filtered."
1058+
)
1059+
1060+
# Verify test file was properly filtered (should not be in results)
1061+
assert test_file not in filtered, (
1062+
f"Test file {test_file} should have been filtered but wasn't"
1063+
)
1064+
1065+
1066+
def test_filter_functions_typescript_project_in_tests_folder():
1067+
"""Test TypeScript-like project structure inside a folder named 'tests'.
1068+
1069+
This simulates the n8n project structure:
1070+
/home/user/tests/n8n/packages/workflow/src/utils.ts
1071+
1072+
Ensures that TypeScript source files are not incorrectly filtered
1073+
when the parent directory happens to be named 'tests'.
1074+
"""
1075+
with tempfile.TemporaryDirectory() as outer_temp_dir_str:
1076+
outer_temp_dir = Path(outer_temp_dir_str)
1077+
1078+
# Simulate: /home/user/tests/n8n
1079+
tests_folder = outer_temp_dir / "tests"
1080+
tests_folder.mkdir()
1081+
n8n_project = tests_folder / "n8n"
1082+
n8n_project.mkdir()
1083+
1084+
# Simulate: packages/workflow/src/utils.py (using .py for testing)
1085+
packages_dir = n8n_project / "packages"
1086+
packages_dir.mkdir()
1087+
workflow_dir = packages_dir / "workflow"
1088+
workflow_dir.mkdir()
1089+
src_dir = workflow_dir / "src"
1090+
src_dir.mkdir()
1091+
1092+
# Source file deep in the monorepo structure
1093+
utils_file = src_dir / "utils.py"
1094+
with utils_file.open("w") as f:
1095+
f.write("""
1096+
def deep_copy(source):
1097+
\"\"\"Create a deep copy of the source object.\"\"\"
1098+
if source is None:
1099+
return None
1100+
return source.copy() if hasattr(source, 'copy') else source
1101+
1102+
def is_object_empty(obj):
1103+
\"\"\"Check if an object is empty.\"\"\"
1104+
return len(obj) == 0 if obj else True
1105+
""")
1106+
1107+
# Create test directory inside the package (simulating packages/workflow/test/)
1108+
test_dir = workflow_dir / "test"
1109+
test_dir.mkdir()
1110+
test_file = test_dir / "utils.test.py"
1111+
with test_file.open("w") as f:
1112+
f.write("""
1113+
def test_deep_copy():
1114+
return True
1115+
1116+
def test_is_object_empty():
1117+
return True
1118+
""")
1119+
1120+
# Discover functions
1121+
all_functions = {}
1122+
for file_path in [utils_file, test_file]:
1123+
discovered = find_all_functions_in_file(file_path)
1124+
all_functions.update(discovered)
1125+
1126+
# Test with module_root = packages (typical TypeScript monorepo setup)
1127+
with unittest.mock.patch(
1128+
"codeflash.discovery.functions_to_optimize.get_blocklisted_functions", return_value={}
1129+
):
1130+
filtered, count = filter_functions(
1131+
all_functions,
1132+
tests_root=packages_dir, # Overlapping with module_root
1133+
ignore_paths=[],
1134+
project_root=n8n_project, # /outer/tests/n8n
1135+
module_root=packages_dir, # /outer/tests/n8n/packages
1136+
)
1137+
1138+
# Strict check: only the source file should remain
1139+
assert set(filtered.keys()) == {utils_file}, (
1140+
f"Expected only {utils_file} but got {set(filtered.keys())}.\n"
1141+
f"Source files in /outer/tests/n8n/packages/workflow/src/ were incorrectly filtered.\n"
1142+
f"The /tests/ pattern in the parent path should not affect filtering."
1143+
)
1144+
1145+
# Verify the correct functions are present
1146+
filtered_functions = sorted([fn.function_name for fn in filtered.get(utils_file, [])])
1147+
assert filtered_functions == ["deep_copy", "is_object_empty"], (
1148+
f"Expected ['deep_copy', 'is_object_empty'], got {filtered_functions}"
1149+
)
1150+
1151+
# Strict check: exactly 2 functions
1152+
assert count == 2, f"Expected exactly 2 functions, got {count}"

0 commit comments

Comments
 (0)