Developer-Dashboard
view release on metacpan or search on metacpan
t/106-runtimemanager-coverage-2.t view on Meta::CPAN
# ---------------------------------------------------------------------------
{
no warnings 'redefine';
my ( @procs, %markers, $procfs, $state_root );
local *Developer::Dashboard::RuntimeManager::_find_processes_by_prefix = sub { return @procs };
local *Developer::Dashboard::RuntimeManager::_read_process_env_marker = sub {
my ( $self, $pid, $key ) = @_;
return exists $markers{$pid} ? $markers{$pid} : undef;
};
local *Developer::Dashboard::RuntimeManager::_procfs_available = sub { return $procfs };
local *Developer::Dashboard::PathRegistry::state_root = sub { return $state_root };
# Runtime root is non-empty: marker matches the root (kept), empty marker
# and undef markers belonging to another root are skipped.
$state_root = '/root';
$procfs = 1;
@procs = (
{ pid => 101, args => 'dashboard ajax: a' },
{ pid => 102, args => 'dashboard ajax: b' },
{ pid => 103, args => 'dashboard ajax: c' },
{ pid => 104, args => 'dashboard ajax: d' },
);
%markers = ( 101 => '', 103 => '/root', 104 => 'other' );
is_deeply(
[ map { $_->{pid} } $manager->_managed_ajax_processes ],
[103],
'_managed_ajax_processes keeps only the marker that matches the active runtime root'
);
# Runtime root is empty: the empty-marker and undef-marker arms fall
# through to the keep path (covers the l&&!r cells of lines 386 and 387).
$state_root = '';
$procfs = 1;
@procs = (
{ pid => 101, args => 'dashboard ajax: a' },
{ pid => 102, args => 'dashboard ajax: b' },
);
%markers = ( 101 => '' );
is_deeply(
[ sort { $a <=> $b } map { $_->{pid} } $manager->_managed_ajax_processes ],
[ 101, 102 ],
'_managed_ajax_processes keeps every ajax worker when the runtime root is empty'
);
# procfs unavailable with an undef marker: the middle term of line 387 is
# false, so the process is kept regardless of runtime root.
$state_root = '/root';
$procfs = 0;
@procs = ( { pid => 102, args => 'dashboard ajax: b' } );
%markers = ();
is_deeply(
[ map { $_->{pid} } $manager->_managed_ajax_processes ],
[102],
'_managed_ajax_processes keeps markerless workers when procfs is unavailable'
);
}
# ---------------------------------------------------------------------------
# _collector_supervisor_running: exercise every short-circuit stage of the
# five-term guard on source line 1634, including the previously-missing
# "alive but a different pid namespace" arm.
# ---------------------------------------------------------------------------
{
no warnings 'redefine';
my ( $ns, $super ) = ( 1, 1 );
local *Developer::Dashboard::RuntimeManager::_same_pid_namespace = sub { return $ns };
local *Developer::Dashboard::RuntimeManager::_is_collector_supervisor = sub { return $super };
my $pidfile = $manager->_collector_supervisor_pidfile;
my $plant = sub {
my ($content) = @_;
open my $fh, '>', $pidfile or die "Unable to write $pidfile: $!";
print {$fh} $content;
close $fh;
};
$plant->('');
is( $manager->_collector_supervisor_running, undef, 'supervisor running: empty pid file yields undef' );
$plant->('abc');
is( $manager->_collector_supervisor_running, undef, 'supervisor running: non-numeric pid yields undef' );
$plant->('999999');
is( $manager->_collector_supervisor_running, undef, 'supervisor running: dead numeric pid yields undef' );
$ns = 0;
$plant->("$$");
is( $manager->_collector_supervisor_running, undef, 'supervisor running: live pid in another namespace yields undef' );
$ns = 1;
$super = 0;
$plant->("$$");
is( $manager->_collector_supervisor_running, undef, 'supervisor running: live same-namespace non-supervisor yields undef' );
$super = 1;
$plant->("$$");
is( $manager->_collector_supervisor_running, $$ + 0, 'supervisor running: live managed supervisor pid is returned' );
$manager->_cleanup_collector_supervisor_files;
}
# ---------------------------------------------------------------------------
# _merge_collector_supervisor_targets: cover the "$names || []" branch on
# source line 1405 for both a truthy and a falsy names argument, and for both
# an empty and a populated persisted watch set.
# ---------------------------------------------------------------------------
{
no warnings 'redefine';
my $sup_state = {};
local *Developer::Dashboard::RuntimeManager::_collector_supervisor_state = sub { return $sup_state };
local *Developer::Dashboard::RuntimeManager::_set_collector_supervisor_targets = sub {
my ( $self, $targets ) = @_;
return scalar @{ $targets || [] };
};
$sup_state = {};
is( $manager->_merge_collector_supervisor_targets( ['alpha'] ), 1, 'merge with a truthy names list and an empty watch set' );
is( $manager->_merge_collector_supervisor_targets(undef), 0, 'merge with an undef names list defaults to an empty list' );
$sup_state = { watched_names => ['xray'] };
is( $manager->_merge_collector_supervisor_targets( ['bravo'] ), 2, 'merge unions a truthy names list with the persisted watch set' );
t/106-runtimemanager-coverage-2.t view on Meta::CPAN
is( $manager->_read_process_state(4242), undef, '_read_process_state returns undef when the stat line does not match' );
$slurp = undef;
is( $manager->_read_process_state(4242), undef, '_read_process_state returns undef when the stat file is unreadable' );
}
# ---------------------------------------------------------------------------
# _read_process_env_marker: cover both arms of the KEY=VALUE split guard on
# source line 3251. Our own environ supplies well-formed pairs; a child whose
# process title has overwritten its environ region supplies the malformed
# pairs that make the skip branch fire.
# ---------------------------------------------------------------------------
{
# /proc/self/environ reflects the environ captured at process start, so we
# assert a well-formed pair was parsed rather than the value of a variable
# mutated after startup.
my $home_value = $manager->_read_process_env_marker( $$, 'HOME' );
ok( defined $home_value && $home_value ne '', '_read_process_env_marker reads a well-formed KEY=VALUE pair from the current process' );
is( $manager->_read_process_env_marker( $$, 'DD_ABSENT_KEY_XYZ' ), undef, '_read_process_env_marker returns undef for an absent key' );
my $child = fork();
die "fork failed: $!" if !defined $child;
if ( !$child ) {
$0 = 'dashboard collector coverage worker ' . ( 'z' x 512 );
select undef, undef, undef, 5;
POSIX::_exit(0);
}
my $environ = "/proc/$child/environ";
my $ready = 0;
for ( 1 .. 200 ) {
if ( -r $environ ) { $ready = 1; last }
select undef, undef, undef, 0.02;
}
SKIP: {
skip 'child /proc environ not readable on this host', 1 if !$ready;
my $marker = $manager->_read_process_env_marker( $child, 'DD_ABSENT_KEY_XYZ' );
is( $marker, undef, '_read_process_env_marker skips malformed environ entries and returns undef' );
}
kill 'KILL', $child;
waitpid $child, 0;
}
chdir $original_cwd or die "Unable to restore cwd: $!";
done_testing;
__END__
=pod
=head1 NAME
t/106-runtimemanager-coverage-2.t - branch and condition closure for the runtime lifecycle manager
=head1 PURPOSE
This test drives the residual branch and condition arms of
C<Developer::Dashboard::RuntimeManager> that the rest of the suite never
reaches: the empty-runtime-root arms of the managed Ajax worker filter, the
alive-but-foreign-namespace arm of the collector supervisor discovery guard,
the environment-tuning fallbacks, the Windows PowerShell listener parser, the
process-state and process-environ readers, and the Windows background
state-assembly path. It exists so those arms are exercised by a real,
observable behavior rather than an annotation.
=head1 WHY IT EXISTS
The all-metric coverage gate requires C<lib/> to report 100.0 on statement,
subroutine, branch, AND condition. The runtime manager accumulated a handful of
short-circuit arms and environ/stat edge cases that only occur against live
process trees or Windows hosts, so ordinary functional tests left them dark.
Two of those arms were previously masked with C<# uncoverable> annotations even
though real dashboard processes (whose C<$0> rewrite scrambles their environ,
and whose stat reads can come back empty) genuinely reach them; this test
replaces those annotations with executed coverage.
=head1 WHEN TO USE
Use this file when changing runtime lifecycle discovery, the collector
watchdog supervisor, the managed Ajax worker filter, the process
inspection helpers, or the Windows web-service background launch path.
=head1 HOW TO USE
Run C<prove -lv t/106-runtimemanager-coverage-2.t> while iterating on the
runtime manager. Keep it green under C<prove -lr t>, and confirm the runtime
manager still reports 100.0 on every Devel::Cover metric before release.
=head1 WHAT USES IT
Developers during TDD, the repository test suite, and the coverage gate use
this file to keep the runtime manager's process-lifecycle edge cases exercised.
=head1 EXAMPLES
Example 1:
prove -lv t/106-runtimemanager-coverage-2.t
Run the dedicated runtime-manager branch/condition closure check by itself.
Example 2:
prove -lr t
Run it inside the full repository suite before release.
=cut
( run in 1.254 second using v1.01-cache-2.11-cpan-14f38c9f855 )