Skip to content

Commit be53558

Browse files
Add per-test timeout support (-t) to test.pl
Fork the test command into a new process group and use alarm() to enforce an optional per-test timeout. When the timeout fires, kill the entire process group with SIGKILL and report the test as failed with a TIMEOUT marker. The timeout can be set via -t <secs> on the command line or via the TESTPL_TIMEOUT environment variable (command line takes precedence). Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
1 parent 841b444 commit be53558

1 file changed

Lines changed: 61 additions & 13 deletions

File tree

regression/test.pl

Lines changed: 61 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ sub with_color {
3131
}
3232
}
3333

34-
sub run($$$$$) {
35-
my ($name, $input, $cmd, $options, $output) = @_;
34+
sub run($$$$$$) {
35+
my ($name, $input, $cmd, $options, $output, $timeout) = @_;
3636
my $cmdline;
3737
if(length($input)) {
3838
$cmdline = "$cmd $options '$input' >'$output' 2>&1";
@@ -41,13 +41,58 @@ ($$$$$)
4141
}
4242

4343
print LOG "Running $cmdline\n";
44-
# see https://github.com/git-for-windows/msys2-runtime/pull/11/files
45-
system("bash", "-c", "cd '$name' ; MSYS_NO_PATHCONV=1 $cmdline");
46-
my $exit_value = $? >> 8;
47-
my $signal_num = $? & 127;
48-
my $dumped_core = $? & 128;
44+
my $exit_value;
45+
my $signal_num;
46+
my $dumped_core;
47+
my $timed_out = 0;
48+
49+
# Fork so we can enforce a timeout via kill on the child process group.
50+
my $pid = fork();
51+
die "fork failed: $!" unless defined $pid;
52+
53+
if($pid == 0) {
54+
# Child: start a new process group so we can kill the whole group on
55+
# timeout, then exec the test command.
56+
setpgrp(0, 0);
57+
# see https://github.com/git-for-windows/msys2-runtime/pull/11/files
58+
exec("bash", "-c", "cd '$name' ; MSYS_NO_PATHCONV=1 $cmdline");
59+
die "exec failed: $!";
60+
}
61+
62+
# Parent: wait for the child, with an optional timeout.
63+
if($timeout) {
64+
eval {
65+
local $SIG{ALRM} = sub { die "alarm\n" };
66+
alarm($timeout);
67+
waitpid($pid, 0);
68+
alarm(0);
69+
};
70+
if($@ && $@ eq "alarm\n") {
71+
$timed_out = 1;
72+
# Kill the entire process group of the child.
73+
kill(9, -$pid);
74+
waitpid($pid, 0);
75+
} elsif($@) {
76+
die $@;
77+
}
78+
} else {
79+
waitpid($pid, 0);
80+
}
81+
82+
$exit_value = $? >> 8;
83+
$signal_num = $? & 127;
84+
$dumped_core = $? & 128;
4985
my $failed = 0;
5086

87+
if($timed_out) {
88+
print LOG " Timed out after $timeout seconds\n";
89+
print " [TIMEOUT]";
90+
$failed = 1;
91+
# Write output so that pattern matching does not crash on missing file.
92+
system("bash", "-c", "cd '$name' ; echo '\nTIMEOUT=1\nEXIT=137\nSIGNAL=9\n' >> '$output'");
93+
return $failed;
94+
}
95+
5196
print LOG " Exit: $exit_value\n";
5297
print LOG " Signal: $signal_num\n";
5398
print LOG " Core: $dumped_core\n";
@@ -84,8 +129,8 @@ ($$)
84129
return @data;
85130
}
86131

87-
sub test($$$$$$$$$$$) {
88-
my ($name, $test, $t_level, $cmd, $ign, $dry_run, $defines, $include_tags, $exclude_tags, $output_suffix, $exit_signal_checks) = @_;
132+
sub test($$$$$$$$$$$$) {
133+
my ($name, $test, $t_level, $cmd, $ign, $dry_run, $defines, $include_tags, $exclude_tags, $output_suffix, $exit_signal_checks, $timeout) = @_;
89134
my ($level_and_tags, $input, $options, $grep_options, @results) = load("$test", $exit_signal_checks);
90135
my @keys = keys %{$defines};
91136
foreach my $key (@keys) {
@@ -179,7 +224,7 @@ ($$$$$$$$$$$)
179224
return 0;
180225
}
181226

182-
$failed = run($name, $input, $cmd, $options, $output);
227+
$failed = run($name, $input, $cmd, $options, $output, $timeout);
183228

184229
if(!$failed) {
185230
print LOG "Execution [OK]\n";
@@ -302,6 +347,8 @@ ($$$$)
302347
as runs with different suffixes will operate independently and keep
303348
independent logs.
304349
-f forward the test name to CMD
350+
-t <secs> timeout per test in seconds (kills test if exceeded)
351+
default is \$TESTPL_TIMEOUT if set; overridden by -t <secs>
305352
306353
--[no]color enable/disable color output; enabled by default unless
307354
TESTPL_COLOR_OUTPUT is set to 0, in which case it is
@@ -341,7 +388,7 @@ ($$$$)
341388
use Getopt::Long qw(:config pass_through bundling);
342389
$main::VERSION = 0.1;
343390
$Getopt::Std::STANDARD_HELP_VERSION = 1;
344-
our ($opt_c, $opt_e, $opt_f, $opt_i, $opt_j, $opt_n, $opt_p, $opt_h, $opt_C, $opt_T, $opt_F, $opt_K, $opt_s, $opt_S, %defines, @include_tags, @exclude_tags); # the variables for getopt
391+
our ($opt_c, $opt_e, $opt_f, $opt_i, $opt_j, $opt_n, $opt_p, $opt_h, $opt_C, $opt_T, $opt_F, $opt_K, $opt_s, $opt_S, $opt_t, %defines, @include_tags, @exclude_tags); # the variables for getopt
345392

346393
# this needs to come before GetOptions to ensure the
347394
# default -> environment -> flag override priority
@@ -351,7 +398,7 @@ ($$$$)
351398
}
352399

353400
GetOptions("D=s" => \%defines, "X=s" => \@exclude_tags, "I=s" => \@include_tags, 'color!' => \$color_output_enabled);
354-
getopts('c:efi:j:nphCTFKs:S:') or &main::HELP_MESSAGE(\*STDOUT, "", $main::VERSION, "");
401+
getopts('c:efi:j:nphCTFKs:S:t:') or &main::HELP_MESSAGE(\*STDOUT, "", $main::VERSION, "");
355402
$opt_c or &main::HELP_MESSAGE(\*STDOUT, "", $main::VERSION, "");
356403
$opt_j = $opt_j || $ENV{'TESTPL_JOBS'} || 0;
357404
if($opt_j && $opt_j != 1 && !$has_thread_pool) {
@@ -369,6 +416,7 @@ ($$$$)
369416
my $dry_run = $opt_n;
370417
my $log_suffix = $opt_s;
371418
my $exit_signal_checks = defined($opt_e);
419+
my $timeout = $opt_t || $ENV{'TESTPL_TIMEOUT'} || 0;
372420

373421
my $logfile_name = "tests";
374422
if($log_suffix) {
@@ -403,7 +451,7 @@ ($)
403451
defined($pool) or print " Running $files[$_]";
404452
my $start_time = time();
405453
$failed_skipped = test(
406-
$test, $files[$_], $t_level, $opt_c, $opt_i, $dry_run, \%defines, \@include_tags, \@exclude_tags, $log_suffix, $exit_signal_checks);
454+
$test, $files[$_], $t_level, $opt_c, $opt_i, $dry_run, \%defines, \@include_tags, \@exclude_tags, $log_suffix, $exit_signal_checks, $timeout);
407455
my $runtime = time() - $start_time;
408456

409457
lock($skips);

0 commit comments

Comments
 (0)