mirror of
https://github.com/codeflash-ai/codeflash.git
synced 2026-05-04 18:25:17 +00:00
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:
parent
46c49910cd
commit
e7596b5bef
3 changed files with 93 additions and 227 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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."
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue