Developer-Dashboard
view release on metacpan or search on metacpan
t/100-runtimemanager-coverage.t view on Meta::CPAN
sub build_manager {
my (%over) = @_;
return Developer::Dashboard::RuntimeManager->new(
app_builder => $over{app_builder} || sub { return Local::Server->new(@_) },
config => $over{config} || $config,
files => $files,
paths => $paths,
runner => $over{runner} || $runner,
( exists $over{collectors} ? ( collectors => $over{collectors} ) : () ),
);
}
my $manager = build_manager();
# --- new(): collectors arg present (left side of the || short-circuit) ------
{
my $explicit = build_manager( collectors => $collector_store );
isa_ok( $explicit, $RM, 'manager built with an explicit collectors object' );
is( $explicit->{collectors}, $collector_store, 'new keeps an explicitly supplied collectors object' );
}
# --- _numeric_pid ------------------------------------------------------------
is( $RM->can('_numeric_pid') ? Developer::Dashboard::RuntimeManager::_numeric_pid(undef) : 'x', undef, '_numeric_pid returns undef for undef' );
is( Developer::Dashboard::RuntimeManager::_numeric_pid(''), undef, '_numeric_pid returns undef for empty string' );
is( Developer::Dashboard::RuntimeManager::_numeric_pid('42'), 42, '_numeric_pid coerces numeric pids' );
is( Developer::Dashboard::RuntimeManager::_numeric_pid('x9'), 'x9', '_numeric_pid preserves non-numeric scalars' );
# --- _reap_child_process guard ----------------------------------------------
is( $manager->_reap_child_process(undef), 0, '_reap_child_process rejects undef pid' );
is( $manager->_reap_child_process('abc'), 0, '_reap_child_process rejects non-numeric pid' );
is( $manager->_reap_child_process(0), 0, '_reap_child_process rejects pid below one' );
is( $manager->_reap_child_process(999999), 0, '_reap_child_process returns false for a non-child pid' );
# --- _pid_is_running guard ---------------------------------------------------
is( $manager->_pid_is_running(undef), 0, '_pid_is_running rejects undef pid' );
is( $manager->_pid_is_running('abc'), 0, '_pid_is_running rejects non-numeric pid' );
is( $manager->_pid_is_running(0), 0, '_pid_is_running rejects pid below one' );
ok( $manager->_pid_is_running($$), '_pid_is_running confirms the current live process' );
# --- _reap_any_child_processes ----------------------------------------------
{
no warnings 'redefine';
local *Developer::Dashboard::RuntimeManager::_wait_for_any_child_process = sub { return undef };
is( $manager->_reap_any_child_processes, 0, '_reap_any_child_processes stops when waitpid yields undef' );
}
{
my $child = fork();
die "fork failed: $!" if !defined $child;
if ( !$child ) { POSIX::_exit(0) }
my $reaped = 0;
for ( 1 .. 40 ) {
$reaped = $manager->_reap_any_child_processes;
last if $reaped;
select undef, undef, undef, 0.05;
}
is( $reaped, 1, '_reap_any_child_processes reaps one exited direct child then stops on -1' );
}
# --- _wait_for_windows_web_shutdown -----------------------------------------
ok( $manager->_wait_for_windows_web_shutdown( $$, undef, undef ), '_wait_for_windows_web_shutdown true when the saved pid is alive' );
ok( !$manager->_wait_for_windows_web_shutdown( 999999, undef, [] ), '_wait_for_windows_web_shutdown false when nothing is alive' );
ok( $manager->_wait_for_windows_web_shutdown( undef, undef, [$$] ), '_wait_for_windows_web_shutdown true when a listener pid is alive' );
{
no warnings 'redefine';
local *Developer::Dashboard::RuntimeManager::_listener_pids_for_port = sub { return (4321) };
ok( $manager->_wait_for_windows_web_shutdown( undef, 7890, [] ), '_wait_for_windows_web_shutdown true when the port still has listeners' );
}
{
no warnings 'redefine';
local *Developer::Dashboard::RuntimeManager::_listener_pids_for_port = sub { return () };
ok( !$manager->_wait_for_windows_web_shutdown( undef, 7890, [] ), '_wait_for_windows_web_shutdown false when the port has no listeners' );
ok( !$manager->_wait_for_windows_web_shutdown( undef, undef, [] ), '_wait_for_windows_web_shutdown false when no port is supplied' );
}
# --- _portable_signal empty string ------------------------------------------
{
my $err = eval { Developer::Dashboard::RuntimeManager::_portable_signal(''); 1 } ? '' : $@;
like( $err, qr/Missing signal name/, '_portable_signal rejects an empty signal name' );
}
# --- _collector_disabled -----------------------------------------------------
is( $manager->_collector_disabled(undef), 0, '_collector_disabled false for a non-hash job' );
is( $manager->_collector_disabled( {} ), 0, '_collector_disabled false for an enabled job' );
is( $manager->_collector_disabled( { disable => 1 } ), 1, '_collector_disabled true for a disabled job' );
# --- _collector_job_by_name --------------------------------------------------
{
my $err = eval { $manager->_collector_job_by_name(undef); 1 } ? '' : $@;
like( $err, qr/Missing collector name/, '_collector_job_by_name requires a name' );
$err = eval { $manager->_collector_job_by_name(''); 1 } ? '' : $@;
like( $err, qr/Missing collector name/, '_collector_job_by_name rejects an empty name' );
is( $manager->_collector_job_by_name('alpha.collector')->{name}, 'alpha.collector', '_collector_job_by_name finds a configured job' );
$err = eval { $manager->_collector_job_by_name('does.not.exist'); 1 } ? '' : $@;
like( $err, qr/Unknown collector 'does\.not\.exist'/, '_collector_job_by_name dies on unknown collectors' );
}
{
# A non-hash entry in the collectors array is skipped by _collector_job_by_name.
my $mixed = Developer::Dashboard::Config->new( files => $files, paths => $paths );
no warnings 'redefine';
local *Developer::Dashboard::Config::collectors = sub { return [ 'not-a-hash', { name => 'good.one' } ] };
my $mixed_manager = build_manager( config => $mixed );
is( $mixed_manager->_collector_job_by_name('good.one')->{name}, 'good.one', '_collector_job_by_name skips non-hash collector entries' );
}
# --- _loop_job_for_named_start ----------------------------------------------
{
my $manual = $manager->_loop_job_for_named_start( { name => 'm', schedule => 'manual' } );
is( $manual->{schedule}, 'interval', '_loop_job_for_named_start promotes manual collectors to interval' );
is( $manual->{interval}, 30, '_loop_job_for_named_start defaults the manual interval' );
my $manual_kept = $manager->_loop_job_for_named_start( { name => 'm', schedule => 'manual', interval => 12 } );
is( $manual_kept->{interval}, 12, '_loop_job_for_named_start keeps a valid manual interval' );
my $manual_bad = $manager->_loop_job_for_named_start( { name => 'm', schedule => 'manual', interval => 'x' } );
is( $manual_bad->{interval}, 30, '_loop_job_for_named_start replaces a non-numeric manual interval' );
my $cron = $manager->_loop_job_for_named_start( { name => 'c', cron => '* * * * *' } );
is( $cron->{schedule}, undef, '_loop_job_for_named_start leaves a cron schedule alone' );
my $interval = $manager->_loop_job_for_named_start( { name => 'i', interval => 5 } );
is( $interval->{interval}, 5, '_loop_job_for_named_start leaves an interval schedule alone' );
t/100-runtimemanager-coverage.t view on Meta::CPAN
my @stop_names;
local *Developer::Dashboard::RuntimeManager::stop_collectors = sub { my ( undef, %a ) = @_; @stop_names = @{ $a{names} }; return () };
my $disabled = eval { $named_manager->start_named_collector( name => 'named.disabled' ); 1 } ? '' : $@;
like( $disabled, qr/Collector 'named\.disabled' is disabled/, 'start_named_collector rejects disabled collectors' );
is_deeply( \@stop_names, ['named.disabled'], 'start_named_collector stops a disabled collector before failing' );
}
{
local *Developer::Dashboard::RuntimeManager::_collector_runtime_ready = sub { return 0 };
my $err = eval { $named_manager->start_named_collector( name => 'named.ok' ); 1 } ? '' : $@;
like( $err, qr/Failed to keep collector 'named\.ok' running after startup/, 'start_named_collector dies when the collector does not stay ready' );
}
{
my $fail_runner = Local::Runner->new( fail => { 'named.ok' => "kaboom\n" } );
my $fail_manager = build_manager( config => $named_config, runner => $fail_runner );
my $err = eval { $fail_manager->start_named_collector( name => 'named.ok' ); 1 } ? '' : $@;
like( $err, qr/Failed to start collector 'named\.ok': kaboom/, 'start_named_collector surfaces start_loop failures' );
}
}
# --- _stop_disabled_collectors ----------------------------------------------
{
is_deeply( [ $manager->_stop_disabled_collectors( jobs => 'not-array' ) ], [], '_stop_disabled_collectors rejects a non-array jobs value' );
my @stopped_names;
no warnings 'redefine';
local *Developer::Dashboard::RuntimeManager::stop_collectors = sub { my ( undef, %a ) = @_; @stopped_names = @{ $a{names} }; return () };
my @jobs = (
'not-a-hash',
{ name => 'dis.one', disable => 1 },
{ name => '', disable => 1 },
{ name => 'dis.two', disable => 1 },
{ name => 'enabled', interval => 1 },
);
my @names = $manager->_stop_disabled_collectors( jobs => \@jobs );
is_deeply( [ sort @names ], [ 'dis.one', 'dis.two' ], '_stop_disabled_collectors stops disabled named collectors' );
@stopped_names = ();
my @scoped = $manager->_stop_disabled_collectors( jobs => \@jobs, wanted => { 'dis.one' => 1 } );
is_deeply( \@scoped, ['dis.one'], '_stop_disabled_collectors honours the wanted filter' );
my @none = $manager->_stop_disabled_collectors( jobs => [ { name => 'enabled', interval => 1 } ] );
is_deeply( \@none, [], '_stop_disabled_collectors returns empty when nothing is disabled' );
}
# --- _ensure_collector_pid_stopped ------------------------------------------
is( $manager->_ensure_collector_pid_stopped( 'c', undef ), 1, '_ensure_collector_pid_stopped ignores an undef pid' );
is( $manager->_ensure_collector_pid_stopped( 'c', 'x' ), 1, '_ensure_collector_pid_stopped ignores a non-numeric pid' );
is( $manager->_ensure_collector_pid_stopped( 'c', 0 ), 1, '_ensure_collector_pid_stopped ignores a pid below one' );
{
no warnings 'redefine';
local *Developer::Dashboard::RuntimeManager::_same_pid_namespace = sub { return 0 };
is( $manager->_ensure_collector_pid_stopped( 'c', 123456 ), 1, '_ensure_collector_pid_stopped ignores a foreign-namespace pid' );
}
{
no warnings 'redefine';
local *Developer::Dashboard::RuntimeManager::_same_pid_namespace = sub { return 1 };
local *Developer::Dashboard::RuntimeManager::_send_signal = sub { return 1 };
local *Developer::Dashboard::RuntimeManager::_reap_child_process = sub { return 1 };
my @seq = ( 1, 0 ); # alive at first check, then gone during the TERM loop
local *Developer::Dashboard::RuntimeManager::_pid_is_running = sub { return @seq ? shift(@seq) : 0 };
is( $manager->_ensure_collector_pid_stopped( 'c', 123456 ), 1, '_ensure_collector_pid_stopped returns after the collector dies on TERM' );
@seq = ( (1) x 21, 0 ); # survives TERM loop, then dies after KILL
is( $manager->_ensure_collector_pid_stopped( 'c', 123456 ), 1, '_ensure_collector_pid_stopped escalates to KILL when TERM is not enough' );
local *Developer::Dashboard::RuntimeManager::_pid_is_running = sub { return 1 };
my $err = eval { $manager->_ensure_collector_pid_stopped( 'c', 123456 ); 1 } ? '' : $@;
like( $err, qr/Collector 'c' did not stop after TERM and KILL/, '_ensure_collector_pid_stopped dies when the collector never stops' );
}
# --- serve_all: foreground path ---------------------------------------------
{
no warnings 'redefine';
local *Developer::Dashboard::RuntimeManager::start_collectors = sub { return ( { name => 'a', pid => 1 } ) };
local *Developer::Dashboard::RuntimeManager::stop_collectors = sub { return ('a') };
local *Developer::Dashboard::RuntimeManager::start_web = sub { return 'server-result' };
my $fg = $manager->serve_all( foreground => 1, host => '127.0.0.1', port => 9100, workers => 2, ssl => 1 );
is( $fg->{foreground}, 1, 'serve_all foreground returns a foreground marker' );
is( $fg->{host}, '127.0.0.1', 'serve_all foreground keeps an explicit host' );
is( $fg->{result}, 'server-result', 'serve_all foreground returns the web result' );
my $fg_default = $manager->serve_all( foreground => 1 );
is( $fg_default->{host}, '0.0.0.0', 'serve_all foreground defaults the host' );
is( $fg_default->{port}, 7890, 'serve_all foreground defaults the port' );
}
{
no warnings 'redefine';
local *Developer::Dashboard::RuntimeManager::start_collectors = sub { return () };
local *Developer::Dashboard::RuntimeManager::stop_collectors = sub { return () };
local *Developer::Dashboard::RuntimeManager::start_web = sub { die "fg boom\n" };
my $err = eval { $manager->serve_all( foreground => 1 ); 1 } ? '' : $@;
like( $err, qr/fg boom/, 'serve_all foreground re-throws a web startup error after stopping collectors' );
}
# --- serve_all: background path ---------------------------------------------
{
no warnings 'redefine';
local *Developer::Dashboard::RuntimeManager::_restart_web_with_retry = sub { return 5555 };
local *Developer::Dashboard::RuntimeManager::start_collectors = sub { return ( { name => 'a', pid => 1 } ) };
my $bg = $manager->serve_all( host => '127.0.0.1', port => 9101 );
is( $bg->{pid}, 5555, 'serve_all background returns the restarted web pid' );
is( $bg->{host}, '127.0.0.1', 'serve_all background keeps the requested host' );
}
# --- _procfs_available / _slurp_proc_file / _process_exists -----------------
ok( $manager->_procfs_available, '_procfs_available true on a /proc host' );
is( $manager->_slurp_proc_file(undef), undef, '_slurp_proc_file rejects an undef path' );
is( $manager->_slurp_proc_file(''), undef, '_slurp_proc_file rejects an empty path' );
is( $manager->_slurp_proc_file('/proc/does-not-exist-xyz'), undef, '_slurp_proc_file returns undef for an unreadable path' );
ok( defined $manager->_slurp_proc_file("/proc/$$/stat"), '_slurp_proc_file reads a real proc file' );
# --- _same_pid_namespace -----------------------------------------------------
is( $manager->_same_pid_namespace(undef), 0, '_same_pid_namespace rejects undef pid' );
is( $manager->_same_pid_namespace('x'), 0, '_same_pid_namespace rejects non-numeric pid' );
is( $manager->_same_pid_namespace(0), 0, '_same_pid_namespace rejects pid below one' );
is( $manager->_same_pid_namespace(999999), 1, '_same_pid_namespace true when the target namespace is unknown' );
ok( $manager->_same_pid_namespace($$), '_same_pid_namespace true for the current process' );
{
no warnings 'redefine';
local *Developer::Dashboard::RuntimeManager::_current_pid_namespace_id = sub { return undef };
t/100-runtimemanager-coverage.t view on Meta::CPAN
local *Developer::Dashboard::RuntimeManager::_port_accepting_connections = sub { return 0 };
is( $manager->_web_runtime_ready( 5, 7890 ), 0, '_web_runtime_ready Windows fails when nothing binds the port' );
}
# --- _web_runtime_ready non-Windows main loop -------------------------------
{
no warnings 'redefine';
local *Developer::Dashboard::RuntimeManager::is_windows = sub { return 0 };
local *Developer::Dashboard::RuntimeManager::running_web = sub { return { pid => 10, port => 7890 } };
local *Developer::Dashboard::RuntimeManager::_web_runtime_matches_pid = sub { return 0 };
local *Developer::Dashboard::RuntimeManager::_same_pid_namespace = sub { return 1 };
local *Developer::Dashboard::RuntimeManager::_listener_pids_for_port = sub { return (1234) };
my @adopted;
local *Developer::Dashboard::RuntimeManager::_adopt_web_listener_pid = sub { my ( undef, %a ) = @_; push @adopted, $a{listener_pid}; return $a{listener_pid} };
is( $manager->_web_runtime_ready( 5, undef ), 1, '_web_runtime_ready adopts a listener pid discovered from runtime state' );
is_deeply( \@adopted, [1234], '_web_runtime_ready hands the discovered listener pid to the adopter' );
}
{
no warnings 'redefine';
local *Developer::Dashboard::RuntimeManager::is_windows = sub { return 0 };
local *Developer::Dashboard::RuntimeManager::running_web = sub { return { pid => 10, port => 7890 } };
local *Developer::Dashboard::RuntimeManager::_web_runtime_matches_pid = sub { return 0 };
local *Developer::Dashboard::RuntimeManager::_same_pid_namespace = sub { my ( undef, $p ) = @_; return $p == 1234 ? 1 : 0 };
local *Developer::Dashboard::RuntimeManager::_listener_pids_for_port = sub { return (1234) };
is( $manager->_web_runtime_ready( 5, 7890 ), 1, '_web_runtime_ready confirms a listener even when the startup pid is in a foreign namespace' );
}
# --- _adopt_web_listener_pid ------------------------------------------------
{
no warnings 'redefine';
local *Developer::Dashboard::RuntimeManager::_write_web_state = sub { return $_[1] };
is( $manager->_adopt_web_listener_pid( listener_pid => undef ), undef, '_adopt_web_listener_pid rejects an undef listener pid' );
is( $manager->_adopt_web_listener_pid( listener_pid => 'x' ), undef, '_adopt_web_listener_pid rejects a non-numeric listener pid' );
is( $manager->_adopt_web_listener_pid( listener_pid => 0 ), undef, '_adopt_web_listener_pid rejects a listener pid below one' );
{
local *Developer::Dashboard::RuntimeManager::_same_pid_namespace = sub { return 0 };
is( $manager->_adopt_web_listener_pid( listener_pid => 5 ), undef, '_adopt_web_listener_pid rejects a foreign-namespace listener pid' );
}
{
local *Developer::Dashboard::RuntimeManager::_same_pid_namespace = sub { return 1 };
local *Developer::Dashboard::RuntimeManager::_read_process_title = sub { return 'dashboard web: 0.0.0.0:1' };
$manager->_cleanup_web_files;
is( $manager->_adopt_web_listener_pid( listener_pid => 5, state => 'not-a-hash' ), 5, '_adopt_web_listener_pid adopts a listener pid using persisted web state' );
is( $manager->_adopt_web_listener_pid( listener_pid => 5, state => { pid => 5 } ), undef, '_adopt_web_listener_pid skips when the state pid already matches' );
local *Developer::Dashboard::RuntimeManager::_read_process_title = sub { return undef };
is( $manager->_adopt_web_listener_pid( listener_pid => 6, state => { pid => 1 } ), 6, '_adopt_web_listener_pid adopts even when the process title is unavailable' );
}
}
# --- _wait_for_unix_web_shutdown --------------------------------------------
{
no warnings 'redefine';
local *Developer::Dashboard::RuntimeManager::_pid_is_running = sub { return 0 };
is( $manager->_wait_for_unix_web_shutdown(), 0, '_wait_for_unix_web_shutdown false when nothing is pending' );
is( $manager->_wait_for_unix_web_shutdown( pid => 'x' ), 0, '_wait_for_unix_web_shutdown false for a non-numeric pid' );
is( $manager->_wait_for_unix_web_shutdown( pid => 0 ), 0, '_wait_for_unix_web_shutdown false for a pid below one' );
}
{
no warnings 'redefine';
local *Developer::Dashboard::RuntimeManager::_pid_is_running = sub { return 1 };
is( $manager->_wait_for_unix_web_shutdown( pid => $$ ), 1, '_wait_for_unix_web_shutdown true while the wrapper pid is alive' );
is( $manager->_wait_for_unix_web_shutdown( ajax_pids => [ undef, $$ ] ), 1, '_wait_for_unix_web_shutdown true while an ajax worker is alive' );
is( $manager->_wait_for_unix_web_shutdown( legacy_pids => [ undef, $$ ] ), 1, '_wait_for_unix_web_shutdown true while a legacy web pid is alive' );
}
is( $manager->_wait_for_unix_web_shutdown( listener_pids => [ undef, 'x', $$ ] ), 1, '_wait_for_unix_web_shutdown true while a listener pid still answers signals' );
is( $manager->_wait_for_unix_web_shutdown( listener_pids => [ 999999 ] ), 0, '_wait_for_unix_web_shutdown false when a listener pid is gone' );
# --- _managed_ajax_processes ------------------------------------------------
{
no warnings 'redefine';
my $root = $paths->state_root;
local *Developer::Dashboard::RuntimeManager::_find_processes_by_prefix = sub {
return (
{ pid => 1, args => 'dashboard ajax: a' },
{ pid => 2, args => 'dashboard ajax: b' },
{ pid => 3, args => 'dashboard ajax: c' },
{ pid => 4, args => 'dashboard ajax: d' },
);
};
local *Developer::Dashboard::RuntimeManager::_read_process_env_marker = sub {
my ( undef, $pid ) = @_;
return $root if $pid == 1;
return '/other' if $pid == 2;
return '' if $pid == 3;
return undef;
};
local *Developer::Dashboard::RuntimeManager::_procfs_available = sub { return 1 };
is_deeply( [ map { $_->{pid} } $manager->_managed_ajax_processes ], [1], '_managed_ajax_processes keeps only the current runtime root workers' );
}
{
no warnings 'redefine';
local *Developer::Dashboard::RuntimeManager::_find_processes_by_prefix = sub { return () };
local $manager->{paths} = undef;
is_deeply( [ $manager->_managed_ajax_processes ], [], '_managed_ajax_processes tolerates a runtime with no path registry' );
}
# --- _is_managed_web ---------------------------------------------------------
is( $manager->_is_managed_web(0), 0, '_is_managed_web rejects a pid of zero' );
is( $manager->_is_managed_web(999999), 0, '_is_managed_web rejects a dead pid' );
{
no warnings 'redefine';
local *Developer::Dashboard::RuntimeManager::_same_pid_namespace = sub { return 0 };
is( $manager->_is_managed_web($$), 0, '_is_managed_web rejects a foreign-namespace pid' );
}
{
no warnings 'redefine';
local *Developer::Dashboard::RuntimeManager::_same_pid_namespace = sub { return 1 };
local *Developer::Dashboard::RuntimeManager::_read_process_env_marker = sub { return '1' };
ok( $manager->_is_managed_web($$), '_is_managed_web trusts the managed-web environment marker' );
local *Developer::Dashboard::RuntimeManager::_read_process_env_marker = sub { return '0' };
local *Developer::Dashboard::RuntimeManager::_read_process_title = sub { return undef };
is( $manager->_is_managed_web($$), 0, '_is_managed_web rejects a pid with no readable title' );
local *Developer::Dashboard::RuntimeManager::_read_process_title = sub { return '' };
is( $manager->_is_managed_web($$), 0, '_is_managed_web rejects a pid with an empty title' );
local *Developer::Dashboard::RuntimeManager::_read_process_title = sub { return 'dashboard web: 0.0.0.0:1' };
ok( $manager->_is_managed_web($$), '_is_managed_web recognizes a managed web process title' );
local *Developer::Dashboard::RuntimeManager::_read_process_title = sub { return 'something else' };
is( $manager->_is_managed_web($$), 0, '_is_managed_web rejects an unrelated process title' );
}
# --- _detach_web_process_session --------------------------------------------
{
( run in 0.839 second using v1.01-cache-2.11-cpan-14f38c9f855 )