From 46c49910cddd9a0f9b371c860b987a0350ab2399 Mon Sep 17 00:00:00 2001 From: mohammed ahmed Date: Fri, 3 Apr 2026 14:30:24 +0000 Subject: [PATCH 1/2] Fix: Recalculate js_project_root per function in monorepos **Issue:** When optimizing multiple functions in a monorepo with nested package.json files (e.g., extensions/discord/package.json), the js_project_root was set once for the first function and reused for all subsequent functions. This caused vitest to look for setupFiles in the wrong directory. **Root Cause:** test_cfg.js_project_root was set during initial setup and never recalculated. When function #1 was in extensions/discord/, all subsequent functions in src/ inherited this wrong project root. **Fix:** - Added _get_js_project_root() method to FunctionOptimizer - Calculate js_project_root fresh for each function using find_node_project_root() - Updated all test execution paths (behavior, performance, line_profile) **Impact:** - Vitest now runs from the correct working directory for each function - setupFiles can be resolved correctly - Functions in different monorepo packages can be optimized correctly Fixes trace IDs: 12d26b00-cbae-49a8-a3cd-c36024ee06ec, 1cde1c65-ef42-4072-afbc-165b0c235688, and 18 others Co-Authored-By: Claude Sonnet 4.5 --- codeflash/languages/function_optimizer.py | 36 ++++- tests/test_js_project_root_per_function.py | 95 +++++++++++++ tests/test_optimizer_js_project_root_bug.py | 148 ++++++++++++++++++++ 3 files changed, 276 insertions(+), 3 deletions(-) create mode 100644 tests/test_js_project_root_per_function.py create mode 100644 tests/test_optimizer_js_project_root_bug.py diff --git a/codeflash/languages/function_optimizer.py b/codeflash/languages/function_optimizer.py index b348a6e46..b462f86c5 100644 --- a/codeflash/languages/function_optimizer.py +++ b/codeflash/languages/function_optimizer.py @@ -3085,6 +3085,30 @@ def run_optimized_candidate( ) ) + def _get_js_project_root(self) -> Path | None: + """Get the JavaScript project root for the current function being optimized. + + This method calculates the js_project_root for each function instead of + caching it in test_cfg. This is important in monorepos where different + functions may belong to different packages/extensions with their own + package.json files. + + Returns: + Path to the JavaScript project root, or None if not a JavaScript project + or if the project root cannot be determined. + """ + # Only calculate for JavaScript/TypeScript projects + if self.function_to_optimize.language not in ("javascript", "typescript"): + return self.test_cfg.js_project_root # Fall back to cached value for non-JS + + # For JS/TS, calculate fresh for each function + from pathlib import Path + + from codeflash.languages.javascript.test_runner import find_node_project_root + + source_file = Path(self.function_to_optimize.file_path) + return find_node_project_root(source_file) + def run_and_parse_tests( self, testing_type: TestingMode, @@ -3103,33 +3127,39 @@ def run_and_parse_tests( coverage_config_file = None try: if testing_type == TestingMode.BEHAVIOR: + # Calculate js_project_root for the current function being optimized + # instead of using cached value from test_cfg, which may be from a different function + js_project_root = self._get_js_project_root() + result_file_path, run_result, coverage_database_file, coverage_config_file = ( self.language_support.run_behavioral_tests( test_paths=test_files, test_env=test_env, cwd=self.project_root, timeout=INDIVIDUAL_TESTCASE_TIMEOUT, - project_root=self.test_cfg.js_project_root, + project_root=js_project_root, enable_coverage=enable_coverage, candidate_index=optimization_iteration, ) ) elif testing_type == TestingMode.LINE_PROFILE: + js_project_root = self._get_js_project_root() result_file_path, run_result = self.language_support.run_line_profile_tests( test_paths=test_files, test_env=test_env, cwd=self.project_root, timeout=INDIVIDUAL_TESTCASE_TIMEOUT, - project_root=self.test_cfg.js_project_root, + project_root=js_project_root, line_profile_output_file=line_profiler_output_file, ) elif testing_type == TestingMode.PERFORMANCE: + js_project_root = self._get_js_project_root() result_file_path, run_result = self.language_support.run_benchmarking_tests( test_paths=test_files, test_env=test_env, cwd=self.project_root, timeout=INDIVIDUAL_TESTCASE_TIMEOUT, - project_root=self.test_cfg.js_project_root, + project_root=js_project_root, min_loops=pytest_min_loops, max_loops=pytest_max_loops, target_duration_seconds=testing_time, diff --git a/tests/test_js_project_root_per_function.py b/tests/test_js_project_root_per_function.py new file mode 100644 index 000000000..1b52a291c --- /dev/null +++ b/tests/test_js_project_root_per_function.py @@ -0,0 +1,95 @@ +"""Test that js_project_root is recalculated per function, not cached.""" + +import tempfile +from pathlib import Path + +import pytest + +from codeflash.languages.javascript.test_runner import find_node_project_root + + +def test_find_node_project_root_returns_different_roots_for_different_files(): + """Test that find_node_project_root returns the correct root for each file.""" + with tempfile.TemporaryDirectory() as tmpdir: + root = Path(tmpdir) + + # Create main project structure + main_project = root / "project" + main_project.mkdir() + (main_project / "package.json").write_text("{}") + (main_project / "src").mkdir() + main_file = main_project / "src" / "main.ts" + main_file.write_text("// main file") + + # Create extension subdirectory with its own package.json + extension_dir = main_project / "extensions" / "discord" + extension_dir.mkdir(parents=True) + (extension_dir / "package.json").write_text("{}") + (extension_dir / "src").mkdir() + extension_file = extension_dir / "src" / "accounts.ts" + extension_file.write_text("// extension file") + + # Test 1: Extension file should return extension directory + result1 = find_node_project_root(extension_file) + assert result1 == extension_dir, ( + f"Expected {extension_dir}, got {result1}" + ) + + # Test 2: Main file should return main project directory + result2 = find_node_project_root(main_file) + assert result2 == main_project, ( + f"Expected {main_project}, got {result2}" + ) + + # Test 3: Calling again with extension file should still return extension dir + result3 = find_node_project_root(extension_file) + assert result3 == extension_dir, ( + f"Expected {extension_dir}, got {result3}" + ) + + +def test_js_project_root_should_be_recalculated_per_function(): + """ + Test the actual bug: when optimizing multiple functions from different + directories, each should get its own js_project_root, not inherit from + the first function. + + This test simulates the scenario where: + 1. Function #1 is in extensions/discord/src/accounts.ts + 2. Function #2 is in src/plugins/commands.ts + 3. Both should get their correct respective project roots + """ + with tempfile.TemporaryDirectory() as tmpdir: + root = Path(tmpdir) + + # Create main project + main_project = root / "project" + main_project.mkdir() + (main_project / "package.json").write_text('{"name": "main"}') + (main_project / "src").mkdir() + (main_project / "test").mkdir() + + # Create extension with its own package.json + extension_dir = main_project / "extensions" / "discord" + extension_dir.mkdir(parents=True) + (extension_dir / "package.json").write_text('{"name": "discord-extension"}') + (extension_dir / "src").mkdir() + + # Files to optimize + extension_file = extension_dir / "src" / "accounts.ts" + extension_file.write_text("export function foo() {}") + + main_file = main_project / "src" / "commands.ts" + main_file.write_text("export function bar() {}") + + # Simulate what happens in Codeflash optimizer + # Function 1 (extension file) sets js_project_root + js_project_root_1 = find_node_project_root(extension_file) + assert js_project_root_1 == extension_dir + + # Function 2 (main file) should get its own root, not inherit from function 1 + js_project_root_2 = find_node_project_root(main_file) + assert js_project_root_2 == main_project, ( + f"Bug reproduced: main file got {js_project_root_2} instead of {main_project}. " + f"This happens when test_cfg.js_project_root is not recalculated per function." + ) diff --git a/tests/test_optimizer_js_project_root_bug.py b/tests/test_optimizer_js_project_root_bug.py new file mode 100644 index 000000000..8fefb1657 --- /dev/null +++ b/tests/test_optimizer_js_project_root_bug.py @@ -0,0 +1,148 @@ +""" +Test for the bug where test_cfg.js_project_root is set once and reused. + +The bug: When optimizing multiple functions from different directories in a monorepo, +the js_project_root from the FIRST function is cached in test_cfg and used for ALL +subsequent functions, causing incorrect vitest working directories. +""" + +import tempfile +from pathlib import Path +from unittest.mock import MagicMock, patch + +import pytest + +from codeflash.languages.javascript.support import JavaScriptSupport +from codeflash.verification.verification_utils import TestConfig + + +@patch("codeflash.languages.javascript.optimizer.verify_js_requirements") +def test_js_project_root_not_recalculated_demonstrates_bug(mock_verify): + """ + This test demonstrates the bug where js_project_root is set once + and never updated when optimizing functions from different directories. + + Expected behavior: Each function should get its own js_project_root + Actual behavior: All functions share the first function's js_project_root + """ + # Mock verify_js_requirements to always pass + mock_verify.return_value = [] + + with tempfile.TemporaryDirectory() as tmpdir: + root = Path(tmpdir) + + # Create main project + main_project = root / "project" + main_project.mkdir() + (main_project / "package.json").write_text('{"name": "main"}') + (main_project / "src").mkdir() + (main_project / "test").mkdir() + (main_project / "node_modules").mkdir() # Add node_modules to pass requirements check + + # Create extension with its own package.json + extension_dir = main_project / "extensions" / "discord" + extension_dir.mkdir(parents=True) + (extension_dir / "package.json").write_text('{"name": "discord-extension"}') + (extension_dir / "src").mkdir() + (extension_dir / "node_modules").mkdir() # Add node_modules to pass requirements check + + # Create test config (shared across all functions, simulating optimizer behavior) + test_cfg = TestConfig( + tests_root=main_project / "test", + project_root_path=main_project, + tests_project_rootdir=main_project / "test", + ) + test_cfg.set_language("javascript") + + # Create JavaScript support instance + js_support = JavaScriptSupport() + + # Optimize function 1 (in extension directory) + extension_file = extension_dir / "src" / "accounts.ts" + extension_file.write_text("export function foo() {}") + + success = js_support.setup_test_config(test_cfg, extension_file, current_worktree=None) + assert success, "setup_test_config should succeed" + js_project_root_after_func1 = test_cfg.js_project_root + + # Should be extension directory + assert js_project_root_after_func1 == extension_dir, ( + f"Function 1: Expected {extension_dir}, got {js_project_root_after_func1}" + ) + + # Optimize function 2 (in main src directory) + main_file = main_project / "src" / "commands.ts" + main_file.write_text("export function bar() {}") + + # This is the bug: setup_test_config is NOT called again in the real code! + # The test_cfg object is reused, so js_project_root stays as extension_dir + + # In the real optimizer, test_cfg is reused without calling setup_test_config again + # So js_project_root remains the same from function 1 + js_project_root_for_func2 = test_cfg.js_project_root + + # BUG: This assertion should fail because js_project_root was not recalculated + # It's still pointing to extension_dir instead of main_project + assert js_project_root_for_func2 == extension_dir, ( + f"BUG DEMONSTRATED: Function 2 inherits function 1's js_project_root. " + f"Expected {main_project}, got {js_project_root_for_func2}" + ) + + # What SHOULD happen: + # js_support.setup_test_config(test_cfg, main_file, current_worktree=None) + # correct_root = test_cfg.js_project_root + # assert correct_root == main_project + + +@pytest.mark.xfail(reason="Demonstrates the bug - will fail once bug is fixed") +@patch("codeflash.languages.javascript.optimizer.verify_js_requirements") +def test_js_project_root_reused_across_functions_wrong_behavior(mock_verify): + """ + This test is marked xfail because it currently PASSES (demonstrating the bug). + Once the bug is fixed, this test will FAIL (which is correct), and we can remove xfail. + """ + # Mock verify_js_requirements to always pass + mock_verify.return_value = [] + + with tempfile.TemporaryDirectory() as tmpdir: + root = Path(tmpdir) + + main_project = root / "project" + main_project.mkdir() + (main_project / "package.json").write_text('{"name": "main"}') + (main_project / "src").mkdir() + (main_project / "test").mkdir() + (main_project / "node_modules").mkdir() + + extension_dir = main_project / "extensions" / "discord" + extension_dir.mkdir(parents=True) + (extension_dir / "package.json").write_text('{"name": "discord"}') + (extension_dir / "src").mkdir() + (extension_dir / "node_modules").mkdir() + + test_cfg = TestConfig( + tests_root=main_project / "test", + project_root_path=main_project, + tests_project_rootdir=main_project / "test", + ) + test_cfg.set_language("javascript") + + js_support = JavaScriptSupport() + + # Set up for extension file + extension_file = extension_dir / "src" / "accounts.ts" + extension_file.write_text("export function foo() {}") + js_support.setup_test_config(test_cfg, extension_file, current_worktree=None) + + # Now try to use test_cfg for a different file + main_file = main_project / "src" / "commands.ts" + main_file.write_text("export function bar() {}") + + # This assertion will PASS (showing the bug) because js_project_root is wrong + # Once fixed, this will FAIL because js_project_root will be recalculated + assert test_cfg.js_project_root == extension_dir, ( + "Bug exists: js_project_root is not recalculated per function" + ) + + # The correct behavior would be: + # assert test_cfg.js_project_root == main_project From e7596b5bef7ca6c44bda6cb1e16afd21989c9405 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Fri, 3 Apr 2026 14:36:12 +0000 Subject: [PATCH 2/2] style: fix naming, imports, and test conventions in js_project_root fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename `_get_js_project_root` → `get_js_project_root` (no leading underscores per convention) - Remove redundant `from pathlib import Path` import inside method (already imported at top) - Remove unnecessary docstring from new method - Rewrite tests to use `tmp_path` fixture instead of `tempfile.TemporaryDirectory()` - Add `.resolve()` calls and `encoding="utf-8"` per project conventions - Simplify second test file to focus on the actual caching behavior Co-authored-by: mohammed ahmed --- codeflash/languages/function_optimizer.py | 26 +-- tests/test_js_project_root_per_function.py | 145 ++++++--------- tests/test_optimizer_js_project_root_bug.py | 185 +++++--------------- 3 files changed, 111 insertions(+), 245 deletions(-) diff --git a/codeflash/languages/function_optimizer.py b/codeflash/languages/function_optimizer.py index b462f86c5..7a5322857 100644 --- a/codeflash/languages/function_optimizer.py +++ b/codeflash/languages/function_optimizer.py @@ -3085,29 +3085,15 @@ def run_optimized_candidate( ) ) - def _get_js_project_root(self) -> Path | None: - """Get the JavaScript project root for the current function being optimized. - - This method calculates the js_project_root for each function instead of - caching it in test_cfg. This is important in monorepos where different - functions may belong to different packages/extensions with their own - package.json files. - - Returns: - Path to the JavaScript project root, or None if not a JavaScript project - or if the project root cannot be determined. - """ + def get_js_project_root(self) -> Path | None: # Only calculate for JavaScript/TypeScript projects if self.function_to_optimize.language not in ("javascript", "typescript"): return self.test_cfg.js_project_root # Fall back to cached value for non-JS - # For JS/TS, calculate fresh for each function - from pathlib import Path - + # For JS/TS, calculate fresh for each function to support monorepos from codeflash.languages.javascript.test_runner import find_node_project_root - source_file = Path(self.function_to_optimize.file_path) - return find_node_project_root(source_file) + return find_node_project_root(Path(self.function_to_optimize.file_path)) def run_and_parse_tests( self, @@ -3129,7 +3115,7 @@ def run_and_parse_tests( if testing_type == TestingMode.BEHAVIOR: # Calculate js_project_root for the current function being optimized # instead of using cached value from test_cfg, which may be from a different function - js_project_root = self._get_js_project_root() + js_project_root = self.get_js_project_root() result_file_path, run_result, coverage_database_file, coverage_config_file = ( self.language_support.run_behavioral_tests( @@ -3143,7 +3129,7 @@ def run_and_parse_tests( ) ) elif testing_type == TestingMode.LINE_PROFILE: - js_project_root = self._get_js_project_root() + js_project_root = self.get_js_project_root() result_file_path, run_result = self.language_support.run_line_profile_tests( test_paths=test_files, test_env=test_env, @@ -3153,7 +3139,7 @@ def run_and_parse_tests( line_profile_output_file=line_profiler_output_file, ) elif testing_type == TestingMode.PERFORMANCE: - js_project_root = self._get_js_project_root() + js_project_root = self.get_js_project_root() result_file_path, run_result = self.language_support.run_benchmarking_tests( test_paths=test_files, test_env=test_env, diff --git a/tests/test_js_project_root_per_function.py b/tests/test_js_project_root_per_function.py index 1b52a291c..771b011a9 100644 --- a/tests/test_js_project_root_per_function.py +++ b/tests/test_js_project_root_per_function.py @@ -1,95 +1,66 @@ """Test that js_project_root is recalculated per function, not cached.""" -import tempfile from pathlib import Path -import pytest - from codeflash.languages.javascript.test_runner import find_node_project_root -def test_find_node_project_root_returns_different_roots_for_different_files(): +def test_find_node_project_root_returns_different_roots_for_different_files(tmp_path: Path) -> None: """Test that find_node_project_root returns the correct root for each file.""" - with tempfile.TemporaryDirectory() as tmpdir: - root = Path(tmpdir) - - # Create main project structure - main_project = root / "project" - main_project.mkdir() - (main_project / "package.json").write_text("{}") - (main_project / "src").mkdir() - main_file = main_project / "src" / "main.ts" - main_file.write_text("// main file") - - # Create extension subdirectory with its own package.json - extension_dir = main_project / "extensions" / "discord" - extension_dir.mkdir(parents=True) - (extension_dir / "package.json").write_text("{}") - (extension_dir / "src").mkdir() - extension_file = extension_dir / "src" / "accounts.ts" - extension_file.write_text("// extension file") - - # Test 1: Extension file should return extension directory - result1 = find_node_project_root(extension_file) - assert result1 == extension_dir, ( - f"Expected {extension_dir}, got {result1}" - ) - - # Test 2: Main file should return main project directory - result2 = find_node_project_root(main_file) - assert result2 == main_project, ( - f"Expected {main_project}, got {result2}" - ) - - # Test 3: Calling again with extension file should still return extension dir - result3 = find_node_project_root(extension_file) - assert result3 == extension_dir, ( - f"Expected {extension_dir}, got {result3}" - ) - - -def test_js_project_root_should_be_recalculated_per_function(): - """ - Test the actual bug: when optimizing multiple functions from different - directories, each should get its own js_project_root, not inherit from - the first function. - - This test simulates the scenario where: - 1. Function #1 is in extensions/discord/src/accounts.ts - 2. Function #2 is in src/plugins/commands.ts - 3. Both should get their correct respective project roots - """ - with tempfile.TemporaryDirectory() as tmpdir: - root = Path(tmpdir) - - # Create main project - main_project = root / "project" - main_project.mkdir() - (main_project / "package.json").write_text('{"name": "main"}') - (main_project / "src").mkdir() - (main_project / "test").mkdir() - - # Create extension with its own package.json - extension_dir = main_project / "extensions" / "discord" - extension_dir.mkdir(parents=True) - (extension_dir / "package.json").write_text('{"name": "discord-extension"}') - (extension_dir / "src").mkdir() - - # Files to optimize - extension_file = extension_dir / "src" / "accounts.ts" - extension_file.write_text("export function foo() {}") - - main_file = main_project / "src" / "commands.ts" - main_file.write_text("export function bar() {}") - - # Simulate what happens in Codeflash optimizer - # Function 1 (extension file) sets js_project_root - js_project_root_1 = find_node_project_root(extension_file) - assert js_project_root_1 == extension_dir - - # Function 2 (main file) should get its own root, not inherit from function 1 - js_project_root_2 = find_node_project_root(main_file) - assert js_project_root_2 == main_project, ( - f"Bug reproduced: main file got {js_project_root_2} instead of {main_project}. " - f"This happens when test_cfg.js_project_root is not recalculated per function." - ) + # Create main project structure + main_project = (tmp_path / "project").resolve() + main_project.mkdir() + (main_project / "package.json").write_text("{}", encoding="utf-8") + (main_project / "src").mkdir() + main_file = (main_project / "src" / "main.ts").resolve() + main_file.write_text("// main file", encoding="utf-8") + + # Create extension subdirectory with its own package.json + extension_dir = (main_project / "extensions" / "discord").resolve() + extension_dir.mkdir(parents=True) + (extension_dir / "package.json").write_text("{}", encoding="utf-8") + (extension_dir / "src").mkdir() + extension_file = (extension_dir / "src" / "accounts.ts").resolve() + extension_file.write_text("// extension file", encoding="utf-8") + + # Extension file should return extension directory + result1 = find_node_project_root(extension_file) + assert result1 == extension_dir, f"Expected {extension_dir}, got {result1}" + + # Main file should return main project directory + result2 = find_node_project_root(main_file) + assert result2 == main_project, f"Expected {main_project}, got {result2}" + + # Calling again with extension file should still return extension dir + result3 = find_node_project_root(extension_file) + assert result3 == extension_dir, f"Expected {extension_dir}, got {result3}" + + +def test_js_project_root_recalculated_per_function(tmp_path: Path) -> None: + """Each function in a monorepo should resolve to its own nearest package.json root.""" + # Create main project + main_project = (tmp_path / "project").resolve() + main_project.mkdir() + (main_project / "package.json").write_text('{"name": "main"}', encoding="utf-8") + (main_project / "src").mkdir() + + # Create extension with its own package.json + extension_dir = (main_project / "extensions" / "discord").resolve() + extension_dir.mkdir(parents=True) + (extension_dir / "package.json").write_text('{"name": "discord-extension"}', encoding="utf-8") + (extension_dir / "src").mkdir() + + extension_file = (extension_dir / "src" / "accounts.ts").resolve() + extension_file.write_text("export function foo() {}", encoding="utf-8") + + main_file = (main_project / "src" / "commands.ts").resolve() + main_file.write_text("export function bar() {}", encoding="utf-8") + + js_project_root_1 = find_node_project_root(extension_file) + assert js_project_root_1 == extension_dir + + js_project_root_2 = find_node_project_root(main_file) + assert js_project_root_2 == main_project, ( + f"Expected {main_project}, got {js_project_root_2}. " + f"Happens when js_project_root is not recalculated per function." + ) diff --git a/tests/test_optimizer_js_project_root_bug.py b/tests/test_optimizer_js_project_root_bug.py index 8fefb1657..65e0237cb 100644 --- a/tests/test_optimizer_js_project_root_bug.py +++ b/tests/test_optimizer_js_project_root_bug.py @@ -1,148 +1,57 @@ -""" -Test for the bug where test_cfg.js_project_root is set once and reused. +"""Test that test_cfg.js_project_root caching bug is demonstrated and bypassed by the fix.""" -The bug: When optimizing multiple functions from different directories in a monorepo, -the js_project_root from the FIRST function is cached in test_cfg and used for ALL -subsequent functions, causing incorrect vitest working directories. -""" - -import tempfile from pathlib import Path -from unittest.mock import MagicMock, patch - -import pytest +from unittest.mock import patch from codeflash.languages.javascript.support import JavaScriptSupport from codeflash.verification.verification_utils import TestConfig @patch("codeflash.languages.javascript.optimizer.verify_js_requirements") -def test_js_project_root_not_recalculated_demonstrates_bug(mock_verify): - """ - This test demonstrates the bug where js_project_root is set once - and never updated when optimizing functions from different directories. - - Expected behavior: Each function should get its own js_project_root - Actual behavior: All functions share the first function's js_project_root - """ - # Mock verify_js_requirements to always pass - mock_verify.return_value = [] - - with tempfile.TemporaryDirectory() as tmpdir: - root = Path(tmpdir) - - # Create main project - main_project = root / "project" - main_project.mkdir() - (main_project / "package.json").write_text('{"name": "main"}') - (main_project / "src").mkdir() - (main_project / "test").mkdir() - (main_project / "node_modules").mkdir() # Add node_modules to pass requirements check - - # Create extension with its own package.json - extension_dir = main_project / "extensions" / "discord" - extension_dir.mkdir(parents=True) - (extension_dir / "package.json").write_text('{"name": "discord-extension"}') - (extension_dir / "src").mkdir() - (extension_dir / "node_modules").mkdir() # Add node_modules to pass requirements check - - # Create test config (shared across all functions, simulating optimizer behavior) - test_cfg = TestConfig( - tests_root=main_project / "test", - project_root_path=main_project, - tests_project_rootdir=main_project / "test", - ) - test_cfg.set_language("javascript") - - # Create JavaScript support instance - js_support = JavaScriptSupport() - - # Optimize function 1 (in extension directory) - extension_file = extension_dir / "src" / "accounts.ts" - extension_file.write_text("export function foo() {}") - - success = js_support.setup_test_config(test_cfg, extension_file, current_worktree=None) - assert success, "setup_test_config should succeed" - js_project_root_after_func1 = test_cfg.js_project_root - - # Should be extension directory - assert js_project_root_after_func1 == extension_dir, ( - f"Function 1: Expected {extension_dir}, got {js_project_root_after_func1}" - ) - - # Optimize function 2 (in main src directory) - main_file = main_project / "src" / "commands.ts" - main_file.write_text("export function bar() {}") - - # This is the bug: setup_test_config is NOT called again in the real code! - # The test_cfg object is reused, so js_project_root stays as extension_dir +def test_js_project_root_cached_in_test_cfg(mock_verify: object, tmp_path: Path) -> None: + """Demonstrates that test_cfg.js_project_root is set once per setup_test_config call. - # In the real optimizer, test_cfg is reused without calling setup_test_config again - # So js_project_root remains the same from function 1 - js_project_root_for_func2 = test_cfg.js_project_root - - # BUG: This assertion should fail because js_project_root was not recalculated - # It's still pointing to extension_dir instead of main_project - assert js_project_root_for_func2 == extension_dir, ( - f"BUG DEMONSTRATED: Function 2 inherits function 1's js_project_root. " - f"Expected {main_project}, got {js_project_root_for_func2}" - ) - - # What SHOULD happen: - # js_support.setup_test_config(test_cfg, main_file, current_worktree=None) - # correct_root = test_cfg.js_project_root - # assert correct_root == main_project - - -@pytest.mark.xfail(reason="Demonstrates the bug - will fail once bug is fixed") -@patch("codeflash.languages.javascript.optimizer.verify_js_requirements") -def test_js_project_root_reused_across_functions_wrong_behavior(mock_verify): - """ - This test is marked xfail because it currently PASSES (demonstrating the bug). - Once the bug is fixed, this test will FAIL (which is correct), and we can remove xfail. + This test shows the root cause: test_cfg caches the project root from the first function. + The fix bypasses this cache in FunctionOptimizer.get_js_project_root() instead of + changing how test_cfg stores the value. """ - # Mock verify_js_requirements to always pass - mock_verify.return_value = [] - - with tempfile.TemporaryDirectory() as tmpdir: - root = Path(tmpdir) - - main_project = root / "project" - main_project.mkdir() - (main_project / "package.json").write_text('{"name": "main"}') - (main_project / "src").mkdir() - (main_project / "test").mkdir() - (main_project / "node_modules").mkdir() - - extension_dir = main_project / "extensions" / "discord" - extension_dir.mkdir(parents=True) - (extension_dir / "package.json").write_text('{"name": "discord"}') - (extension_dir / "src").mkdir() - (extension_dir / "node_modules").mkdir() - - test_cfg = TestConfig( - tests_root=main_project / "test", - project_root_path=main_project, - tests_project_rootdir=main_project / "test", - ) - test_cfg.set_language("javascript") - - js_support = JavaScriptSupport() - - # Set up for extension file - extension_file = extension_dir / "src" / "accounts.ts" - extension_file.write_text("export function foo() {}") - js_support.setup_test_config(test_cfg, extension_file, current_worktree=None) - - # Now try to use test_cfg for a different file - main_file = main_project / "src" / "commands.ts" - main_file.write_text("export function bar() {}") - - # This assertion will PASS (showing the bug) because js_project_root is wrong - # Once fixed, this will FAIL because js_project_root will be recalculated - assert test_cfg.js_project_root == extension_dir, ( - "Bug exists: js_project_root is not recalculated per function" - ) - - # The correct behavior would be: - # assert test_cfg.js_project_root == main_project + mock_verify.return_value = [] # type: ignore[attr-defined] + + # Create main project + main_project = (tmp_path / "project").resolve() + main_project.mkdir() + (main_project / "package.json").write_text('{"name": "main"}', encoding="utf-8") + (main_project / "src").mkdir() + (main_project / "test").mkdir() + (main_project / "node_modules").mkdir() + + # Create extension with its own package.json + extension_dir = (main_project / "extensions" / "discord").resolve() + extension_dir.mkdir(parents=True) + (extension_dir / "package.json").write_text('{"name": "discord-extension"}', encoding="utf-8") + (extension_dir / "src").mkdir() + (extension_dir / "node_modules").mkdir() + + test_cfg = TestConfig( + tests_root=main_project / "test", + project_root_path=main_project, + tests_project_rootdir=main_project / "test", + ) + test_cfg.set_language("javascript") + + js_support = JavaScriptSupport() + + extension_file = (extension_dir / "src" / "accounts.ts").resolve() + extension_file.write_text("export function foo() {}", encoding="utf-8") + + success = js_support.setup_test_config(test_cfg, extension_file, current_worktree=None) + assert success, "setup_test_config should succeed" + # After setup for extension file, js_project_root is the extension directory + assert test_cfg.js_project_root == extension_dir + + # test_cfg is NOT re-initialized for subsequent functions — js_project_root stays cached + main_file = (main_project / "src" / "commands.ts").resolve() + main_file.write_text("export function bar() {}", encoding="utf-8") + + # The cached value is still extension_dir, not main_project — this is the root cause + assert test_cfg.js_project_root == extension_dir