@@ -477,78 +477,105 @@ run_component_tests() {
477477
478478 local output_file=" $results_dir /random_test_output_${component} _$$ .log"
479479 local events_file=" $results_dir /random_test_events_${component} _$$ .log"
480- local random_seed=$( generate_phpunit_random_seed)
481480 local exit_code=0
482-
483- # Security: Use array to avoid eval and prevent command injection
484- local -a phpunit_args=(
485- " vendor/bin/phpunit "
486- " $test_dir "
487- " --colors=never "
488- " --no-coverage "
489- " --do-not-cache-result "
490- " --order-by=random "
491- " --random-order-seed= ${random_seed} "
492- " --log-events-text "
493- " $events_file "
494- )
495-
496- if [[ $timeout_seconds -gt 0 ]] && command -v timeout > /dev/null 2>&1 ; then
497- (cd " $project_root " && timeout --kill-after=2s " ${timeout_seconds} s " " ${phpunit_args[@]} " ) > " $output_file " 2>&1
498- exit_code= $?
499- elif [[ $timeout_seconds -gt 0 ]] && command -v gtimeout > /dev/null 2>&1 ; then
500- (cd " $project_root " && gtimeout --kill-after=2s " ${timeout_seconds} s " " ${phpunit_args[@]} " ) > " $output_file " 2>&1
501- exit_code= $?
502- else
503- local timeout_marker= " $output_file .timeout "
504- (cd " $project_root " && " ${phpunit_args[@]} " ) > " $output_file " 2>&1 &
505- local test_pid= $!
506-
507- if [[ $timeout_seconds -gt 0 ]]; then
508- # Watchdog: monitors test process and kills it after timeout
509- # Uses 1-second sleep intervals to respond quickly when test finishes early
510- (
511- local elapsed=0
512- while [[ $elapsed -lt $timeout_seconds ]] ; do
513- sleep 1
514- elapsed= $(( elapsed + 1 ))
515- kill -0 " $test_pid " 2> /dev/null || exit 0
516- done
517-
518- if kill -0 " $test_pid " 2> /dev/null ; then
519- touch " $timeout_marker "
520- local pids_to_kill= $( pgrep -P " $test_pid " 2> /dev/null )
521-
522- kill -TERM " $test_pid " 2> /dev/null || true
523- if [[ -n " $pids_to_kill " ]]; then
524- echo " $pids_to_kill " | xargs kill -TERM 2> /dev/null || true
525- fi
526-
527- sleep 2
481+ local attempt=1
482+ local -r max_attempts=2
483+ local random_seed
484+ local -a phpunit_args
485+
486+ # Retry loop: the Composer classmap autoloader occasionally fails to load
487+ # CodeIgniter\CodeIgniter under parallel CI load — a transient infra race,
488+ # not a real test failure. Retry once on that signature with a fresh random
489+ # seed; a second miss is reported as genuine failure.
490+ while true ; do
491+ random_seed= $( generate_phpunit_random_seed )
492+
493+ # Security: Use array to avoid eval and prevent command injection
494+ phpunit_args=(
495+ " vendor/bin/phpunit "
496+ " $test_dir "
497+ " --colors=never "
498+ " --no-coverage "
499+ " --do-not-cache-result "
500+ " --order-by=random "
501+ " --random-order-seed= ${random_seed} "
502+ " --log-events-text "
503+ " $events_file "
504+ )
505+
506+ if [[ $timeout_seconds -gt 0 ]] && command -v timeout > /dev/null 2>&1 ; then
507+ (cd " $project_root " && timeout --kill-after=2s " ${timeout_seconds} s " " ${phpunit_args[@]} " ) > " $output_file " 2>&1
508+ exit_code= $?
509+ elif [[ $timeout_seconds -gt 0 ]] && command -v gtimeout > /dev/null 2>&1 ; then
510+ (cd " $project_root " && gtimeout --kill-after=2s " ${timeout_seconds} s " " ${phpunit_args[@]} " ) > " $output_file " 2>&1
511+ exit_code= $?
512+ else
513+ local timeout_marker= " $output_file .timeout "
514+ (cd " $project_root " && " ${phpunit_args[@]} " ) > " $output_file " 2>&1 &
515+ local test_pid= $!
516+
517+ if [[ $timeout_seconds -gt 0 ]] ; then
518+ # Watchdog: monitors test process and kills it after timeout
519+ # Uses 1-second sleep intervals to respond quickly when test finishes early
520+ (
521+ local elapsed=0
522+ while [[ $elapsed -lt $timeout_seconds ]]; do
523+ sleep 1
524+ elapsed= $(( elapsed + 1 ))
525+ kill -0 " $test_pid " 2> /dev/null || exit 0
526+ done
528527
529528 if kill -0 " $test_pid " 2> /dev/null; then
530- kill -KILL " $test_pid " 2> /dev/null || true
529+ touch " $timeout_marker "
530+ local pids_to_kill=$( pgrep -P " $test_pid " 2> /dev/null)
531+
532+ kill -TERM " $test_pid " 2> /dev/null || true
531533 if [[ -n " $pids_to_kill " ]]; then
532- echo " $pids_to_kill " | xargs kill -KILL 2> /dev/null || true
534+ echo " $pids_to_kill " | xargs kill -TERM 2> /dev/null || true
535+ fi
536+
537+ sleep 2
538+
539+ if kill -0 " $test_pid " 2> /dev/null; then
540+ kill -KILL " $test_pid " 2> /dev/null || true
541+ if [[ -n " $pids_to_kill " ]]; then
542+ echo " $pids_to_kill " | xargs kill -KILL 2> /dev/null || true
543+ fi
544+ # Security: Quote and escape test_dir for safe pattern matching
545+ pkill -KILL -f " phpunit.*${test_dir// \/ / \\ / } " 2> /dev/null || true
533546 fi
534- # Security: Quote and escape test_dir for safe pattern matching
535- pkill -KILL -f " phpunit.*${test_dir// \/ / \\ / } " 2> /dev/null || true
536547 fi
537- fi
538- ) &
539- disown $! 2> /dev/null || true
548+ ) &
549+ disown $! 2> /dev/null || true
550+ fi
551+
552+ wait " $test_pid " 2> /dev/null
553+ exit_code=$?
554+
555+ if [[ -f " $timeout_marker " ]]; then
556+ exit_code=124
557+ rm -f " $timeout_marker "
558+ elif [[ $exit_code -eq 143 || $exit_code -eq 137 ]]; then
559+ exit_code=124
560+ fi
540561 fi
541562
542- wait " $test_pid " 2> /dev/null
543- exit_code=$?
563+ # Success, exhausted attempts, or a non-infra failure: stop retrying.
564+ if [[ $exit_code -eq 0 ]] || [[ $attempt -ge $max_attempts ]]; then
565+ break
566+ fi
544567
545- if [[ -f " $timeout_marker " ]] ; then
546- exit_code=124
547- rm -f " $timeout_marker "
548- elif [[ $exit_code -eq 143 || $exit_code -eq 137 ]] ; then
549- exit_code=124
568+ # Only retry on the known transient autoload race signatures.
569+ # Matching on error messages (not line numbers) so the pattern survives
570+ # unrelated edits to MockCodeIgniter/CIUnitTestCase.
571+ if ! grep -qE ' Failed to open stream: No such file or directory|Class "CodeIgniter.CodeIgniter" not found ' " $output_file " 2> /dev/null ; then
572+ break
550573 fi
551- fi
574+
575+ print_debug " Transient autoload failure detected in $component ; retrying (attempt $(( attempt + 1 )) /${max_attempts} )"
576+ (( attempt++ ))
577+ rm -f " $events_file "
578+ done
552579
553580 local elapsed=$(( ($(date +% s% N) - $start_time ) / 1000000 ))
554581 local result_file=" $results_dir /random_test_result_${elapsed} _${component} .txt"
0 commit comments