22
33import contextlib
44import shlex
5+ import shutil
56import subprocess
67import sys
78from pathlib import Path
1314from codeflash .code_utils .config_consts import TOTAL_LOOPING_TIME_EFFECTIVE
1415from codeflash .code_utils .coverage_utils import prepare_coverage_files
1516from codeflash .code_utils .shell_utils import get_cross_platform_subprocess_run_args
17+ from codeflash .languages .javascript .runtime import get_all_runtime_files
1618from codeflash .models .models import TestFiles , TestType
1719
1820if TYPE_CHECKING :
2224BENCHMARKING_BLOCKLISTED_PLUGINS = ["codspeed" , "cov" , "benchmark" , "profiling" , "xdist" , "sugar" ]
2325
2426
27+ def _ensure_js_runtime_files (js_project_root : Path ) -> None :
28+ """Ensure JavaScript runtime files are present in the project root.
29+
30+ Copies codeflash-jest-helper.js and related files to the JS project root
31+ if they don't already exist or are outdated.
32+
33+ Args:
34+ js_project_root: The JavaScript project root directory.
35+ """
36+ for runtime_file in get_all_runtime_files ():
37+ dest_path = js_project_root / runtime_file .name
38+ # Always copy to ensure we have the latest version
39+ if not dest_path .exists () or dest_path .stat ().st_mtime < runtime_file .stat ().st_mtime :
40+ shutil .copy2 (runtime_file , dest_path )
41+ logger .debug (f"Copied { runtime_file .name } to { js_project_root } " )
42+
43+
2544def _find_js_project_root (file_path : Path ) -> Path | None :
2645 """Find the JavaScript/TypeScript project root by looking for package.json.
2746
@@ -82,6 +101,9 @@ def run_jest_behavioral_tests(
82101 effective_cwd = js_project_root if js_project_root else cwd
83102 logger .debug (f"Jest working directory: { effective_cwd } " )
84103
104+ # Ensure runtime files (codeflash-jest-helper.js, etc.) are present
105+ _ensure_js_runtime_files (effective_cwd )
106+
85107 # Coverage output directory
86108 coverage_dir = get_run_tmp_file (Path ("jest_coverage" ))
87109 coverage_json_path = coverage_dir / "coverage-final.json" if enable_coverage else None
@@ -372,11 +394,13 @@ def run_jest_benchmarking_tests(
372394 test_paths: TestFiles object containing test file information.
373395 test_env: Environment variables for the test run.
374396 cwd: Working directory for running tests.
375- timeout: Optional timeout in seconds.
397+ timeout: Optional timeout in seconds for the subprocess .
376398 js_project_root: JavaScript project root (directory containing package.json).
377399 min_loops: Minimum number of loops to run for each test case.
378400 max_loops: Maximum number of loops to run for each test case.
379- target_duration_ms: Target duration in milliseconds for looping.
401+ target_duration_ms: Target TOTAL duration in milliseconds for looping.
402+ This is divided among test cases since JavaScript uses capturePerfLooped
403+ which loops internally per test case, unlike Python's external looping.
380404 stability_check: Whether to enable stability-based early stopping.
381405
382406 Returns:
@@ -388,6 +412,13 @@ def run_jest_benchmarking_tests(
388412 # Get performance test files
389413 test_files = [str (file .benchmarking_file_path ) for file in test_paths .test_files if file .benchmarking_file_path ]
390414
415+ # Count approximate number of test cases to divide time budget
416+ # JavaScript's capturePerfLooped loops internally per test case, so we need to divide
417+ # the total time budget among test cases to avoid timeout
418+ num_test_cases = len (test_files ) * 10 # Estimate ~10 test cases per file (conservative)
419+ # Use at least 500ms per test case for fast functions, cap at 2 seconds
420+ per_test_duration_ms = max (500 , min (2000 , target_duration_ms // max (1 , num_test_cases )))
421+
391422 # Use provided js_project_root, or detect it as fallback
392423 if js_project_root is None and test_files :
393424 first_test_file = Path (test_files [0 ])
@@ -396,6 +427,9 @@ def run_jest_benchmarking_tests(
396427 effective_cwd = js_project_root if js_project_root else cwd
397428 logger .debug (f"Jest benchmarking working directory: { effective_cwd } " )
398429
430+ # Ensure runtime files (codeflash-jest-helper.js, etc.) are present
431+ _ensure_js_runtime_files (effective_cwd )
432+
399433 # Build Jest command for performance tests
400434 jest_cmd = ["npx" , "jest" , "--reporters=default" , "--reporters=jest-junit" , "--runInBand" , "--forceExit" ]
401435
@@ -425,12 +459,21 @@ def run_jest_benchmarking_tests(
425459 # Looping configuration for stable performance measurements
426460 jest_env ["CODEFLASH_MIN_LOOPS" ] = str (min_loops )
427461 jest_env ["CODEFLASH_MAX_LOOPS" ] = str (max_loops )
428- jest_env ["CODEFLASH_TARGET_DURATION_MS" ] = str (target_duration_ms )
462+ # Use per-test duration instead of total duration
463+ jest_env ["CODEFLASH_TARGET_DURATION_MS" ] = str (per_test_duration_ms )
429464 jest_env ["CODEFLASH_STABILITY_CHECK" ] = "true" if stability_check else "false"
430465 # Seed random number generator for reproducible test runs across original and optimized code
431466 jest_env ["CODEFLASH_RANDOM_SEED" ] = "42"
432467
468+ # Calculate subprocess timeout based on expected benchmarking time
469+ # For slow O(n²) functions, a single call might take seconds, so add generous buffer
470+ # Allow for Jest startup overhead (10s) + per-test-case benchmarking + safety margin
471+ expected_benchmarking_time_s = (num_test_cases * per_test_duration_ms ) / 1000 + 10
472+ # Use at least 60 seconds to handle slow functions, or calculated time with 2x margin
473+ subprocess_timeout = max (60 , int (expected_benchmarking_time_s * 2 ))
474+
433475 logger .debug (f"Running Jest benchmarking tests: { ' ' .join (jest_cmd )} " )
476+ logger .debug (f"Jest benchmarking config: { num_test_cases } estimated test cases, { per_test_duration_ms } ms per test, min_loops={ min_loops } , { subprocess_timeout } s subprocess timeout" )
434477
435478 # Calculate subprocess timeout: for Jest benchmarking, we need enough time for all tests to complete
436479 # Each test can run up to target_duration_ms (default 10s) for stable measurements
0 commit comments