fix on loop count

This commit is contained in:
Sarthak Agarwal 2026-02-05 18:07:35 +05:30
parent 1f127d15ed
commit 17916ef9fb
6 changed files with 265 additions and 15 deletions

View file

@ -197,6 +197,21 @@ def parse_jest_test_xml(
key = match.groups()[:5]
end_matches_dict[key] = match
# Also collect timing markers from testcase-level system-out (Vitest puts output at testcase level)
for tc in suite:
tc_system_out = tc._elem.find("system-out") # noqa: SLF001
if tc_system_out is not None and tc_system_out.text:
tc_stdout = tc_system_out.text.strip()
logger.debug(f"Vitest testcase system-out found: {len(tc_stdout)} chars, first 200: {tc_stdout[:200]}")
end_marker_count = 0
for match in jest_end_pattern.finditer(tc_stdout):
key = match.groups()[:5]
end_matches_dict[key] = match
end_marker_count += 1
if end_marker_count > 0:
logger.debug(f"Found {end_marker_count} END timing markers in testcase system-out")
start_matches.extend(jest_start_pattern.finditer(tc_stdout))
for testcase in suite:
testcase_count += 1
test_class_path = testcase.classname # For Jest, this is the file path

View file

@ -2098,6 +2098,10 @@ class JavaScriptSupport:
candidate_index=candidate_index,
)
# JavaScript/TypeScript benchmarking uses high max_loops like Python (100,000)
# The actual loop count is limited by target_duration_seconds, not max_loops
JS_BENCHMARKING_MAX_LOOPS = 100_000
def run_benchmarking_tests(
self,
test_paths: Any,
@ -2131,6 +2135,9 @@ class JavaScriptSupport:
framework = test_framework or get_js_test_framework_or_default()
# Use JS-specific high max_loops - actual loop count is limited by target_duration
effective_max_loops = self.JS_BENCHMARKING_MAX_LOOPS
if framework == "vitest":
from codeflash.languages.javascript.vitest_runner import run_vitest_benchmarking_tests
@ -2141,7 +2148,7 @@ class JavaScriptSupport:
timeout=timeout,
project_root=project_root,
min_loops=min_loops,
max_loops=max_loops,
max_loops=effective_max_loops,
target_duration_ms=int(target_duration_seconds * 1000),
)
@ -2154,7 +2161,7 @@ class JavaScriptSupport:
timeout=timeout,
project_root=project_root,
min_loops=min_loops,
max_loops=max_loops,
max_loops=effective_max_loops,
target_duration_ms=int(target_duration_seconds * 1000),
)

View file

