Developer-Dashboard
view release on metacpan or search on metacpan
t/14-coverage-closure-extra.t view on Meta::CPAN
my @loops = $runner->running_loops;
is( scalar @loops, 1, 'running_loops lists active managed loop pids' );
is( $loops[0]{name}, $loop_name, 'running_loops returns the managed loop name' );
$collector_store->mark_run_started( $loop_name, {} );
ok( $collector_store->read_status($loop_name)->{running}, 'collector status reports running before the loop is stopped' );
my $stopped_pid = $runner->stop_loop($loop_name);
is( $stopped_pid, $managed_child, 'stop_loop returns managed loop pid' );
waitpid( $managed_child, 0 );
ok( !-e $pidfile, 'stop_loop removes loop pid files after forced kill' );
ok( !-e $statefile, 'stop_loop removes loop state files after forced kill' );
my $status_after_stop = $collector_store->read_status($loop_name) || {};
ok( !$status_after_stop->{running}, 'stop_loop resets the consumer-facing running flag so a stopped collector is no longer reported as running' );
is( $status_after_stop->{active_runs}, 0, 'stop_loop clears active_runs when the loop is stopped so in-flight counters do not linger' );
}
{
is( $collector_store->mark_stopped('never.seeded.collector'), undef, 'mark_stopped is a no-op when the collector has no status file yet' );
$collector_store->mark_run_started( 'reset.direct.collector', {} );
ok( $collector_store->read_status('reset.direct.collector')->{running}, 'seeded collector status reports running' );
is( $collector_store->read_status('reset.direct.collector')->{active_runs}, 1, 'seeded collector status reports one active run' );
$collector_store->mark_stopped('reset.direct.collector');
my $reset = $collector_store->read_status('reset.direct.collector');
ok( !$reset->{running}, 'mark_stopped clears the running flag directly' );
is( $reset->{active_runs}, 0, 'mark_stopped clears active_runs directly' );
ok( defined $reset->{stopped_at} && $reset->{stopped_at} ne '', 'mark_stopped records when the collector was stopped' );
}
{
# Fix A: a just-spawned worker must be persisted to active_worker_pids
# immediately, not only at the top of the next iteration, so a crash cannot
# orphan a worker that is invisible to the persisted state.
my $persist_name = 'coverage.persist.on.spawn';
my $child = fork();
die "fork failed: $!" if !defined $child;
if ( !$child ) {
no warnings 'redefine';
local *Developer::Dashboard::CollectorRunner::_job_is_due = sub { return 1 };
my $ok = $runner->_run_loop_child(
daemonize => 0,
interval => 1,
job => { command => 'sleep 30', cwd => $home },
name => $persist_name,
schedule_mode => 'interval',
single_tick => 1,
title => $runner->_process_title($persist_name),
);
exit( $ok ? 0 : 1 );
}
waitpid( $child, 0 );
my @persisted = $runner->_state_active_worker_pids($persist_name);
ok( scalar(@persisted) >= 1, 'loop persists active_worker_pids immediately on spawn so a just-started worker survives a crash-and-stop' );
for my $wpid (@persisted) { kill 9, -$wpid; kill 9, $wpid; }
}
{
# Fix D (Facet 1c): a command child that ignores SIGTERM stays alive in the
# worker's process group after the worker (group leader) exits; pass 2 must
# send the group SIGKILL unconditionally to reap it.
my $gc_file = File::Spec->catfile( $home, "trapgc.$$" );
unlink $gc_file;
my $worker = fork();
die "fork failed: $!" if !defined $worker;
if ( !$worker ) {
setsid();
my $gc = fork();
if ( !defined $gc ) { exit 1 }
if ( !$gc ) {
$SIG{TERM} = 'IGNORE';
select undef, undef, undef, 60;
exit 0;
}
open my $gf, '>', $gc_file or exit 1;
print {$gf} $gc;
close $gf;
$SIG{TERM} = 'DEFAULT';
select undef, undef, undef, 60;
exit 0;
}
my $gc_pid = '';
for ( 1 .. 60 ) { last if -s $gc_file; select undef, undef, undef, 0.05; }
if ( open my $gf, '<', $gc_file ) { local $/; $gc_pid = <$gf>; close $gf; }
$gc_pid =~ s/\s+//g;
ok( $gc_pid && kill( 0, $gc_pid ), 'SIGTERM-ignoring command child is running before termination' );
$runner->_terminate_loop_workers( { $worker => 1 } );
select undef, undef, undef, 0.3;
ok( !kill( 0, $gc_pid ), '_terminate_loop_workers group-SIGKILLs a SIGTERM-ignoring child left in a dead worker process group' );
kill 9, -$worker; kill 9, $worker; waitpid( $worker, 0 ) if kill 0, $worker;
unlink $gc_file;
}
{
# Fix E (Facet 5): on a proc-blind host (no /proc, no ps: Windows) a live
# loop recorded in loop.json must still be recognized from its pid/name/title
# even when its status is outside the old whitelist, so stop can kill it.
my $ename = 'coverage.confirm.broadened';
my $echild = fork();
die "fork failed: $!" if !defined $echild;
if ( !$echild ) { $SIG{TERM} = 'DEFAULT'; select undef, undef, undef, 60; exit 0; }
my $epidfile = $runner->_pidfile($ename);
open my $ef, '>', $epidfile or die $!;
print {$ef} $echild;
close $ef;
$runner->_write_loop_state(
$ename,
{
pid => $echild,
name => $ename,
process_name => $runner->_process_title($ename),
status => 'attention',
}
);
my $ret;
{
no warnings 'redefine';
local *Developer::Dashboard::CollectorRunner::_read_process_title = sub { return undef };
local *Developer::Dashboard::CollectorRunner::_read_process_env_marker = sub { return undef };
t/14-coverage-closure-extra.t view on Meta::CPAN
},
'start_loop child path dispatches the expected collector job hash',
);
}
{
my $loop_name = 'coverage.loop.inline';
no warnings 'redefine';
local *Developer::Dashboard::CollectorRunner::_job_is_due = sub { return 1 };
local *Developer::Dashboard::CollectorRunner::run_once = sub { return { ok => 1 } };
ok(
$runner->_run_loop_child(
daemonize => 0,
interval => 0,
job => { command => 'printf inline', cwd => $home },
name => $loop_name,
schedule_mode => 'interval',
single_tick => 1,
title => $runner->_process_title($loop_name),
),
'_run_loop_child can execute a single non-daemonized coverage tick',
);
my $state = $runner->loop_state($loop_name);
is( $state->{status}, 'running', '_run_loop_child non-daemonized tick still writes running state' );
}
{
my $loop_name = 'coverage.loop.scrub';
my $seen_file = File::Spec->catfile( $paths->state_root, 'coverage-loop-scrub.json' );
my $child_pid = fork();
die "fork failed: $!" if !defined $child_pid;
if ( !$child_pid ) {
no warnings 'redefine';
local $ENV{PERL5OPT} = '-MDevel::Cover';
local $ENV{HARNESS_PERL_SWITCHES} = '-MDevel::Cover';
local *Developer::Dashboard::CollectorRunner::_job_is_due = sub { return 1 };
local *Developer::Dashboard::CollectorRunner::run_once = sub {
my ($self, $job) = @_;
open my $fh, '>', $seen_file or die "Unable to write $seen_file: $!";
print {$fh} json_encode(
{
perl5opt => ( defined $ENV{PERL5OPT} ? $ENV{PERL5OPT} : '' ),
harness_perl_switches => ( defined $ENV{HARNESS_PERL_SWITCHES} ? $ENV{HARNESS_PERL_SWITCHES} : '' ),
}
);
close $fh;
return { ok => 1 };
};
my $ok = $runner->_run_loop_child(
daemonize => 0,
interval => 0,
job => { command => 'printf scrub', cwd => $home },
name => $loop_name,
schedule_mode => 'interval',
single_tick => 1,
title => $runner->_process_title($loop_name),
);
exit( $ok ? 0 : 1 );
}
waitpid( $child_pid, 0 );
is( $? >> 8, 0, '_run_loop_child keeps a coverage-instrumented child alive long enough to execute one scrubbed tick' );
open my $seen_fh, '<', $seen_file or die "Unable to read $seen_file: $!";
my $seen = json_decode( do { local $/; <$seen_fh> } );
close $seen_fh;
is( $seen->{perl5opt}, '', '_run_loop_child clears PERL5OPT inside managed collector children when coverage instrumentation is active' );
is( $seen->{harness_perl_switches}, '', '_run_loop_child clears HARNESS_PERL_SWITCHES inside managed collector children when coverage instrumentation is active' );
}
{
my $loop_name = 'coverage.loop.error';
my $child_pid = fork();
die "fork failed: $!" if !defined $child_pid;
if ( !$child_pid ) {
no warnings 'redefine';
local *Developer::Dashboard::CollectorRunner::_job_is_due = sub { return 1 };
local *Developer::Dashboard::CollectorRunner::run_once = sub { die "forced child failure\n" };
my $ok = $runner->_run_loop_child(
daemonize => 1,
interval => 0,
job => { command => 'printf child', cwd => $home },
name => $loop_name,
schedule_mode => 'interval',
single_tick => 1,
title => $runner->_process_title($loop_name),
);
exit( $ok ? 0 : 1 );
}
waitpid( $child_pid, 0 );
is( $? >> 8, 0, '_run_loop_child returns cleanly after one daemonized error tick' );
my $state = $runner->loop_state($loop_name);
is( $state->{status}, 'error', '_run_loop_child writes error state when a collector tick dies' );
like( $state->{error}, qr/forced child failure/, '_run_loop_child persists the collector error message' );
}
{
my @spawned;
{
no warnings 'redefine';
local *Developer::Dashboard::CollectorRunner::is_windows = sub { return 1 };
local *Developer::Dashboard::CollectorRunner::_windows_background_worker_command = sub {
my ( undef, $name, $loop_pid ) = @_;
return ( 'perl.exe', '_dashboard-core', 'collector-worker-foreground', '--name', $name, '--loop-pid', $loop_pid );
};
local *Developer::Dashboard::CollectorRunner::_spawn_windows_background_command = sub {
my ( undef, @command ) = @_;
@spawned = @command;
return 7171;
};
is(
$runner->_start_loop_worker( { command => 'true' }, 'windows.worker', 'dashboard collector: windows.worker' ),
7171,
'_start_loop_worker returns the detached Windows collector worker pid',
);
}
is_deeply(
\@spawned,
[ 'perl.exe', '_dashboard-core', 'collector-worker-foreground', '--name', 'windows.worker', '--loop-pid', $$ ],
'_start_loop_worker launches the detached Windows collector worker helper command',
);
}
( run in 3.208 seconds using v1.01-cache-2.11-cpan-14f38c9f855 )