codeflash/tests/test_function_discovery.py

1333 lines
50 KiB
Python
Raw Permalink Normal View History

import tempfile
2025-06-04 21:42:07 +00:00
import unittest.mock
2026-01-29 09:39:48 +00:00
from pathlib import Path
2024-10-13 00:41:52 +00:00
from codeflash.discovery.functions_to_optimize import (
2024-10-31 19:03:50 +00:00
filter_files_optimized,
2026-01-29 09:39:48 +00:00
filter_functions,
find_all_functions_in_file,
2026-01-29 09:39:48 +00:00
get_all_files_and_functions,
get_functions_to_optimize,
2024-04-19 22:19:54 +00:00
inspect_top_level_functions_or_methods,
)
from codeflash.verification.verification_utils import TestConfig
def test_function_eligible_for_optimization() -> None:
function = """def test_function_eligible_for_optimization():
a = 5
return a**2
"""
functions_found = {}
2025-07-30 06:49:22 +00:00
with tempfile.TemporaryDirectory() as temp_dir:
temp_dir_path = Path(temp_dir)
file_path = temp_dir_path / "test_function.py"
2026-01-29 09:39:48 +00:00
2025-07-30 06:49:22 +00:00
with file_path.open("w") as f:
f.write(function)
2026-01-29 09:39:48 +00:00
2025-07-30 06:49:22 +00:00
functions_found = find_all_functions_in_file(file_path)
assert functions_found[file_path][0].function_name == "test_function_eligible_for_optimization"
# Has no return statement
function = """def test_function_not_eligible_for_optimization():
a = 5
print(a)
"""
functions_found = {}
2025-07-30 06:49:22 +00:00
with tempfile.TemporaryDirectory() as temp_dir:
temp_dir_path = Path(temp_dir)
file_path = temp_dir_path / "test_function.py"
2026-01-29 09:39:48 +00:00
2025-07-30 06:49:22 +00:00
with file_path.open("w") as f:
f.write(function)
2026-01-29 09:39:48 +00:00
2025-07-30 06:49:22 +00:00
functions_found = find_all_functions_in_file(file_path)
assert len(functions_found[file_path]) == 0
2025-05-30 22:13:10 +00:00
# we want to trigger an error in the function discovery
function = """def test_invalid_code():"""
2025-07-30 06:49:22 +00:00
with tempfile.TemporaryDirectory() as temp_dir:
temp_dir_path = Path(temp_dir)
file_path = temp_dir_path / "test_function.py"
2026-01-29 09:39:48 +00:00
2025-07-30 06:49:22 +00:00
with file_path.open("w") as f:
f.write(function)
2026-01-29 09:39:48 +00:00
2025-07-30 06:49:22 +00:00
functions_found = find_all_functions_in_file(file_path)
2025-05-30 22:13:10 +00:00
assert functions_found == {}
def test_find_top_level_function_or_method():
2025-07-30 06:49:22 +00:00
with tempfile.TemporaryDirectory() as temp_dir:
temp_dir_path = Path(temp_dir)
file_path = temp_dir_path / "test_function.py"
2026-01-29 09:39:48 +00:00
2025-07-30 06:49:22 +00:00
with file_path.open("w") as f:
f.write(
"""def functionA():
def functionB():
return 5
class E:
def functionF():
pass
return functionA()
class A:
def functionC():
def functionD():
pass
return 6
class AirbyteEntrypoint(object):
@staticmethod
def handle_record_counts(message: AirbyteMessage, stream_message_count: DefaultDict[HashableStreamDescriptor, float]) -> AirbyteMessage:
2024-08-07 03:11:54 +00:00
return "idontcare"
@classmethod
def functionE(cls, num):
return AirbyteEntrypoint.handle_record_counts(num)
def non_classmethod_function(cls, name):
return cls.name
"""
2025-07-30 06:49:22 +00:00
)
2026-01-29 09:39:48 +00:00
2025-07-30 06:49:22 +00:00
assert inspect_top_level_functions_or_methods(file_path, "functionA").is_top_level
assert not inspect_top_level_functions_or_methods(file_path, "functionB").is_top_level
assert inspect_top_level_functions_or_methods(file_path, "functionC", class_name="A").is_top_level
assert not inspect_top_level_functions_or_methods(file_path, "functionD", class_name="A").is_top_level
assert not inspect_top_level_functions_or_methods(file_path, "functionF", class_name="E").is_top_level
assert not inspect_top_level_functions_or_methods(file_path, "functionA").has_args
staticmethod_func = inspect_top_level_functions_or_methods(
2025-07-30 06:49:22 +00:00
file_path, "handle_record_counts", class_name=None, line_no=15
)
assert staticmethod_func.is_staticmethod
assert staticmethod_func.staticmethod_class_name == "AirbyteEntrypoint"
assert inspect_top_level_functions_or_methods(
2025-07-30 06:49:22 +00:00
file_path, "functionE", class_name="AirbyteEntrypoint"
).is_classmethod
assert not inspect_top_level_functions_or_methods(
2025-07-30 06:49:22 +00:00
file_path, "non_classmethod_function", class_name="AirbyteEntrypoint"
).is_top_level
# needed because this will be traced with a class_name being passed
2025-05-30 22:13:10 +00:00
# we want to write invalid code to ensure that the function discovery does not crash
2025-07-30 06:49:22 +00:00
with tempfile.TemporaryDirectory() as temp_dir:
temp_dir_path = Path(temp_dir)
file_path = temp_dir_path / "test_function.py"
2026-01-29 09:39:48 +00:00
2025-07-30 06:49:22 +00:00
with file_path.open("w") as f:
f.write(
"""def functionA():
2025-05-30 22:13:10 +00:00
"""
2025-07-30 06:49:22 +00:00
)
2026-01-29 09:39:48 +00:00
2025-07-30 06:49:22 +00:00
assert not inspect_top_level_functions_or_methods(file_path, "functionA")
2026-01-29 09:39:48 +00:00
def test_class_method_discovery():
2025-07-30 06:49:22 +00:00
with tempfile.TemporaryDirectory() as temp_dir:
temp_dir_path = Path(temp_dir)
file_path = temp_dir_path / "test_function.py"
2026-01-29 09:39:48 +00:00
2025-07-30 06:49:22 +00:00
with file_path.open("w") as f:
f.write(
"""class A:
def functionA():
return True
def functionB():
return False
class X:
def functionA():
return True
def functionB():
2024-05-02 23:51:32 +00:00
return False
def functionA():
return True"""
2025-07-30 06:49:22 +00:00
)
2026-01-29 09:39:48 +00:00
2024-10-24 01:55:24 +00:00
test_config = TestConfig(
tests_root="tests", project_root_path=".", test_framework="pytest", tests_project_rootdir=Path()
)
2025-07-03 05:44:38 +00:00
functions, functions_count, _ = get_functions_to_optimize(
optimize_all=None,
replay_test=None,
2025-07-30 06:49:22 +00:00
file=file_path,
only_get_this_function="A.functionA",
test_cfg=test_config,
2024-10-13 00:07:42 +00:00
ignore_paths=[Path("/bruh/")],
2025-07-30 06:49:22 +00:00
project_root=file_path.parent,
module_root=file_path.parent,
)
assert len(functions) == 1
for file in functions:
assert functions[file][0].qualified_name == "A.functionA"
assert functions[file][0].function_name == "functionA"
assert functions[file][0].top_level_parent_name == "A"
2025-07-03 05:44:38 +00:00
functions, functions_count, _ = get_functions_to_optimize(
optimize_all=None,
replay_test=None,
2025-07-30 06:49:22 +00:00
file=file_path,
only_get_this_function="X.functionA",
test_cfg=test_config,
2024-10-13 00:07:42 +00:00
ignore_paths=[Path("/bruh/")],
2025-07-30 06:49:22 +00:00
project_root=file_path.parent,
module_root=file_path.parent,
)
assert len(functions) == 1
for file in functions:
assert functions[file][0].qualified_name == "X.functionA"
assert functions[file][0].function_name == "functionA"
assert functions[file][0].top_level_parent_name == "X"
2024-05-02 23:51:32 +00:00
2025-07-03 05:44:38 +00:00
functions, functions_count, _ = get_functions_to_optimize(
2024-05-02 23:51:32 +00:00
optimize_all=None,
replay_test=None,
2025-07-30 06:49:22 +00:00
file=file_path,
only_get_this_function="functionA",
2024-05-02 23:51:32 +00:00
test_cfg=test_config,
2024-10-13 00:07:42 +00:00
ignore_paths=[Path("/bruh/")],
2025-07-30 06:49:22 +00:00
project_root=file_path.parent,
module_root=file_path.parent,
2024-05-02 23:51:32 +00:00
)
assert len(functions) == 1
for file in functions:
assert functions[file][0].qualified_name == "functionA"
assert functions[file][0].function_name == "functionA"
2024-10-31 19:03:50 +00:00
2025-05-30 22:13:10 +00:00
def test_nested_function():
2025-07-30 06:49:22 +00:00
with tempfile.TemporaryDirectory() as temp_dir:
temp_dir_path = Path(temp_dir)
file_path = temp_dir_path / "test_function.py"
2026-01-29 09:39:48 +00:00
2025-07-30 06:49:22 +00:00
with file_path.open("w") as f:
f.write(
2026-01-29 09:39:48 +00:00
"""
2025-05-30 22:13:10 +00:00
import copy
def propagate_attributes(
nodes: dict[str, dict], edges: list[dict], source_node_id: str, attribute: str
) -> dict[str, dict]:
modified_nodes = copy.deepcopy(nodes)
# Build an adjacency list for faster traversal
adjacency = {}
for edge in edges:
src = edge["source"]
tgt = edge["target"]
if src not in adjacency:
adjacency[src] = []
adjacency[src].append(tgt)
# Track visited nodes to avoid cycles
visited = set()
def traverse(node_id):
if node_id in visited:
return
visited.add(node_id)
# Propagate attribute from source node
if (
node_id != source_node_id
and source_node_id in modified_nodes
and attribute in modified_nodes[source_node_id]
):
if node_id in modified_nodes:
modified_nodes[node_id][attribute] = modified_nodes[source_node_id][
attribute
]
# Continue propagation to neighbors
for neighbor in adjacency.get(node_id, []):
traverse(neighbor)
traverse(source_node_id)
return modified_nodes
"""
2025-07-30 06:49:22 +00:00
)
2026-01-29 09:39:48 +00:00
2025-05-30 22:13:10 +00:00
test_config = TestConfig(
tests_root="tests", project_root_path=".", test_framework="pytest", tests_project_rootdir=Path()
)
2025-07-03 05:44:38 +00:00
functions, functions_count, _ = get_functions_to_optimize(
2025-05-30 22:13:10 +00:00
optimize_all=None,
replay_test=None,
2025-07-30 06:49:22 +00:00
file=file_path,
2025-05-30 22:13:10 +00:00
test_cfg=test_config,
only_get_this_function=None,
ignore_paths=[Path("/bruh/")],
2025-07-30 06:49:22 +00:00
project_root=file_path.parent,
module_root=file_path.parent,
2025-05-30 22:13:10 +00:00
)
assert len(functions) == 1
assert functions_count == 1
2025-07-30 06:49:22 +00:00
with tempfile.TemporaryDirectory() as temp_dir:
temp_dir_path = Path(temp_dir)
file_path = temp_dir_path / "test_function.py"
2026-01-29 09:39:48 +00:00
2025-07-30 06:49:22 +00:00
with file_path.open("w") as f:
f.write(
2026-01-29 09:39:48 +00:00
"""
2025-05-30 22:13:10 +00:00
def outer_function():
def inner_function():
pass
return inner_function
"""
2025-07-30 06:49:22 +00:00
)
2026-01-29 09:39:48 +00:00
2025-05-30 22:13:10 +00:00
test_config = TestConfig(
tests_root="tests", project_root_path=".", test_framework="pytest", tests_project_rootdir=Path()
)
2025-07-03 05:44:38 +00:00
functions, functions_count, _ = get_functions_to_optimize(
2025-05-30 22:13:10 +00:00
optimize_all=None,
replay_test=None,
2025-07-30 06:49:22 +00:00
file=file_path,
2025-05-30 22:13:10 +00:00
test_cfg=test_config,
only_get_this_function=None,
ignore_paths=[Path("/bruh/")],
2025-07-30 06:49:22 +00:00
project_root=file_path.parent,
module_root=file_path.parent,
2025-05-30 22:13:10 +00:00
)
assert len(functions) == 1
assert functions_count == 1
2025-07-30 06:49:22 +00:00
with tempfile.TemporaryDirectory() as temp_dir:
temp_dir_path = Path(temp_dir)
file_path = temp_dir_path / "test_function.py"
2026-01-29 09:39:48 +00:00
2025-07-30 06:49:22 +00:00
with file_path.open("w") as f:
f.write(
2026-01-29 09:39:48 +00:00
"""
2025-05-30 22:13:10 +00:00
def outer_function():
def inner_function():
pass
def another_inner_function():
pass
return inner_function, another_inner_function
"""
2025-07-30 06:49:22 +00:00
)
2026-01-29 09:39:48 +00:00
2025-05-30 22:13:10 +00:00
test_config = TestConfig(
tests_root="tests", project_root_path=".", test_framework="pytest", tests_project_rootdir=Path()
)
2025-07-03 05:44:38 +00:00
functions, functions_count, _ = get_functions_to_optimize(
2025-05-30 22:13:10 +00:00
optimize_all=None,
replay_test=None,
2025-07-30 06:49:22 +00:00
file=file_path,
2025-05-30 22:13:10 +00:00
test_cfg=test_config,
only_get_this_function=None,
ignore_paths=[Path("/bruh/")],
2025-07-30 06:49:22 +00:00
project_root=file_path.parent,
module_root=file_path.parent,
2025-05-30 22:13:10 +00:00
)
assert len(functions) == 1
assert functions_count == 1
2024-10-31 19:03:50 +00:00
def test_filter_files_optimized():
tests_root = Path("tests").resolve()
module_root = Path().resolve()
ignore_paths = []
file_path_test = Path("tests/test_function_discovery.py").resolve()
file_path_same_level = Path("file.py").resolve()
file_path_different_level = Path("src/file.py").resolve()
file_path_above_level = Path("../file.py").resolve()
assert not filter_files_optimized(file_path_test, tests_root, ignore_paths, module_root)
assert filter_files_optimized(file_path_same_level, tests_root, ignore_paths, module_root)
assert filter_files_optimized(file_path_different_level, tests_root, ignore_paths, module_root)
assert not filter_files_optimized(file_path_above_level, tests_root, ignore_paths, module_root)
2025-06-04 21:14:39 +00:00
2026-01-29 09:39:48 +00:00
def test_filter_files_optimized_same_root(tmp_path):
"""When testsRoot == moduleRoot (collocated tests pattern), use pattern matching instead of directory matching."""
src = tmp_path / "src"
src.mkdir()
# Both roots point to the same directory
tests_root = src
module_root = src
source_file = src / "utils.ts"
source_file.touch()
nested_source = src / "lib" / "helpers.ts"
nested_source.parent.mkdir(parents=True, exist_ok=True)
nested_source.touch()
# Test files by naming convention
test_spec = src / "utils.spec.ts"
test_spec.touch()
test_dot = src / "utils.test.ts"
test_dot.touch()
# Test files by directory convention
tests_dir = src / "__tests__" / "utils.ts"
tests_dir.parent.mkdir(parents=True, exist_ok=True)
tests_dir.touch()
ignore_paths: list[Path] = []
# Source files should pass filter (not excluded)
assert filter_files_optimized(source_file, tests_root, ignore_paths, module_root)
assert filter_files_optimized(nested_source, tests_root, ignore_paths, module_root)
# Test files should be excluded by pattern matching
assert not filter_files_optimized(test_spec, tests_root, ignore_paths, module_root)
assert not filter_files_optimized(test_dot, tests_root, ignore_paths, module_root)
assert not filter_files_optimized(tests_dir, tests_root, ignore_paths, module_root)
def test_filter_files_optimized_tests_root_contains_module_root(tmp_path):
"""When tests_root is a parent of module_root, use pattern matching."""
project = tmp_path / "project"
src = project / "src"
src.mkdir(parents=True)
# testsRoot is parent of moduleRoot
tests_root = project
module_root = src
source_file = src / "index.ts"
source_file.touch()
test_file = src / "index.test.ts"
test_file.touch()
ignore_paths: list[Path] = []
assert filter_files_optimized(source_file, tests_root, ignore_paths, module_root)
assert not filter_files_optimized(test_file, tests_root, ignore_paths, module_root)
2025-06-04 21:14:39 +00:00
def test_filter_functions():
2025-06-04 23:16:30 +00:00
with tempfile.TemporaryDirectory() as temp_dir_str:
temp_dir = Path(temp_dir_str)
2026-01-29 09:39:48 +00:00
2025-06-04 23:16:30 +00:00
# Create a test file in the temporary directory
test_file_path = temp_dir.joinpath("test_get_functions_to_optimize.py")
with test_file_path.open("w") as f:
f.write(
2026-01-29 09:39:48 +00:00
"""
2025-06-04 21:14:39 +00:00
import copy
def propagate_attributes(
nodes: dict[str, dict], edges: list[dict], source_node_id: str, attribute: str
) -> dict[str, dict]:
modified_nodes = copy.deepcopy(nodes)
# Build an adjacency list for faster traversal
adjacency = {}
for edge in edges:
src = edge["source"]
tgt = edge["target"]
if src not in adjacency:
adjacency[src] = []
adjacency[src].append(tgt)
# Track visited nodes to avoid cycles
visited = set()
def traverse(node_id):
if node_id in visited:
return
visited.add(node_id)
# Propagate attribute from source node
if (
node_id != source_node_id
and source_node_id in modified_nodes
and attribute in modified_nodes[source_node_id]
):
if node_id in modified_nodes:
modified_nodes[node_id][attribute] = modified_nodes[source_node_id][
attribute
]
# Continue propagation to neighbors
for neighbor in adjacency.get(node_id, []):
traverse(neighbor)
traverse(source_node_id)
return modified_nodes
2025-06-04 21:42:07 +00:00
def vanilla_function():
return "This is a vanilla function."
def not_in_checkpoint_function():
return "This function is not in the checkpoint."
2025-06-04 21:14:39 +00:00
"""
2025-06-04 23:16:30 +00:00
)
2025-06-04 21:14:39 +00:00
2025-06-04 23:16:30 +00:00
discovered = find_all_functions_in_file(test_file_path)
modified_functions = {test_file_path: discovered[test_file_path]}
2025-12-02 06:23:40 +00:00
# Use an absolute path for tests_root that won't match the temp directory
# This avoids path resolution issues in CI where the working directory might differ
tests_root_absolute = (temp_dir.parent / "nonexistent_tests_dir").resolve()
2026-01-29 09:39:48 +00:00
with unittest.mock.patch(
"codeflash.discovery.functions_to_optimize.get_blocklisted_functions", return_value={}
):
2025-12-02 06:23:40 +00:00
filtered, count = filter_functions(
modified_functions,
tests_root=tests_root_absolute,
ignore_paths=[],
project_root=temp_dir,
module_root=temp_dir,
)
2025-06-04 23:16:30 +00:00
function_names = [fn.function_name for fn in filtered.get(test_file_path, [])]
2025-06-04 21:14:39 +00:00
assert "propagate_attributes" in function_names
2025-06-04 21:42:07 +00:00
assert count == 3
2025-06-04 23:16:30 +00:00
# Create a tests directory inside our temp directory
tests_root_dir = temp_dir.joinpath("tests")
tests_root_dir.mkdir(exist_ok=True)
2026-01-29 09:39:48 +00:00
2025-06-04 23:16:30 +00:00
test_file_path = tests_root_dir.joinpath("test_functions.py")
with test_file_path.open("w") as f:
f.write(
2026-01-29 09:39:48 +00:00
"""
2025-06-04 21:42:07 +00:00
def test_function_in_tests_dir():
return "This function is in a test directory and should be filtered out."
"""
2025-06-04 23:16:30 +00:00
)
2026-01-29 09:39:48 +00:00
2025-06-04 23:16:30 +00:00
discovered_test_file = find_all_functions_in_file(test_file_path)
modified_functions_test = {test_file_path: discovered_test_file.get(test_file_path, [])}
2026-01-29 09:39:48 +00:00
2025-06-04 23:16:30 +00:00
filtered_test_file, count_test_file = filter_functions(
modified_functions_test,
tests_root=tests_root_dir,
2025-06-04 21:42:07 +00:00
ignore_paths=[],
2025-06-04 23:16:30 +00:00
project_root=temp_dir,
module_root=temp_dir,
2025-06-04 21:42:07 +00:00
)
2026-01-29 09:39:48 +00:00
2025-06-04 23:16:30 +00:00
assert not filtered_test_file
assert count_test_file == 0
# Test ignored directory
ignored_dir = temp_dir.joinpath("ignored_dir")
ignored_dir.mkdir(exist_ok=True)
ignored_file_path = ignored_dir.joinpath("ignored_file.py")
with ignored_file_path.open("w") as f:
f.write("def ignored_func(): return 1")
2026-01-29 09:39:48 +00:00
2025-06-04 23:16:30 +00:00
discovered_ignored = find_all_functions_in_file(ignored_file_path)
modified_functions_ignored = {ignored_file_path: discovered_ignored.get(ignored_file_path, [])}
2025-06-04 21:42:07 +00:00
2025-06-04 23:16:30 +00:00
filtered_ignored, count_ignored = filter_functions(
modified_functions_ignored,
2025-06-04 21:42:07 +00:00
tests_root=Path("tests"),
2025-06-04 23:16:30 +00:00
ignore_paths=[ignored_dir],
project_root=temp_dir,
module_root=temp_dir,
2025-06-04 21:42:07 +00:00
)
2025-06-04 23:16:30 +00:00
assert not filtered_ignored
assert count_ignored == 0
# Test submodule paths
2026-01-29 09:39:48 +00:00
with unittest.mock.patch(
"codeflash.discovery.functions_to_optimize.ignored_submodule_paths",
return_value=[str(temp_dir.joinpath("submodule_dir"))],
):
2025-06-04 23:16:30 +00:00
submodule_dir = temp_dir.joinpath("submodule_dir")
submodule_dir.mkdir(exist_ok=True)
submodule_file_path = submodule_dir.joinpath("submodule_file.py")
with submodule_file_path.open("w") as f:
f.write("def submodule_func(): return 1")
2026-01-29 09:39:48 +00:00
2025-06-04 23:16:30 +00:00
discovered_submodule = find_all_functions_in_file(submodule_file_path)
modified_functions_submodule = {submodule_file_path: discovered_submodule.get(submodule_file_path, [])}
2026-01-29 09:39:48 +00:00
2025-06-04 23:16:30 +00:00
filtered_submodule, count_submodule = filter_functions(
modified_functions_submodule,
tests_root=Path("tests"),
ignore_paths=[],
project_root=temp_dir,
module_root=temp_dir,
)
assert not filtered_submodule
assert count_submodule == 0
# Test site packages
2026-01-29 09:39:48 +00:00
with unittest.mock.patch(
"codeflash.discovery.functions_to_optimize.path_belongs_to_site_packages", return_value=True
):
2025-06-04 23:16:30 +00:00
site_package_file_path = temp_dir.joinpath("site_package_file.py")
with site_package_file_path.open("w") as f:
f.write("def site_package_func(): return 1")
discovered_site_package = find_all_functions_in_file(site_package_file_path)
2026-01-29 09:39:48 +00:00
modified_functions_site_package = {
site_package_file_path: discovered_site_package.get(site_package_file_path, [])
}
2025-06-04 23:16:30 +00:00
filtered_site_package, count_site_package = filter_functions(
modified_functions_site_package,
tests_root=Path("tests"),
ignore_paths=[],
project_root=temp_dir,
module_root=temp_dir,
)
assert not filtered_site_package
assert count_site_package == 0
2026-01-29 09:39:48 +00:00
2025-06-04 23:16:30 +00:00
# Test outside module root
parent_dir = temp_dir.parent
outside_module_root_path = parent_dir.joinpath("outside_module_root_file.py")
try:
with outside_module_root_path.open("w") as f:
f.write("def func_outside_module_root(): return 1")
2026-01-29 09:39:48 +00:00
2025-06-04 23:16:30 +00:00
discovered_outside_module = find_all_functions_in_file(outside_module_root_path)
2026-01-29 09:39:48 +00:00
modified_functions_outside_module = {
outside_module_root_path: discovered_outside_module.get(outside_module_root_path, [])
}
2025-06-04 23:16:30 +00:00
filtered_outside_module, count_outside_module = filter_functions(
modified_functions_outside_module,
tests_root=Path("tests"),
ignore_paths=[],
project_root=temp_dir,
module_root=temp_dir,
)
assert not filtered_outside_module
assert count_outside_module == 0
finally:
outside_module_root_path.unlink(missing_ok=True)
# Test invalid module name
invalid_module_file_path = temp_dir.joinpath("invalid-module-name.py")
with invalid_module_file_path.open("w") as f:
f.write("def func_in_invalid_module(): return 1")
discovered_invalid_module = find_all_functions_in_file(invalid_module_file_path)
2026-01-29 09:39:48 +00:00
modified_functions_invalid_module = {
invalid_module_file_path: discovered_invalid_module.get(invalid_module_file_path, [])
}
2025-06-04 23:16:30 +00:00
filtered_invalid_module, count_invalid_module = filter_functions(
modified_functions_invalid_module,
2025-06-04 21:42:07 +00:00
tests_root=Path("tests"),
ignore_paths=[],
2025-06-04 23:16:30 +00:00
project_root=temp_dir,
module_root=temp_dir,
2025-06-04 21:42:07 +00:00
)
2025-06-04 23:16:30 +00:00
assert not filtered_invalid_module
assert count_invalid_module == 0
original_file_path = temp_dir.joinpath("test_get_functions_to_optimize.py")
2026-01-29 09:39:48 +00:00
with unittest.mock.patch(
"codeflash.discovery.functions_to_optimize.get_blocklisted_functions",
return_value={original_file_path.name: {"propagate_attributes", "other_blocklisted_function"}},
):
2025-06-04 23:16:30 +00:00
filtered_funcs, count = filter_functions(
modified_functions,
tests_root=Path("tests"),
ignore_paths=[],
project_root=temp_dir,
module_root=temp_dir,
)
assert "propagate_attributes" not in [fn.function_name for fn in filtered_funcs.get(original_file_path, [])]
assert count == 2
module_name = "test_get_functions_to_optimize"
qualified_name_for_checkpoint = f"{module_name}.propagate_attributes"
other_qualified_name_for_checkpoint = f"{module_name}.vanilla_function"
2026-01-29 09:39:48 +00:00
with unittest.mock.patch(
"codeflash.discovery.functions_to_optimize.get_blocklisted_functions", return_value={}
):
2025-06-04 23:16:30 +00:00
filtered_checkpoint, count_checkpoint = filter_functions(
modified_functions,
tests_root=Path("tests"),
ignore_paths=[],
project_root=temp_dir,
module_root=temp_dir,
2026-01-29 09:39:48 +00:00
previous_checkpoint_functions={
qualified_name_for_checkpoint: {"status": "optimized"},
other_qualified_name_for_checkpoint: {},
},
2025-06-04 23:16:30 +00:00
)
assert filtered_checkpoint.get(original_file_path)
assert count_checkpoint == 1
remaining_functions = [fn.function_name for fn in filtered_checkpoint.get(original_file_path, [])]
assert "not_in_checkpoint_function" in remaining_functions
assert "propagate_attributes" not in remaining_functions
assert "vanilla_function" not in remaining_functions
files_and_funcs = get_all_files_and_functions(module_root_path=temp_dir, ignore_paths=[])
2026-01-29 09:39:48 +00:00
assert len(files_and_funcs) == 6
def test_filter_functions_tests_root_overlaps_source():
"""Test that source files are not filtered when tests_root equals module_root or project_root.
This is a critical test for monorepo structures where tests live alongside source code
(e.g., TypeScript projects with .test.ts files in the same directories as source).
"""
with tempfile.TemporaryDirectory() as temp_dir_str:
temp_dir = Path(temp_dir_str)
# Create a source file (NOT a test file)
source_file = temp_dir / "utils.py"
with source_file.open("w") as f:
f.write("""
def process_data(items):
return [item * 2 for item in items]
def calculate_sum(numbers):
return sum(numbers)
""")
# Create a test file with standard naming pattern
test_file = temp_dir / "utils.test.py"
with test_file.open("w") as f:
f.write("""
def test_process_data():
return "test"
""")
# Create a test file with _test suffix pattern
test_file_underscore = temp_dir / "utils_test.py"
with test_file_underscore.open("w") as f:
f.write("""
def test_calculate_sum():
return "test"
""")
# Create a spec file
spec_file = temp_dir / "utils.spec.py"
with spec_file.open("w") as f:
f.write("""
def spec_function():
return "spec"
""")
# Create a file in a tests subdirectory
tests_subdir = temp_dir / "tests"
tests_subdir.mkdir()
tests_subdir_file = tests_subdir / "test_main.py"
with tests_subdir_file.open("w") as f:
f.write("""
def test_in_tests_dir():
return "test"
""")
# Create a file in __tests__ subdirectory (common in JS/TS projects)
dunder_tests_subdir = temp_dir / "__tests__"
dunder_tests_subdir.mkdir()
dunder_tests_file = dunder_tests_subdir / "main.py"
with dunder_tests_file.open("w") as f:
f.write("""
def test_in_dunder_tests():
return "test"
""")
# Discover all functions
discovered_source = find_all_functions_in_file(source_file)
discovered_test = find_all_functions_in_file(test_file)
discovered_test_underscore = find_all_functions_in_file(test_file_underscore)
discovered_spec = find_all_functions_in_file(spec_file)
discovered_tests_dir = find_all_functions_in_file(tests_subdir_file)
discovered_dunder_tests = find_all_functions_in_file(dunder_tests_file)
# Combine all discovered functions
all_functions = {}
for discovered in [
discovered_source,
discovered_test,
discovered_test_underscore,
discovered_spec,
discovered_tests_dir,
discovered_dunder_tests,
]:
all_functions.update(discovered)
# Test Case 1: tests_root == module_root (overlapping case)
# This is the bug scenario where all functions were being filtered
with unittest.mock.patch(
"codeflash.discovery.functions_to_optimize.get_blocklisted_functions", return_value={}
):
filtered, count = filter_functions(
all_functions,
tests_root=temp_dir, # Same as module_root
ignore_paths=[],
project_root=temp_dir,
module_root=temp_dir, # Same as tests_root
)
# Strict check: only source_file should remain in filtered results
assert set(filtered.keys()) == {source_file}, (
f"Expected only source file in filtered results, got: {set(filtered.keys())}"
)
# Strict check: exactly these two functions should be present
source_functions = sorted([fn.function_name for fn in filtered.get(source_file, [])])
assert source_functions == ["calculate_sum", "process_data"], (
f"Expected ['calculate_sum', 'process_data'], got {source_functions}"
)
# Strict check: exactly 2 functions remaining
assert count == 2, f"Expected exactly 2 functions, got {count}"
# Test Case 2: tests_root == project_root (another overlapping case)
with unittest.mock.patch(
"codeflash.discovery.functions_to_optimize.get_blocklisted_functions", return_value={}
):
filtered2, count2 = filter_functions(
{source_file: discovered_source[source_file]},
tests_root=temp_dir, # Same as project_root
ignore_paths=[],
project_root=temp_dir,
module_root=temp_dir,
)
# Strict check: only source_file should remain
assert set(filtered2.keys()) == {source_file}, (
f"Expected only source file when tests_root == project_root, got: {set(filtered2.keys())}"
)
assert count2 == 2, f"Expected exactly 2 functions, got {count2}"
def test_filter_functions_strict_string_matching():
"""Test that test file pattern matching uses strict string matching.
Ensures patterns like '.test.' only match actual test files and don't
accidentally match files with similar names like 'contest.py' or 'latest.py'.
"""
with tempfile.TemporaryDirectory() as temp_dir_str:
temp_dir = Path(temp_dir_str)
# Files that should NOT be filtered (contain 'test' as substring but not as pattern)
contest_file = temp_dir / "contest.py"
with contest_file.open("w") as f:
f.write("def run_contest(): return 1")
latest_file = temp_dir / "latest.py"
with latest_file.open("w") as f:
f.write("def get_latest(): return 1")
attestation_file = temp_dir / "attestation.py"
with attestation_file.open("w") as f:
f.write("def verify_attestation(): return 1")
# File that SHOULD be filtered (matches .test. pattern)
actual_test_file = temp_dir / "utils.test.py"
with actual_test_file.open("w") as f:
f.write("def test_utils(): return 1")
# File that SHOULD be filtered (matches _test. pattern)
underscore_test_file = temp_dir / "utils_test.py"
with underscore_test_file.open("w") as f:
f.write("def test_stuff(): return 1")
# Discover all functions
all_functions = {}
for file_path in [contest_file, latest_file, attestation_file, actual_test_file, underscore_test_file]:
discovered = find_all_functions_in_file(file_path)
all_functions.update(discovered)
with unittest.mock.patch(
"codeflash.discovery.functions_to_optimize.get_blocklisted_functions", return_value={}
):
filtered, count = filter_functions(
all_functions,
tests_root=temp_dir, # Overlapping case to trigger pattern matching
ignore_paths=[],
project_root=temp_dir,
module_root=temp_dir,
)
# Strict check: exactly these 3 files should remain (those with 'test' as substring only)
expected_files = {contest_file, latest_file, attestation_file}
assert set(filtered.keys()) == expected_files, f"Expected files {expected_files}, got {set(filtered.keys())}"
# Strict check: each file should have exactly 1 function with the expected name
assert [fn.function_name for fn in filtered[contest_file]] == ["run_contest"], (
f"Expected ['run_contest'], got {[fn.function_name for fn in filtered[contest_file]]}"
)
assert [fn.function_name for fn in filtered[latest_file]] == ["get_latest"], (
f"Expected ['get_latest'], got {[fn.function_name for fn in filtered[latest_file]]}"
)
assert [fn.function_name for fn in filtered[attestation_file]] == ["verify_attestation"], (
f"Expected ['verify_attestation'], got {[fn.function_name for fn in filtered[attestation_file]]}"
)
# Strict check: exactly 3 functions remaining
assert count == 3, f"Expected exactly 3 functions, got {count}"
def test_filter_functions_test_directory_patterns():
"""Test that test directory patterns work correctly with strict matching.
Ensures that /test/, /tests/, and /__tests__/ patterns only match actual
test directories and not directories that happen to contain 'test' in name.
"""
with tempfile.TemporaryDirectory() as temp_dir_str:
temp_dir = Path(temp_dir_str)
# Directory that should NOT be filtered (contains 'test' but not as /test/ pattern)
contest_dir = temp_dir / "contest_results"
contest_dir.mkdir()
contest_file = contest_dir / "scores.py"
with contest_file.open("w") as f:
f.write("def get_scores(): return [1, 2, 3]")
latest_dir = temp_dir / "latest_data"
latest_dir.mkdir()
latest_file = latest_dir / "data.py"
with latest_file.open("w") as f:
f.write("def load_data(): return {}")
# Directory that SHOULD be filtered (matches /tests/ pattern)
tests_dir = temp_dir / "tests"
tests_dir.mkdir()
tests_file = tests_dir / "test_main.py"
with tests_file.open("w") as f:
f.write("def test_main(): return True")
# Directory that SHOULD be filtered (matches /test/ pattern - singular)
test_dir = temp_dir / "test"
test_dir.mkdir()
test_file = test_dir / "test_utils.py"
with test_file.open("w") as f:
f.write("def test_utils(): return True")
# Directory that SHOULD be filtered (matches /__tests__/ pattern)
dunder_tests_dir = temp_dir / "__tests__"
dunder_tests_dir.mkdir()
dunder_file = dunder_tests_dir / "component.py"
with dunder_file.open("w") as f:
f.write("def test_component(): return True")
# Nested test directory
src_dir = temp_dir / "src"
src_dir.mkdir()
nested_tests_dir = src_dir / "tests"
nested_tests_dir.mkdir()
nested_test_file = nested_tests_dir / "test_nested.py"
with nested_test_file.open("w") as f:
f.write("def test_nested(): return True")
# Discover all functions
all_functions = {}
for file_path in [contest_file, latest_file, tests_file, test_file, dunder_file, nested_test_file]:
discovered = find_all_functions_in_file(file_path)
all_functions.update(discovered)
with unittest.mock.patch(
"codeflash.discovery.functions_to_optimize.get_blocklisted_functions", return_value={}
):
filtered, count = filter_functions(
all_functions,
tests_root=temp_dir, # Overlapping case
ignore_paths=[],
project_root=temp_dir,
module_root=temp_dir,
)
# Strict check: exactly these 2 files should remain (those in non-test directories)
expected_files = {contest_file, latest_file}
assert set(filtered.keys()) == expected_files, f"Expected files {expected_files}, got {set(filtered.keys())}"
# Strict check: each file should have exactly 1 function with the expected name
assert [fn.function_name for fn in filtered[contest_file]] == ["get_scores"], (
f"Expected ['get_scores'], got {[fn.function_name for fn in filtered[contest_file]]}"
)
assert [fn.function_name for fn in filtered[latest_file]] == ["load_data"], (
f"Expected ['load_data'], got {[fn.function_name for fn in filtered[latest_file]]}"
)
# Strict check: exactly 2 functions remaining
assert count == 2, f"Expected exactly 2 functions, got {count}"
def test_filter_functions_non_overlapping_tests_root():
"""Test that the original directory-based filtering still works when tests_root is separate.
When tests_root is a distinct directory (e.g., 'tests/'), the original behavior
of filtering files that start with tests_root should still work.
"""
with tempfile.TemporaryDirectory() as temp_dir_str:
temp_dir = Path(temp_dir_str)
# Create source directory structure
src_dir = temp_dir / "src"
src_dir.mkdir()
source_file = src_dir / "utils.py"
with source_file.open("w") as f:
f.write("def process(): return 1")
# Create a file with .test. pattern in source (should NOT be filtered in non-overlapping mode)
# because directory-based filtering takes precedence
test_in_src = src_dir / "helper.test.py"
with test_in_src.open("w") as f:
f.write("def helper_test(): return 1")
# Create separate tests directory
tests_dir = temp_dir / "tests"
tests_dir.mkdir()
test_file = tests_dir / "test_utils.py"
with test_file.open("w") as f:
f.write("def test_process(): return 1")
# Discover functions
all_functions = {}
for file_path in [source_file, test_in_src, test_file]:
discovered = find_all_functions_in_file(file_path)
all_functions.update(discovered)
# Non-overlapping case: tests_root is a separate directory
with unittest.mock.patch(
"codeflash.discovery.functions_to_optimize.get_blocklisted_functions", return_value={}
):
filtered, count = filter_functions(
all_functions,
tests_root=tests_dir, # Separate from module_root
ignore_paths=[],
project_root=temp_dir,
module_root=src_dir, # Different from tests_root
)
# Strict check: exactly these 2 files should remain (both in src/, not in tests/)
expected_files = {source_file, test_in_src}
assert set(filtered.keys()) == expected_files, f"Expected files {expected_files}, got {set(filtered.keys())}"
# Strict check: each file should have exactly 1 function with the expected name
assert [fn.function_name for fn in filtered[source_file]] == ["process"], (
f"Expected ['process'], got {[fn.function_name for fn in filtered[source_file]]}"
)
assert [fn.function_name for fn in filtered[test_in_src]] == ["helper_test"], (
f"Expected ['helper_test'], got {[fn.function_name for fn in filtered[test_in_src]]}"
)
# Strict check: exactly 2 functions remaining
assert count == 2, f"Expected exactly 2 functions, got {count}"
def test_filter_functions_project_inside_tests_folder():
"""Test that source files are not filtered when project is inside a folder named 'tests'.
This is a critical regression test for projects located at paths like:
- /home/user/tests/myproject/
- /Users/dev/tests/n8n/
The fix ensures that directory pattern matching (e.g., /tests/) is only checked
on the relative path from project_root, not on the full absolute path.
"""
with tempfile.TemporaryDirectory() as outer_temp_dir_str:
outer_temp_dir = Path(outer_temp_dir_str)
# Create a "tests" folder to simulate /home/user/tests/
tests_parent_folder = outer_temp_dir / "tests"
tests_parent_folder.mkdir()
# Create project inside the "tests" folder - simulates /home/user/tests/myproject/
project_dir = tests_parent_folder / "myproject"
project_dir.mkdir()
# Create source file inside the project
src_dir = project_dir / "src"
src_dir.mkdir()
source_file = src_dir / "utils.py"
with source_file.open("w") as f:
f.write("""
def deep_copy(obj):
\"\"\"Deep copy an object.\"\"\"
import copy
return copy.deepcopy(obj)
def compare_values(a, b):
\"\"\"Compare two values.\"\"\"
return a == b
""")
# Create another source file directly in project root
root_source_file = project_dir / "main.py"
with root_source_file.open("w") as f:
f.write("""
def main():
\"\"\"Main entry point.\"\"\"
return 0
""")
# Create actual test files that should be filtered
project_tests_dir = project_dir / "test"
project_tests_dir.mkdir()
test_file = project_tests_dir / "test_utils.py"
with test_file.open("w") as f:
f.write("""
def test_deep_copy():
return True
""")
# Discover functions
all_functions = {}
for file_path in [source_file, root_source_file, test_file]:
discovered = find_all_functions_in_file(file_path)
all_functions.update(discovered)
# Test: project at /outer/tests/myproject with tests_root overlapping
# This simulates: /home/user/tests/n8n with tests_root = /home/user/tests/n8n
with unittest.mock.patch(
"codeflash.discovery.functions_to_optimize.get_blocklisted_functions", return_value={}
):
filtered, count = filter_functions(
all_functions,
tests_root=project_dir, # Same as project_root (overlapping)
ignore_paths=[],
project_root=project_dir, # /outer/tests/myproject
module_root=project_dir,
)
# Strict check: source files should NOT be filtered even though
# the full path contains "/tests/" in the parent directory
expected_files = {source_file, root_source_file}
actual_files = set(filtered.keys())
assert actual_files == expected_files, (
f"Source files were incorrectly filtered when project is inside 'tests' folder.\n"
f"Expected files: {expected_files}\n"
f"Got files: {actual_files}\n"
f"Project path: {project_dir}\n"
f"This indicates the /tests/ pattern matched the parent directory path."
)
# Verify the correct functions are present
source_functions = sorted([fn.function_name for fn in filtered.get(source_file, [])])
assert source_functions == ["compare_values", "deep_copy"], (
f"Expected ['compare_values', 'deep_copy'], got {source_functions}"
)
root_functions = [fn.function_name for fn in filtered.get(root_source_file, [])]
assert root_functions == ["main"], f"Expected ['main'], got {root_functions}"
# Strict check: exactly 3 functions (2 from utils.py + 1 from main.py)
assert count == 3, (
f"Expected exactly 3 functions, got {count}. Some source files may have been incorrectly filtered."
)
# Verify test file was properly filtered (should not be in results)
assert test_file not in filtered, f"Test file {test_file} should have been filtered but wasn't"
def test_filter_functions_typescript_project_in_tests_folder():
"""Test TypeScript-like project structure inside a folder named 'tests'.
This simulates the n8n project structure:
/home/user/tests/n8n/packages/workflow/src/utils.ts
Ensures that TypeScript source files are not incorrectly filtered
when the parent directory happens to be named 'tests'.
"""
with tempfile.TemporaryDirectory() as outer_temp_dir_str:
outer_temp_dir = Path(outer_temp_dir_str)
# Simulate: /home/user/tests/n8n
tests_folder = outer_temp_dir / "tests"
tests_folder.mkdir()
n8n_project = tests_folder / "n8n"
n8n_project.mkdir()
# Simulate: packages/workflow/src/utils.py (using .py for testing)
packages_dir = n8n_project / "packages"
packages_dir.mkdir()
workflow_dir = packages_dir / "workflow"
workflow_dir.mkdir()
src_dir = workflow_dir / "src"
src_dir.mkdir()
# Source file deep in the monorepo structure
utils_file = src_dir / "utils.py"
with utils_file.open("w") as f:
f.write("""
def deep_copy(source):
\"\"\"Create a deep copy of the source object.\"\"\"
if source is None:
return None
return source.copy() if hasattr(source, 'copy') else source
def is_object_empty(obj):
\"\"\"Check if an object is empty.\"\"\"
return len(obj) == 0 if obj else True
""")
# Create test directory inside the package (simulating packages/workflow/test/)
test_dir = workflow_dir / "test"
test_dir.mkdir()
test_file = test_dir / "utils.test.py"
with test_file.open("w") as f:
f.write("""
def test_deep_copy():
return True
def test_is_object_empty():
return True
""")
# Discover functions
all_functions = {}
for file_path in [utils_file, test_file]:
discovered = find_all_functions_in_file(file_path)
all_functions.update(discovered)
# Test with module_root = packages (typical TypeScript monorepo setup)
with unittest.mock.patch(
"codeflash.discovery.functions_to_optimize.get_blocklisted_functions", return_value={}
):
filtered, count = filter_functions(
all_functions,
tests_root=packages_dir, # Overlapping with module_root
ignore_paths=[],
project_root=n8n_project, # /outer/tests/n8n
module_root=packages_dir, # /outer/tests/n8n/packages
)
# Strict check: only the source file should remain
assert set(filtered.keys()) == {utils_file}, (
f"Expected only {utils_file} but got {set(filtered.keys())}.\n"
f"Source files in /outer/tests/n8n/packages/workflow/src/ were incorrectly filtered.\n"
f"The /tests/ pattern in the parent path should not affect filtering."
)
# Verify the correct functions are present
filtered_functions = sorted([fn.function_name for fn in filtered.get(utils_file, [])])
assert filtered_functions == ["deep_copy", "is_object_empty"], (
f"Expected ['deep_copy', 'is_object_empty'], got {filtered_functions}"
)
# Strict check: exactly 2 functions
assert count == 2, f"Expected exactly 2 functions, got {count}"
def test_filter_functions_python_test_prefix_convention():
"""Test that files following Python's test_*.py naming convention are filtered.
Python's standard test file naming uses the test_ prefix (e.g., test_utils.py),
which was previously not caught by the pattern matching in overlapping mode.
"""
with tempfile.TemporaryDirectory() as temp_dir_str:
temp_dir = Path(temp_dir_str)
# Source file that should NOT be filtered
source_file = temp_dir / "utils.py"
with source_file.open("w") as f:
f.write("def process(): return 1")
# Python test file with test_ prefix - SHOULD be filtered
test_prefix_file = temp_dir / "test_utils.py"
with test_prefix_file.open("w") as f:
f.write("def test_process(): return 1")
# conftest.py - SHOULD be filtered
conftest_file = temp_dir / "conftest.py"
with conftest_file.open("w") as f:
f.write("""
import pytest
@pytest.fixture
def sample_data():
return [1, 2, 3]
""")
# File in a test_ prefixed directory - should NOT be filtered by file patterns
# (directory patterns don't cover test_ prefix dirs, which is fine)
test_subdir = temp_dir / "test_integration"
test_subdir.mkdir()
file_in_test_dir = test_subdir / "helpers.py"
with file_in_test_dir.open("w") as f:
f.write("def helper(): return 1")
# test_ prefix file inside a subdirectory - SHOULD be filtered
test_in_subdir = test_subdir / "test_helpers.py"
with test_in_subdir.open("w") as f:
f.write("def test_helper(): return 1")
all_functions = {}
for file_path in [source_file, test_prefix_file, conftest_file, file_in_test_dir, test_in_subdir]:
discovered = find_all_functions_in_file(file_path)
all_functions.update(discovered)
with unittest.mock.patch(
"codeflash.discovery.functions_to_optimize.get_blocklisted_functions", return_value={}
):
filtered, count = filter_functions(
all_functions,
tests_root=temp_dir, # Overlapping case
ignore_paths=[],
2026-02-12 22:03:57 +00:00
project_root=temp_dir,
module_root=temp_dir,
)
# source_file and file_in_test_dir should remain
# test_prefix_file, conftest_file, and test_in_subdir should be filtered
expected_files = {source_file, file_in_test_dir}
assert set(filtered.keys()) == expected_files, f"Expected {expected_files}, got {set(filtered.keys())}"
assert count == 2, f"Expected exactly 2 functions, got {count}"
def test_pytest_fixture_not_discovered():
"""Test that @pytest.fixture decorated functions are not discovered via libcst path."""
from codeflash.languages.python.support import PythonSupport
with tempfile.TemporaryDirectory() as temp_dir_str:
temp_dir = Path(temp_dir_str)
fixture_file = temp_dir / "conftest.py"
with fixture_file.open("w") as f:
f.write("""
import pytest
from pytest import fixture
def regular_function():
return 42
@pytest.fixture
def sample_data():
return [1, 2, 3]
@pytest.fixture()
def sample_config():
return {"key": "value"}
@fixture
def direct_import_fixture():
return "data"
@fixture()
def direct_import_fixture_with_parens():
return "data"
@pytest.fixture(scope="session")
def session_fixture():
return "session"
class TestHelpers:
@pytest.fixture
def class_fixture(self):
return "class_data"
def helper_method(self):
return "helper"
""")
support = PythonSupport()
source = fixture_file.read_text(encoding="utf-8")
functions = support.discover_functions(source, fixture_file)
function_names = [fn.function_name for fn in functions]
assert "regular_function" in function_names
assert "helper_method" in function_names
assert "sample_data" not in function_names
assert "sample_config" not in function_names
assert "direct_import_fixture" not in function_names
assert "direct_import_fixture_with_parens" not in function_names
assert "session_fixture" not in function_names
assert "class_fixture" not in function_names