style: fix naming, imports, and test conventions in js_project_root fix

- 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 <undefined@users.noreply.github.com>
This commit is contained in:
claude[bot] 2026-04-03 14:36:12 +00:00
parent 46c49910cd
commit e7596b5bef
3 changed files with 93 additions and 227 deletions

View file

@ -3085,29 +3085,15 @@ class FunctionOptimizer:
)
)
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 @@ class FunctionOptimizer:
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 @@ class FunctionOptimizer:
)
)
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 @@ class FunctionOptimizer:
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,

View file

@ -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 = (tmp_path / "project").resolve()
main_project.mkdir()
(main_project / "package.json").write_text("{}")
(main_project / "package.json").write_text("{}", encoding="utf-8")
(main_project / "src").mkdir()
main_file = main_project / "src" / "main.ts"
main_file.write_text("// main file")
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"
extension_dir = (main_project / "extensions" / "discord").resolve()
extension_dir.mkdir(parents=True)
(extension_dir / "package.json").write_text("{}")
(extension_dir / "package.json").write_text("{}", encoding="utf-8")
(extension_dir / "src").mkdir()
extension_file = extension_dir / "src" / "accounts.ts"
extension_file.write_text("// extension file")
extension_file = (extension_dir / "src" / "accounts.ts").resolve()
extension_file.write_text("// extension file", encoding="utf-8")
# Test 1: Extension file should return extension directory
# Extension file should return extension directory
result1 = find_node_project_root(extension_file)
assert result1 == extension_dir, (
f"Expected {extension_dir}, got {result1}"
)
assert result1 == extension_dir, f"Expected {extension_dir}, got {result1}"
# Test 2: Main file should return main project directory
# Main file should return main project directory
result2 = find_node_project_root(main_file)
assert result2 == main_project, (
f"Expected {main_project}, got {result2}"
)
assert result2 == main_project, f"Expected {main_project}, got {result2}"
# Test 3: Calling again with extension file should still return extension dir
# 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}"
)
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)
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 = root / "project"
main_project = (tmp_path / "project").resolve()
main_project.mkdir()
(main_project / "package.json").write_text('{"name": "main"}')
(main_project / "package.json").write_text('{"name": "main"}', encoding="utf-8")
(main_project / "src").mkdir()
(main_project / "test").mkdir()
# Create extension with its own package.json
extension_dir = main_project / "extensions" / "discord"
extension_dir = (main_project / "extensions" / "discord").resolve()
extension_dir.mkdir(parents=True)
(extension_dir / "package.json").write_text('{"name": "discord-extension"}')
(extension_dir / "package.json").write_text('{"name": "discord-extension"}', encoding="utf-8")
(extension_dir / "src").mkdir()
# Files to optimize
extension_file = extension_dir / "src" / "accounts.ts"
extension_file.write_text("export function foo() {}")
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"
main_file.write_text("export function bar() {}")
main_file = (main_project / "src" / "commands.ts").resolve()
main_file.write_text("export function bar() {}", encoding="utf-8")
# 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."
f"Expected {main_project}, got {js_project_root_2}. "
f"Happens when js_project_root is not recalculated per function."
)

View file

@ -1,122 +1,34 @@
"""
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.
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.
Expected behavior: Each function should get its own js_project_root
Actual behavior: All functions share the first function's js_project_root
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)
mock_verify.return_value = [] # type: ignore[attr-defined]
# Create main project
main_project = root / "project"
main_project = (tmp_path / "project").resolve()
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 / "package.json").write_text('{"name": "main"}', encoding="utf-8")
(main_project / "src").mkdir()
(main_project / "test").mkdir()
(main_project / "node_modules").mkdir()
extension_dir = main_project / "extensions" / "discord"
# 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_dir / "package.json").write_text('{"name": "discord-extension"}', encoding="utf-8")
(extension_dir / "src").mkdir()
(extension_dir / "node_modules").mkdir()
@ -129,20 +41,17 @@ def test_js_project_root_reused_across_functions_wrong_behavior(mock_verify):
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)
extension_file = (extension_dir / "src" / "accounts.ts").resolve()
extension_file.write_text("export function foo() {}", encoding="utf-8")
# Now try to use test_cfg for a different file
main_file = main_project / "src" / "commands.ts"
main_file.write_text("export function bar() {}")
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
# 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"
)
# 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 correct behavior would be:
# assert test_cfg.js_project_root == main_project
# The cached value is still extension_dir, not main_project — this is the root cause
assert test_cfg.js_project_root == extension_dir