@ -117,6 +117,141 @@ def _ensure_runtime_files(project_root: Path) -> None:
logger.error(f"Could not install codeflash. Please install it manually: {' '.join(install_cmd)}")
def _find_monorepo_root(start_path: Path) -> Path | None:
"""Find the monorepo root by looking for workspace markers.
Args:
start_path: A path within the monorepo.
Returns:
The monorepo root directory, or None if not found.
"""
monorepo_markers = ["pnpm-workspace.yaml", "yarn.lock", "lerna.json", "package-lock.json"]
current = start_path if start_path.is_dir() else start_path.parent
while current != current.parent:
# Check for monorepo markers
if any((current / marker).exists() for marker in monorepo_markers):
# Verify it has node_modules or package.json (it's a real root)
if (current / "node_modules").exists() or (current / "package.json").exists():
return current
current = current.parent
return None
def _is_vitest_workspace(project_root: Path) -> bool:
"""Check if the project uses vitest workspace configuration.
Vitest workspaces have a special structure where the root config
points to package-level configs. We shouldn't override these.
Args:
project_root: The project root directory.
Returns:
True if the project appears to use vitest workspace.
"""
vitest_config = project_root / "vitest.config.ts"
if not vitest_config.exists():
vitest_config = project_root / "vitest.config.js"
if not vitest_config.exists():
return False
try:
content = vitest_config.read_text()
# Check for workspace indicators
return "workspace" in content.lower() or "defineWorkspace" in content
except Exception:
return False
def _ensure_codeflash_vitest_config(project_root: Path) -> Path | None:
"""Create or find a Codeflash-compatible Vitest config.
Vitest configs often have restrictive include patterns like 'test/**/*.test.ts'
which filter out our generated test files. This function creates a config
that overrides the include pattern to accept all test files.
Note: For workspace projects, we skip creating a custom config as it would
conflict with the workspace setup. In those cases, tests should be placed
in the correct package's test directory.
Args:
project_root: The project root directory.
Returns:
Path to the Codeflash Vitest config, or None if creation failed/not needed.
"""
# Check for workspace configuration - don't override these
monorepo_root = _find_monorepo_root(project_root)
if monorepo_root and _is_vitest_workspace(monorepo_root):
logger.debug("Detected vitest workspace configuration - skipping custom config")
return None
codeflash_config_path = project_root / "codeflash.vitest.config.js"
# If already exists, use it
if codeflash_config_path.exists():
logger.debug(f"Using existing Codeflash Vitest config: {codeflash_config_path}")
return codeflash_config_path
# Find the original vitest config to extend
original_config = None
for config_name in ["vitest.config.ts", "vitest.config.js", "vitest.config.mts", "vitest.config.mjs"]:
config_path = project_root / config_name
if config_path.exists():
original_config = config_name
break
# Also check for vite config with vitest settings
if not original_config:
for config_name in ["vite.config.ts", "vite.config.js", "vite.config.mts", "vite.config.mjs"]:
config_path = project_root / config_name
if config_path.exists():
original_config = config_name
break
# Create a config that extends the original and overrides include pattern
if original_config:
config_content = f"""// Auto-generated by Codeflash for test file pattern compatibility
import {{ mergeConfig }} from 'vitest/config';
import originalConfig from './{original_config}';
export default mergeConfig(originalConfig, {{
test: {{
// Override include pattern to match all test files including generated ones
include: ['**/*.test.ts', '**/*.test.js', '**/*.test.tsx', '**/*.test.jsx'],
}},
}});
"""
else:
# No original config found, create a minimal one
config_content = """// Auto-generated by Codeflash for test file pattern compatibility
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
// Include all test files including generated ones
include: ['**/*.test.ts', '**/*.test.js', '**/*.test.tsx', '**/*.test.jsx'],
// Exclude common non-test directories
exclude: ['**/node_modules/**', '**/dist/**'],
},
});
"""
try:
codeflash_config_path.write_text(config_content)
logger.debug(f"Created Codeflash Vitest config: {codeflash_config_path}")
return codeflash_config_path
except Exception as e:
logger.warning(f"Failed to create Codeflash Vitest config: {e}")
return None
def _build_vitest_behavioral_command(
test_files: list[Path],
timeout: int | None = None,
@ -144,10 +279,13 @@ def _build_vitest_behavioral_command(
"--no-file-parallelism", # Serial execution for deterministic timing
]
# Explicitly set the project root to ensure vitest uses the correct config
# This is critical for monorepos where vitest might auto-detect the wrong root
# For monorepos with restrictive vitest configs (e.g., include: test/**/*.test.ts),
# we need to create a custom config that allows all test patterns.
# This is done by creating a codeflash.vitest.config.js file.
if project_root:
cmd.append(f"--root={project_root}")
codeflash_vitest_config = _ensure_codeflash_vitest_config(project_root)
if codeflash_vitest_config:
cmd.append(f"--config={codeflash_vitest_config}")
if output_file:
# Use dot notation for junit reporter output file when multiple reporters are used
@ -190,9 +328,11 @@ def _build_vitest_benchmarking_command(
"--no-file-parallelism", # Serial execution for consistent benchmarking
]
# Explicitly set the project root to ensure vitest uses the correct config
# Use codeflash vitest config to override restrictive include patterns
if project_root:
cmd.append(f"--root={project_root}")
codeflash_vitest_config = _ensure_codeflash_vitest_config(project_root)
if codeflash_vitest_config:
cmd.append(f"--config={codeflash_vitest_config}")
if output_file:
# Use dot notation for junit reporter output file when multiple reporters are used
@ -527,9 +667,11 @@ def run_vitest_line_profile_tests(
"--no-file-parallelism", # Serial execution for consistent line profiling
]
# Explicitly set the project root to ensure vitest uses the correct config
# Use codeflash vitest config to override restrictive include patterns
if effective_cwd:
vitest_cmd.append(f"--root={effective_cwd}")
codeflash_vitest_config = _ensure_codeflash_vitest_config(effective_cwd)
if codeflash_vitest_config:
vitest_cmd.append(f"--config={codeflash_vitest_config}")
# Use dot notation for junit reporter output file when multiple reporters are used
vitest_cmd.append(f"--outputFile.junit={result_file_path}")

View file

@ -545,15 +545,24 @@ class FunctionOptimizer:
]:
"""Generate and instrument tests for the function."""
n_tests = get_effort_value(EffortKeys.N_GENERATED_TESTS, self.effort)
source_file = Path(self.function_to_optimize.file_path)
generated_test_paths = [
get_test_file_path(
self.test_cfg.tests_root, self.function_to_optimize.function_name, test_index, test_type="unit"
self.test_cfg.tests_root,
self.function_to_optimize.function_name,
test_index,
test_type="unit",
source_file_path=source_file,
)
for test_index in range(n_tests)
]
generated_perf_test_paths = [
get_test_file_path(
self.test_cfg.tests_root, self.function_to_optimize.function_name, test_index, test_type="perf"
self.test_cfg.tests_root,
self.function_to_optimize.function_name,
test_index,
test_type="perf",
source_file_path=source_file,
)
for test_index in range(n_tests)
]

