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 )