11from __future__ import annotations
22
33import ast
4+ import contextlib
45from collections import defaultdict
56from functools import lru_cache
67from typing import TYPE_CHECKING , Optional , TypeVar
78
9+ import isort
810import libcst as cst
11+ import libcst .matchers as m
912
1013from codeflash .cli_cmds .console import logger
11- from codeflash .code_utils .code_extractor import add_global_assignments , add_needed_imports_from_module
14+ from codeflash .code_utils .code_extractor import (
15+ add_global_assignments ,
16+ add_needed_imports_from_module ,
17+ get_function_qualified_names ,
18+ )
19+ from codeflash .code_utils .config_parser import find_conftest_files
20+ from codeflash .code_utils .line_profile_utils import ImportAdder , add_decorator_to_qualified_function
1221from codeflash .models .models import FunctionParent
1322
1423if TYPE_CHECKING :
@@ -33,18 +42,78 @@ def normalize_code(code: str) -> str:
3342 return ast .unparse (normalize_node (ast .parse (code )))
3443
3544
36- # def modify_autouse_fixture():
37- # # find fixutre definition in conftetst.py (the one closest to the test)
38- # # get fixtures present in override-fixtures in pyproject.toml
39- # # add if marker closest return
40- # conftest_files = find_conftest_files()
41- # for cf_file in conftest_files:
42- # # iterate over all functions in the file
43- # # if function has autouse fixture, modify function to bypass with custom marker
44- # pass
45+ class AutouseFixtureModifier (cst .CSTTransformer ):
46+ def leave_FunctionDef (self , original_node : cst .FunctionDef , updated_node : cst .FunctionDef ) -> cst .FunctionDef :
47+ # Matcher for '@fixture' or '@pytest.fixture'
48+ fixture_decorator_func = m .Name ("fixture" ) | m .Attribute (value = m .Name ("pytest" ), attr = m .Name ("fixture" ))
49+
50+ for decorator in original_node .decorators :
51+ if m .matches (
52+ decorator ,
53+ m .Decorator (
54+ decorator = m .Call (
55+ func = fixture_decorator_func , args = [m .Arg (value = m .Name ("True" ), keyword = m .Name ("autouse" ))]
56+ )
57+ ),
58+ ):
59+ # Found a matching fixture with autouse=True
60+
61+ # 1. The original body of the function will become the 'else' block.
62+ # updated_node.body is an IndentedBlock, which is what cst.Else expects.
63+ else_block = cst .Else (body = updated_node .body )
4564
65+ # 2. Create the new 'if' block that will exit the fixture early.
66+ if_test = cst .parse_expression ('request.node.get_closest_marker("no_autouse")' )
67+ yield_statement = cst .parse_statement ("yield" )
68+ if_body = cst .IndentedBlock (body = [yield_statement ])
4669
47- # reuse line profiler utils to add decorator and import to test fns
70+ # 3. Construct the full if/else statement.
71+ new_if_statement = cst .If (test = if_test , body = if_body , orelse = else_block )
72+
73+ # 4. Replace the entire function's body with our new single statement.
74+ return updated_node .with_changes (body = cst .IndentedBlock (body = [new_if_statement ]))
75+ return updated_node
76+
77+
78+ @contextlib .contextmanager
79+ def disable_autouse (test_path : Path ) -> None :
80+ file_content = test_path .read_text (encoding = "utf-8" )
81+ try :
82+ module = cst .parse_module (file_content )
83+ disable_autouse_fixture = AutouseFixtureModifier ()
84+ modified_module = module .visit (disable_autouse_fixture )
85+ test_path .write_text (modified_module .code , encoding = "utf-8" )
86+ finally :
87+ test_path .write_text (file_content , encoding = "utf-8" )
88+
89+
90+ def modify_autouse_fixture (test_paths : list [Path ]) -> None :
91+ # find fixutre definition in conftetst.py (the one closest to the test)
92+ # get fixtures present in override-fixtures in pyproject.toml
93+ # add if marker closest return
94+ conftest_files = find_conftest_files (test_paths )
95+ for cf_file in conftest_files :
96+ # iterate over all functions in the file
97+ # if function has autouse fixture, modify function to bypass with custom marker
98+ disable_autouse (cf_file )
99+
100+
101+ # # reuse line profiler utils to add decorator and import to test fns
102+ def add_custom_marker_to_all_tests (test_paths : list [Path ]) -> None :
103+ for test_path in test_paths :
104+ # read file
105+ file_content = test_path .read_text (encoding = "utf-8" )
106+ module = cst .parse_module (file_content )
107+ importadder = ImportAdder ("import pytest" )
108+ modified_module = module .visit (importadder )
109+ modified_module = isort .code (modified_module .code , float_to_top = True )
110+ qualified_fn_names = get_function_qualified_names (test_path )
111+ for fn_name in qualified_fn_names :
112+ modified_module = add_decorator_to_qualified_function (
113+ modified_module , fn_name , "pytest.mark.codeflash_no_autouse"
114+ )
115+ # write the modified module back to the file
116+ test_path .write_text (modified_module .code , encoding = "utf-8" )
48117
49118
50119class OptimFunctionCollector (cst .CSTVisitor ):
0 commit comments