View file

@ -9,17 +9,89 @@ from pydantic.dataclasses import dataclass
from codeflash.languages import current_language_support, is_javascript
def get_test_file_path(test_dir: Path, function_name: str, iteration: int = 0, test_type: str = "unit") -> Path:
def get_test_file_path(
test_dir: Path, function_name: str, iteration: int = 0, test_type: str = "unit", source_file_path: Path | None = None
) -> Path:
assert test_type in {"unit", "inspired", "replay", "perf"}
function_name = function_name.replace(".", "_")
# Use appropriate file extension based on language
extension = current_language_support().get_test_file_suffix() if is_javascript() else ".py"
# For JavaScript/TypeScript, place generated tests in a subdirectory that matches
# Vitest/Jest include patterns (e.g., test/**/*.test.ts)
if is_javascript():
# For monorepos, first try to find the package directory from the source file path
# e.g., packages/workflow/src/utils.ts -> packages/workflow/test/codeflash-generated/
package_test_dir = _find_js_package_test_dir(test_dir, source_file_path)
if package_test_dir:
test_dir = package_test_dir
path = test_dir / f"test_{function_name}__{test_type}_test_{iteration}{extension}"
if path.exists():
return get_test_file_path(test_dir, function_name, iteration + 1, test_type)
return get_test_file_path(test_dir, function_name, iteration + 1, test_type, source_file_path)
return path
def _find_js_package_test_dir(tests_root: Path, source_file_path: Path | None) -> Path | None:
"""Find the appropriate test directory for a JavaScript/TypeScript package.
For monorepos, this finds the package's test directory from the source file path.
For example: packages/workflow/src/utils.ts -> packages/workflow/test/codeflash-generated/
Args:
tests_root: The root tests directory (may be monorepo packages root).
source_file_path: Path to the source file being tested.
Returns:
The test directory path, or None if not found.
"""
if source_file_path is None:
# No source path provided, check if test_dir itself has a test subdirectory
for test_subdir_name in ["test", "tests", "__tests__", "src/__tests__"]:
test_subdir = tests_root / test_subdir_name
if test_subdir.is_dir():
codeflash_test_dir = test_subdir / "codeflash-generated"
codeflash_test_dir.mkdir(parents=True, exist_ok=True)
return codeflash_test_dir
return None
try:
# Resolve paths for reliable comparison
tests_root = tests_root.resolve()
source_path = Path(source_file_path).resolve()
# Walk up from the source file to find a directory with package.json or test/ folder
package_dir = None
for parent in source_path.parents:
# Stop if we've gone above or reached the tests_root level
# For monorepos, tests_root might be /packages/ and we want to search within packages
if parent == tests_root or parent == tests_root.parent:
break
# Check if this looks like a package root
has_package_json = (parent / "package.json").exists()
has_test_dir = any((parent / d).is_dir() for d in ["test", "tests", "__tests__"])
if has_package_json or has_test_dir:
package_dir = parent
break
if package_dir:
# Find the test directory in this package
for test_subdir_name in ["test", "tests", "__tests__", "src/__tests__"]:
test_subdir = package_dir / test_subdir_name
if test_subdir.is_dir():
codeflash_test_dir = test_subdir / "codeflash-generated"
codeflash_test_dir.mkdir(parents=True, exist_ok=True)
return codeflash_test_dir
return None
except Exception:
return None
def delete_multiple_if_name_main(test_ast: ast.Module) -> ast.Module:
if_indexes = []
for index, node in enumerate(test_ast.body):

View file

@ -77,8 +77,13 @@ module.exports = {
incrementBatch: capture.incrementBatch,
getCurrentBatch: capture.getCurrentBatch,
checkSharedTimeLimit: capture.checkSharedTimeLimit,
PERF_BATCH_SIZE: capture.PERF_BATCH_SIZE,
PERF_LOOP_COUNT: capture.PERF_LOOP_COUNT,
// Getter functions for dynamic env var reading (not constants)
getPerfBatchSize: capture.getPerfBatchSize,
getPerfLoopCount: capture.getPerfLoopCount,
getPerfMinLoops: capture.getPerfMinLoops,
getPerfTargetDurationMs: capture.getPerfTargetDurationMs,
getPerfStabilityCheck: capture.getPerfStabilityCheck,
getPerfCurrentBatch: capture.getPerfCurrentBatch,
// === Feature Detection ===
hasV8: serializer.hasV8,