App-karr
view release on metacpan or search on metacpan
t/164-foundation-signal-exit.t view on Meta::CPAN
my \$repo = \$scratch->child('repo');
my \$f = App::karr::Foundation->new( _config_data => {} );
\$f->_run_command(\$repo, { max_runtime => 30 }, 'exec $^X $wrapper_path');
exit 0;
PERL
return $driver;
}
sub reap_wait {
my ( $pid, $max ) = @_;
$max //= 10;
my $end = time + $max;
while ( time < $end ) {
my $w = waitpid( $pid, WNOHANG );
return $w if $w > 0 || $w < 0;
select undef, undef, undef, 0.05;
}
diag "driver $pid did not exit within ${max}s; killing";
kill 'KILL', $pid;
waitpid( $pid, 0 );
return -1;
}
sub wait_for_pid_file {
my ( $pid_file, $timeout ) = @_;
my $pid;
my $end = time + $timeout;
while ( time < $end && !$pid ) {
if ( -e $pid_file ) {
my $content = $pid_file->slurp_utf8;
chomp $content;
$pid = $content if $content =~ /^\d+$/;
}
select undef, undef, undef, 0.05;
}
return $pid;
}
subtest 'agent killed by SIGKILL is reported as 128+SIGKILL, not exit 0' => sub {
my $scratch = path( tempdir( CLEANUP => 1 ) );
my $repo = $scratch->child('repo');
$repo->mkpath;
$repo->child('.karr.log')->touch;
my $pid_file = $scratch->child('agent.pid');
my $wrapper = write_agent_wrapper( $scratch, '/bin/sleep 20' );
my $driver = write_driver( $scratch, $wrapper );
my $dpid = fork;
die "fork: $!" unless defined $dpid;
if ( $dpid == 0 ) {
exec( $^X, "$driver" ) or die;
}
my $agent_pid = wait_for_pid_file( $pid_file, 5 );
ok $agent_pid, 'agent was forked (pid file populated)'
or BAIL_OUT 'no agent pid recorded -- the runner did not fork';
select undef, undef, undef, 0.1;
ok kill( 0, $agent_pid ), 'agent is alive in /proc before the kill';
# External SIGKILL -- the OOM-killer shape.
kill 'KILL', $agent_pid;
reap_wait($dpid);
my $log = -e $repo->child('.karr.log') ? $repo->child('.karr.log')->slurp_utf8 : '';
like $log, qr/END elapsed=\d+s exit=(\d+)/, 'log has END line'
or diag "log was: $log";
my ($reported) = $log =~ /END elapsed=\d+s exit=(\d+)/;
is $reported, 128 + SIGKILL,
"exit code in log is 128+SIGKILL=137 (was: $reported)"
or diag "runner reported $reported for a SIGKILLed agent -- bug #164 still present";
isnt $reported, 0,
'the SIGKILLed agent is NOT booked as exit 0'
or diag 'agent was killed by SIGKILL but runner said exit 0';
};
subtest 'agent killed by external SIGTERM+SIGKILL is reported as 128+SIGKILL' => sub {
# The wrapper ignores SIGTERM (so the kill actually escalates), then
# the test sends SIGKILL to simulate the kernel OOM-killer landing
# mid-escalation. The runner sees only SIGKILL because SIGTERM was
# caught by the wrapper.
my $scratch = path( tempdir( CLEANUP => 1 ) );
my $repo = $scratch->child('repo');
$repo->mkpath;
$repo->child('.karr.log')->touch;
my $pid_file = $scratch->child('agent.pid');
my $wrapper = write_signal_ignoring_wrapper( $scratch, '/bin/sleep 20' );
my $driver = write_driver( $scratch, $wrapper );
my $dpid = fork;
die "fork: $!" unless defined $dpid;
if ( $dpid == 0 ) {
exec( $^X, "$driver" ) or die;
}
my $agent_pid = wait_for_pid_file( $pid_file, 5 );
ok $agent_pid, 'agent was forked';
select undef, undef, undef, 0.1;
kill 'TERM', $agent_pid;
select undef, undef, undef, 0.1;
kill 'KILL', $agent_pid;
reap_wait($dpid);
my $log = -e $repo->child('.karr.log') ? $repo->child('.karr.log')->slurp_utf8 : '';
like $log, qr/END elapsed=\d+s exit=(\d+)/, 'log has END line'
or diag "log was: $log";
my ($reported) = $log =~ /END elapsed=\d+s exit=(\d+)/;
ok defined $reported, 'log has END line (parsed)';
isnt $reported, 0, 'killed agent is not booked as exit 0';
is $reported, 128 + SIGKILL,
'reported exit is 128+SIGKILL (the actual kill signal)'
or diag "runner reported $reported for a SIGTERM->SIGKILLed agent";
( run in 1.205 second using v1.01-cache-2.11-cpan-14f38c9f855 )