App-karr
view release on metacpan or search on metacpan
t/148-foundation-runner-child-leak.t view on Meta::CPAN
return unless defined $pid;
return if waitpid( $pid, WNOHANG ) != 0;
kill 'KILL', $pid;
waitpid( $pid, 0 );
}
subtest 'an unopenable log is refused before anything is forked' => sub {
my $foundation = FakeFoundation->new; # held: the Runner keeps it weakly
my $runner = App::karr::Foundation::Runner->new( foundation => $foundation );
my $repo = tempdir( CLEANUP => 1 );
# A directory where the log belongs: open '>>' fails with EISDIR whatever
# the caller's privileges are, so this holds for root too.
$repo->child('.karr.log')->mkpath;
local $LAST_CHILD_PID;
eval { $runner->_run_command( $repo, { command => 'true', max_runtime => 5 } ) };
my $err = $@;
like $err, qr/^open log /, 'the run is refused, naming the log';
is $LAST_CHILD_PID, undef, 'and nothing was forked: no agent was started'
or diag "a child was forked before the log open failed: $LAST_CHILD_PID";
is waitpid( -1, WNOHANG ), -1, 'no child of this process is left over'
or diag 'the refused run left a child behind';
cleanup_child($LAST_CHILD_PID);
};
subtest 'a log lost mid-run does not cost the agent its SIGTERM and its reap' => sub {
# The reachable half: the log is writable when the run starts, so the
# foundation's START line lands and the agent is forked -- and then the
# agent itself replaces .karr.log with a directory and hangs past
# max_runtime. The TIMEOUT append then fails, in the window between the tee
# loop and the waitpid. `exec` in the command matters: without it /bin/sh
# keeps the sleep as a grandchild that survives the kill, and this test
# would litter the box.
my $f = App::karr::Foundation->new( _config_data => {} );
my $repo = tempdir( CLEANUP => 1 );
my $cmd = 'rm -f .karr.log; mkdir .karr.log; exec sleep 30';
local $LAST_CHILD_PID;
my @warnings;
my $ok = do {
local $SIG{__WARN__} = sub { push @warnings, $_[0] };
eval { $f->_run_command( $repo, { max_runtime => 1 }, $cmd ); 1 };
};
my $err = $@;
ok !$ok, 'the broken log still fails the run for this board'
or diag 'the run reported success over an unwritable log';
like $err, qr/\.karr\.log/, 'and the error names the log';
ok defined $LAST_CHILD_PID, 'the agent really was forked'
or diag 'nothing forked -- this subtest is not exercising the window';
SKIP: {
skip 'nothing was forked', 3 unless defined $LAST_CHILD_PID;
is waitpid( $LAST_CHILD_PID, WNOHANG ), -1,
'the library reaped the agent: it is no longer a child of this process'
or diag "pid $LAST_CHILD_PID is still ours -- alive, or a zombie";
is kill( 0, $LAST_CHILD_PID ), 0, 'and it is not running any more'
or diag "pid $LAST_CHILD_PID survived the run";
ok scalar( grep { /cannot write .*\.karr\.log/ } @warnings ),
'the TIMEOUT line it could not write is reported, not swallowed'
or diag "warnings were:\n@warnings";
}
cleanup_child($LAST_CHILD_PID);
is waitpid( -1, WNOHANG ), -1, 'no child of this process is left over'
or diag 'the timed-out run left a child behind';
};
done_testing;
( run in 1.722 second using v1.01-cache-2.11-cpan-8dfa8b56332 )