@@ -419,27 +419,29 @@ def run_jest_benchmarking_tests(
419419 target_duration_ms : int = 10_000 , # 10 seconds for benchmarking tests
420420 stability_check : bool = True ,
421421) -> tuple [Path , subprocess .CompletedProcess ]:
422- """Run Jest benchmarking tests with session-level looping for stable measurements.
422+ """Run Jest benchmarking tests with in-process session-level looping.
423+
424+ Uses a custom Jest runner (codeflash/loop-runner) to loop all tests
425+ within a single Jest process, eliminating process startup overhead.
423426
424427 This matches Python's pytest_plugin behavior:
425- - Run Jest multiple times (like pytest loops the session)
426- - Each Jest run executes each test once
427- - Timing data is collected per Jest run
428- - Stability is checked across all Jest runs (session-level)
428+ - All tests are run multiple times within a single Jest process
429+ - Timing data is collected per iteration
430+ - Stability is checked within the runner
429431
430432 Args:
431433 test_paths: TestFiles object containing test file information.
432434 test_env: Environment variables for the test run.
433435 cwd: Working directory for running tests.
434- timeout: Optional timeout in seconds per Jest invocation .
436+ timeout: Optional timeout in seconds for the entire benchmark run .
435437 project_root: JavaScript project root (directory containing package.json).
436- min_loops: Minimum number of Jest runs (session loops) .
437- max_loops: Maximum number of Jest runs (session loops) .
438+ min_loops: Minimum number of loop iterations .
439+ max_loops: Maximum number of loop iterations .
438440 target_duration_ms: Target TOTAL duration in milliseconds for all loops.
439441 stability_check: Whether to enable stability-based early stopping.
440442
441443 Returns:
442- Tuple of (result_file_path, subprocess_result with combined stdout from all runs ).
444+ Tuple of (result_file_path, subprocess_result with stdout from all iterations ).
443445
444446 """
445447 result_file_path = get_run_tmp_file (Path ("jest_perf_results.xml" ))
@@ -458,8 +460,16 @@ def run_jest_benchmarking_tests(
458460 # Ensure the codeflash npm package is installed
459461 _ensure_runtime_files (effective_cwd )
460462
461- # Build Jest command for performance tests
462- jest_cmd = ["npx" , "jest" , "--reporters=default" , "--reporters=jest-junit" , "--runInBand" , "--forceExit" ]
463+ # Build Jest command for performance tests with custom loop runner
464+ jest_cmd = [
465+ "npx" ,
466+ "jest" ,
467+ "--reporters=default" ,
468+ "--reporters=jest-junit" ,
469+ "--runInBand" , # Ensure serial execution even though runner enforces it
470+ "--forceExit" ,
471+ "--runner=codeflash/loop-runner" , # Use custom loop runner for in-process looping
472+ ]
463473
464474 if test_files :
465475 jest_cmd .append ("--runTestsByPath" )
@@ -483,118 +493,57 @@ def run_jest_benchmarking_tests(
483493 jest_env ["CODEFLASH_MODE" ] = "performance"
484494 jest_env ["CODEFLASH_RANDOM_SEED" ] = "42"
485495
496+ # Loop runner configuration (passed via environment variables)
497+ jest_env ["CODEFLASH_LOOP_COUNT" ] = str (max_loops )
498+ jest_env ["CODEFLASH_MIN_LOOPS" ] = str (min_loops )
499+ jest_env ["CODEFLASH_TARGET_DURATION_MS" ] = str (target_duration_ms )
500+ jest_env ["CODEFLASH_STABILITY_CHECK" ] = "true" if stability_check else "false"
501+ jest_env ["CODEFLASH_LOOP_INDEX" ] = "1" # Initial value, updated by runner
502+
486503 # Configure ESM support if project uses ES Modules
487504 _configure_esm_environment (jest_env , effective_cwd )
488505
489- # Per-Jest-invocation timeout (not total timeout)
490- subprocess_timeout = max (60 , timeout or 60 )
506+ # Total timeout for the entire benchmark run (longer than single-loop timeout)
507+ # Account for startup overhead + target duration + buffer
508+ total_timeout = max (120 , (target_duration_ms // 1000 ) + 60 , timeout or 120 )
491509
492- logger .debug (f"Running Jest benchmarking tests with session-level looping : { ' ' .join (jest_cmd )} " )
510+ logger .debug (f"Running Jest benchmarking tests with in-process loop runner : { ' ' .join (jest_cmd )} " )
493511 logger .debug (
494512 f"Jest benchmarking config: min_loops={ min_loops } , max_loops={ max_loops } , "
495513 f"target_duration={ target_duration_ms } ms, stability_check={ stability_check } "
496514 )
497515
498- # Session-level looping (like Python's pytest_plugin)
499- runtime_data_by_test : dict [str , list [int ]] = {}
500- aggregate_runtimes : list [int ] = []
501- all_stdout : list [str ] = []
502- final_result : subprocess .CompletedProcess | None = None
503516 total_start_time = time .time ()
504- loop_count = 0
505-
506- while loop_count < max_loops :
507- loop_count += 1
508-
509- # Set loop index for this Jest run
510- jest_env ["CODEFLASH_LOOP_INDEX" ] = str (loop_count )
511517
512- logger .debug (f"Jest benchmarking loop { loop_count } /{ max_loops } " )
513-
514- try :
515- run_args = get_cross_platform_subprocess_run_args (
516- cwd = effective_cwd , env = jest_env , timeout = subprocess_timeout , check = False , text = True , capture_output = True
517- )
518- result = subprocess .run (jest_cmd , ** run_args ) # noqa: PLW1510
519-
520- # Combine stderr into stdout for timing markers
521- stdout = result .stdout or ""
522- if result .stderr :
523- stdout = stdout + "\n " + result .stderr if stdout else result .stderr
524-
525- all_stdout .append (stdout )
526- final_result = result
527-
528- # Parse timing data from this Jest run
529- timing_data = _parse_timing_from_jest_output (stdout )
530- for test_id , duration_ns in timing_data .items ():
531- runtime_data_by_test .setdefault (test_id , []).append (duration_ns )
532-
533- # Check for test failures - stop looping if tests fail
534- if result .returncode != 0 :
535- logger .debug (f"Jest run { loop_count } failed with returncode { result .returncode } , stopping loops" )
536- break
537-
538- except subprocess .TimeoutExpired :
539- logger .warning (f"Jest run { loop_count } timed out after { subprocess_timeout } s" )
540- break
541- except FileNotFoundError :
542- logger .error ("Jest not found for benchmarking" )
543- final_result = subprocess .CompletedProcess (args = jest_cmd , returncode = - 1 , stdout = "" , stderr = "Jest not found" )
544- break
545-
546- # Check stopping conditions
547- elapsed_seconds = time .time () - total_start_time
548- elapsed_ms = elapsed_seconds * 1000
549-
550- # Stop if we've reached min loops AND exceeded time limit
551- if loop_count >= min_loops and elapsed_ms >= target_duration_ms :
552- logger .debug (
553- f"Stopping: reached min_loops={ min_loops } and elapsed={ elapsed_ms :.0f} ms >= target={ target_duration_ms } ms"
554- )
555- break
518+ try :
519+ run_args = get_cross_platform_subprocess_run_args (
520+ cwd = effective_cwd , env = jest_env , timeout = total_timeout , check = False , text = True , capture_output = True
521+ )
522+ result = subprocess .run (jest_cmd , ** run_args ) # noqa: PLW1510
556523
557- # Stability check (matches Python's pytest_plugin logic exactly)
558- if stability_check and runtime_data_by_test :
559- # Calculate best runtime (sum of min per test case) - matches Python
560- best_runtime = sum (min (data ) for data in runtime_data_by_test .values () if data )
561- if best_runtime > 0 :
562- aggregate_runtimes .append (best_runtime )
563-
564- # Estimate window size based on loop rate (matches Python's pytest_plugin)
565- # window_size = int(STABILITY_WINDOW_SIZE * estimated_total_loops + 0.5)
566- elapsed_ns = elapsed_seconds * 1e9
567- if elapsed_ns > 0 :
568- rate = loop_count / elapsed_ns
569- total_time_ns = target_duration_ms * 1e6 # Convert ms to ns
570- estimated_total_loops = int (rate * total_time_ns )
571- window_size = int (STABILITY_WINDOW_SIZE * estimated_total_loops + 0.5 )
572-
573- if _should_stop_stability (aggregate_runtimes , window_size , min_loops ):
574- logger .debug (f"Stopping: stability reached at loop { loop_count } " )
575- break
576-
577- # Combine all stdout from all Jest runs
578- combined_stdout = "\n " .join (all_stdout )
524+ # Combine stderr into stdout for timing markers
525+ stdout = result .stdout or ""
526+ if result .stderr :
527+ stdout = stdout + "\n " + result .stderr if stdout else result .stderr
579528
580- wall_clock_seconds = time .time () - total_start_time
581- logger .debug (
582- f"Jest benchmarking completed: { loop_count } loops in { wall_clock_seconds :.2f} s, "
583- f"{ len (runtime_data_by_test )} test cases tracked"
584- )
529+ # Create result with combined stdout
530+ result = subprocess .CompletedProcess (
531+ args = result .args , returncode = result .returncode , stdout = stdout , stderr = ""
532+ )
585533
586- # Return combined result
587- if final_result is None :
588- final_result = subprocess .CompletedProcess (
589- args = jest_cmd , returncode = - 1 , stdout = "" , stderr = "No Jest runs completed "
534+ except subprocess . TimeoutExpired :
535+ logger . warning ( f"Jest benchmarking timed out after { total_timeout } s" )
536+ result = subprocess .CompletedProcess (
537+ args = jest_cmd , returncode = - 1 , stdout = "" , stderr = "Benchmarking timed out "
590538 )
539+ except FileNotFoundError :
540+ logger .error ("Jest not found for benchmarking" )
541+ result = subprocess .CompletedProcess (args = jest_cmd , returncode = - 1 , stdout = "" , stderr = "Jest not found" )
591542
592- # Create result with combined stdout from all runs
593- combined_result = subprocess .CompletedProcess (
594- args = final_result .args , returncode = final_result .returncode , stdout = combined_stdout , stderr = ""
595- )
543+ wall_clock_seconds = time .time () - total_start_time
544+ logger .debug (f"Jest benchmarking completed in { wall_clock_seconds :.2f} s" )
596545
597- return result_file_path , combined_result
546+ return result_file_path , result
598547
599548
600549def run_jest_line_profile_tests (
0 commit comments