@@ -84,7 +84,8 @@ def test_code_replacement10() -> None:
8484
8585 code_ctx = get_code_optimization_context (function_to_optimize = func_top_optimize , project_root_path = file_path .parent )
8686 qualified_names = {func .qualified_name for func in code_ctx .helper_functions }
87- assert qualified_names == {"HelperClass.helper_method" } # Nested method should not be in here
87+ # HelperClass.__init__ is now tracked because HelperClass(self.name) instantiates the class
88+ assert qualified_names == {"HelperClass.helper_method" , "HelperClass.__init__" } # Nested method should not be in here
8889 read_write_context , read_only_context = code_ctx .read_writable_code , code_ctx .read_only_context_code
8990 hashing_context = code_ctx .hashing_code_context
9091
@@ -570,6 +571,8 @@ def __call__(self, *args: _P.args, **kwargs: _P.kwargs) -> _R:
570571class AbstractCacheBackend(CacheBackend, Protocol[_KEY_T, _STORE_T]):
571572 """Interface for cache backends used by the persistent cache decorator."""
572573
574+ def __init__(self) -> None: ...
575+
573576 def hash_key(
574577 self,
575578 *,
@@ -1296,6 +1299,8 @@ def __repr__(self) -> str:
12961299```
12971300```python:{ path_to_transform_utils .relative_to (project_root )}
12981301class DataTransformer:
1302+ def __init__(self):
1303+ self.data = None
12991304
13001305 def transform(self, data):
13011306 self.data = data
@@ -1599,7 +1604,11 @@ def __repr__(self) -> str:
15991604 \" \" \" Return a string representation of the DataProcessor.\" \" \"
16001605 return f"DataProcessor(default_prefix={{self.default_prefix!r}})"
16011606```
1602-
1607+ ```python:{ path_to_transform_utils .relative_to (project_root )}
1608+ class DataTransformer:
1609+ def __init__(self):
1610+ self.data = None
1611+ ```
16031612"""
16041613 expected_hashing_context = f"""
16051614```python:utils.py
@@ -1705,13 +1714,19 @@ def test_direct_module_import() -> None:
17051714
17061715 expected_read_only_context = """
17071716```python:utils.py
1717+ import math
17081718from transform_utils import DataTransformer
17091719
17101720class DataProcessor:
17111721 \" \" \" A class for processing data.\" \" \"
17121722
17131723 number = 1
17141724
1725+ def __init__(self, default_prefix: str = "PREFIX_"):
1726+ \" \" \" Initialize the DataProcessor with a default prefix.\" \" \"
1727+ self.default_prefix = default_prefix
1728+ self.number += math.log(self.number)
1729+
17151730 def __repr__(self) -> str:
17161731 \" \" \" Return a string representation of the DataProcessor.\" \" \"
17171732 return f"DataProcessor(default_prefix={self.default_prefix!r})"
@@ -2727,3 +2742,154 @@ async def async_function():
27272742 # Verify correct order
27282743 expected_order = ["GLOBAL_CONSTANT" , "ANOTHER_CONSTANT" , "FINAL_ASSIGNMENT" ]
27292744 assert collector .assignment_order == expected_order
2745+
2746+
2747+ def test_class_instantiation_includes_init_as_helper (tmp_path : Path ) -> None :
2748+ """Test that when a class is instantiated, its __init__ method is tracked as a helper.
2749+
2750+ This test verifies the fix for the bug where class constructors were not
2751+ included in the context when only the class instantiation was called
2752+ (not any other methods). This caused LLMs to not know the constructor
2753+ signatures when generating tests.
2754+ """
2755+ code = '''
2756+ class DataDumper:
2757+ """A class that dumps data."""
2758+
2759+ def __init__(self, data):
2760+ """Initialize with data."""
2761+ self.data = data
2762+
2763+ def dump(self):
2764+ """Dump the data."""
2765+ return self.data
2766+
2767+
2768+ def target_function():
2769+ # Only instantiates DataDumper, doesn't call any other methods
2770+ dumper = DataDumper({"key": "value"})
2771+ return dumper
2772+ '''
2773+ file_path = tmp_path / "test_code.py"
2774+ file_path .write_text (code , encoding = "utf-8" )
2775+ opt = Optimizer (
2776+ Namespace (
2777+ project_root = file_path .parent .resolve (),
2778+ disable_telemetry = True ,
2779+ tests_root = "tests" ,
2780+ test_framework = "pytest" ,
2781+ pytest_cmd = "pytest" ,
2782+ experiment_id = None ,
2783+ test_project_root = Path ().resolve (),
2784+ )
2785+ )
2786+ function_to_optimize = FunctionToOptimize (
2787+ function_name = "target_function" ,
2788+ file_path = file_path ,
2789+ parents = [],
2790+ starting_line = None ,
2791+ ending_line = None ,
2792+ )
2793+
2794+ code_ctx = get_code_optimization_context (function_to_optimize , opt .args .project_root )
2795+
2796+ # The __init__ method should be tracked as a helper since DataDumper() instantiates the class
2797+ qualified_names = {func .qualified_name for func in code_ctx .helper_functions }
2798+ assert "DataDumper.__init__" in qualified_names , (
2799+ "DataDumper.__init__ should be tracked as a helper when the class is instantiated"
2800+ )
2801+
2802+ # The testgen context should contain the class with __init__ (critical for LLM to know constructor)
2803+ testgen_context = code_ctx .testgen_context .markdown
2804+ assert "class DataDumper:" in testgen_context , "DataDumper class should be in testgen context"
2805+ assert "def __init__(self, data):" in testgen_context , (
2806+ "__init__ method should be included in testgen context"
2807+ )
2808+
2809+ # The hashing context should NOT contain __init__ (excluded for stability)
2810+ hashing_context = code_ctx .hashing_code_context
2811+ assert "__init__" not in hashing_context , (
2812+ "__init__ should NOT be in hashing context (excluded for hash stability)"
2813+ )
2814+
2815+
2816+ def test_class_instantiation_preserves_full_class_in_testgen (tmp_path : Path ) -> None :
2817+ """Test that instantiated classes are fully preserved in testgen context.
2818+
2819+ This is specifically for the unstructured LayoutDumper bug where helper classes
2820+ that were instantiated but had no other methods called were being excluded
2821+ from the testgen context.
2822+ """
2823+ code = '''
2824+ class LayoutDumper:
2825+ """Base class for layout dumpers."""
2826+ layout_source: str = "unknown"
2827+
2828+ def __init__(self, layout):
2829+ self._layout = layout
2830+
2831+ def dump(self) -> dict:
2832+ raise NotImplementedError()
2833+
2834+
2835+ class ObjectDetectionLayoutDumper(LayoutDumper):
2836+ """Specific dumper for object detection layouts."""
2837+
2838+ def __init__(self, layout):
2839+ super().__init__(layout)
2840+
2841+ def dump(self) -> dict:
2842+ return {"type": "object_detection", "layout": self._layout}
2843+
2844+
2845+ def dump_layout(layout_type, layout):
2846+ """Dump a layout based on its type."""
2847+ if layout_type == "object_detection":
2848+ dumper = ObjectDetectionLayoutDumper(layout)
2849+ else:
2850+ dumper = LayoutDumper(layout)
2851+ return dumper.dump()
2852+ '''
2853+ file_path = tmp_path / "test_code.py"
2854+ file_path .write_text (code , encoding = "utf-8" )
2855+ opt = Optimizer (
2856+ Namespace (
2857+ project_root = file_path .parent .resolve (),
2858+ disable_telemetry = True ,
2859+ tests_root = "tests" ,
2860+ test_framework = "pytest" ,
2861+ pytest_cmd = "pytest" ,
2862+ experiment_id = None ,
2863+ test_project_root = Path ().resolve (),
2864+ )
2865+ )
2866+ function_to_optimize = FunctionToOptimize (
2867+ function_name = "dump_layout" ,
2868+ file_path = file_path ,
2869+ parents = [],
2870+ starting_line = None ,
2871+ ending_line = None ,
2872+ )
2873+
2874+ code_ctx = get_code_optimization_context (function_to_optimize , opt .args .project_root )
2875+ qualified_names = {func .qualified_name for func in code_ctx .helper_functions }
2876+
2877+ # Both class __init__ methods should be tracked as helpers
2878+ assert "ObjectDetectionLayoutDumper.__init__" in qualified_names , (
2879+ "ObjectDetectionLayoutDumper.__init__ should be tracked"
2880+ )
2881+ assert "LayoutDumper.__init__" in qualified_names , (
2882+ "LayoutDumper.__init__ should be tracked"
2883+ )
2884+
2885+ # The testgen context should include both classes with their __init__ methods
2886+ testgen_context = code_ctx .testgen_context .markdown
2887+ assert "class LayoutDumper:" in testgen_context , "LayoutDumper should be in testgen context"
2888+ assert "class ObjectDetectionLayoutDumper" in testgen_context , (
2889+ "ObjectDetectionLayoutDumper should be in testgen context"
2890+ )
2891+
2892+ # Both __init__ methods should be in the testgen context (so LLM knows constructor signatures)
2893+ assert testgen_context .count ("def __init__" ) >= 2 , (
2894+ "Both __init__ methods should be in testgen context"
2895+ )
0 commit comments