mirror of
https://github.com/codeflash-ai/codeflash.git
synced 2026-05-04 18:25:17 +00:00
fix on loop count
This commit is contained in:
parent
1f127d15ed
commit
17916ef9fb
6 changed files with 265 additions and 15 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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}")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue