Developer-Dashboard

 view release on metacpan or  search on metacpan

t/09-runtime-manager.t  view on Meta::CPAN

    };
    is(
        $manager->_restart_web_with_retry( host => '127.0.0.1', port => 7924 ),
        8816,
        '_restart_web_with_retry retries when a replacement web pid fails the startup stability window',
    );
    is( $attempt, 2, '_restart_web_with_retry retries after a briefly-live replacement web pid fails stability checks' );
    is( scalar @cleanups, 1, '_restart_web_with_retry clears persisted web state before retrying a briefly-live replacement web pid' );
}

{
    my $polls = 0;
    no warnings 'redefine';
    local *Developer::Dashboard::RuntimeManager::sleep = sub { return 0 };
    local *Local::RuntimeRunner::running_loops = sub {
        $polls++;
        return ( { name => 'alpha.collector', pid => 7002 } );
    };
    ok(
        $manager->_collector_runtime_ready( 'alpha.collector', 7002 ),
        '_collector_runtime_ready returns once the managed collector survives the short confirmation window',
    );
    is( $polls, 3, '_collector_runtime_ready only spends three ready polls proving a healthy collector loop' );
}

{
    my $polls = 0;
    no warnings 'redefine';
    local *Developer::Dashboard::RuntimeManager::sleep = sub { return 0 };
    local *Local::RuntimeRunner::running_loops = sub {
        $polls++;
        return $polls <= 2
          ? ( { name => 'alpha.collector', pid => 7001 } )
          : ();
    };
    ok(
        !$manager->_collector_runtime_ready( 'alpha.collector', 7001 ),
        '_collector_runtime_ready fails when a managed collector loop disappears during the startup stability window',
    );
}

{
    my $polls = 0;
    no warnings 'redefine';
    local *Developer::Dashboard::RuntimeManager::sleep = sub { return 0 };
    local *Local::RuntimeRunner::running_loops = sub {
        $polls++;
        return ();
    };
    local *Local::RuntimeRunner::loop_state = sub {
        return {
            pid    => $$,
            name   => 'alpha.collector',
            status => 'starting',
        };
    };
    ok(
        $manager->_collector_runtime_ready( 'alpha.collector', $$ ),
        '_collector_runtime_ready falls back to the persisted loop state while the managed process title is not observable yet',
    );
    is( $polls, 0, '_collector_runtime_ready trusts the persisted loop-state fallback without consulting running_loops when the pid is already proven alive' );
}

{
    no warnings 'redefine';
    local *Developer::Dashboard::RuntimeManager::sleep = sub { return 0 };
    local *Local::RuntimeRunner::running_loops = sub {
        die "running_loops should not be consulted while the persisted loop-state fallback already proves the pid is alive\n";
    };
    local *Local::RuntimeRunner::loop_state = sub {
        return {
            pid    => $$,
            name   => 'alpha.collector',
            status => 'starting',
        };
    };
    ok(
        $manager->_collector_runtime_ready( 'alpha.collector', $$ ),
        '_collector_runtime_ready trusts the persisted loop-state fallback before the destructive running_loops cleanup path can run',
    );
}

{
    my $polls = 0;
    no warnings 'redefine';
    local *Developer::Dashboard::RuntimeManager::sleep = sub { return 0 };
    local *Developer::Dashboard::RuntimeManager::_runtime_stability_polls = sub { return 1 };
    local *Local::RuntimeRunner::loop_state = sub { return undef };
    local *Local::RuntimeRunner::running_loops = sub {
        $polls++;
        return ();
    };
    ok(
        !$manager->_collector_runtime_ready( 'alpha.collector', 7999 ),
        '_collector_runtime_ready returns false after the stability window expires without ever observing a managed collector loop',
    );
    is( $polls, 1, '_collector_runtime_ready consults running_loops across the full timeout path before giving up' );
}

{
    no warnings 'redefine';
    $runner->{started} = [];
    $runner->{loops} = [];
    $collector_store->write_status( 'missing.collector', {} );
    local *Local::RuntimeRunner::running_loops = sub { return () };

    my $result = $manager->_supervise_collectors_once( names => ['missing.collector'] );
    my $status = $collector_store->read_status('missing.collector') || {};

    is_deeply( $result->{restarted}, [], '_supervise_collectors_once does not report restarts for unknown collectors' );
    is( $result->{attention}[0]{name}, 'missing.collector', '_supervise_collectors_once reports unknown watched collectors in the attention list' );
    is( $status->{watchdog_status}, 'attention_required', '_supervise_collectors_once marks unknown watched collectors as attention_required' );
    ok( $status->{watchdog_last_error}, '_supervise_collectors_once records an explicit error for unknown watched collectors' );
}

{
    no warnings 'redefine';
    $runner->{started} = [];
    $runner->{loops} = [ { name => 'alpha.collector', pid => 4401 } ];
    local *Local::RuntimeRunner::running_loops = sub { return @{ $runner->{loops} } };

    my $result = $manager->_supervise_collectors_once( names => ['alpha.collector'] );

    is_deeply( $result->{restarted}, [], '_supervise_collectors_once skips watchdog work for collectors that are already running' );
    is_deeply( $result->{attention}, [], '_supervise_collectors_once does not raise attention for collectors that are already running' );
}

{

t/09-runtime-manager.t  view on Meta::CPAN

    my $state = $manager->running_web;
    is( $state->{pid}, $$, 'running_web trusts the recorded live listener pid for a running state even after the server process renames itself' );
}

{
    my $listener = fork();
    die "fork failed: $!" if !defined $listener;
    if ( !$listener ) {
        sleep 30;
        POSIX::_exit(0);
    }
    no warnings 'redefine';
    my $original_read = Developer::Dashboard::FileRegistry->can('read');
    local *Developer::Dashboard::FileRegistry::read = sub {
        my ( $self, $name ) = @_;
        return "$$\n" if $name eq 'web_pid';
        return $original_read->( $self, $name );
    };
    local *Developer::Dashboard::RuntimeManager::web_state = sub {
        return { pid => $$, host => '127.0.0.1', port => 7919, status => 'running' };
    };
    local *Developer::Dashboard::RuntimeManager::_is_managed_web = sub { return 0 };
    local *Developer::Dashboard::RuntimeManager::_find_web_processes = sub { return () };
    local *Developer::Dashboard::RuntimeManager::_listener_pids_for_port = sub { return ($listener) };
    my $state = $manager->running_web;
    is( $state->{pid}, $$, 'running_web trusts the recorded master pid when the managed port is still held by a separate listener worker pid' );
    kill 'KILL', $listener;
    waitpid( $listener, 0 );
}

{
    no warnings 'redefine';
    $files->write( 'web_pid', "$$\n" );
    $manager->_write_web_state( { pid => $$, host => '127.0.0.1', port => 7920, status => 'running' } );
    local *Developer::Dashboard::RuntimeManager::_is_managed_web = sub { return 0 };
    local *Developer::Dashboard::RuntimeManager::_find_web_processes = sub { return () };
    local *Developer::Dashboard::RuntimeManager::_listener_pids_for_port = sub { return () };
    my $state = $manager->running_web;
    is( $state->{pid}, $$, 'running_web trusts the recorded running pid even when listener discovery cannot identify the managed process shape' );
    $manager->_cleanup_web_files;
}

{
    my $listener = fork();
    die "fork failed: $!" if !defined $listener;
    if ( !$listener ) {
        local $SIG{TERM} = 'IGNORE';
        sleep 30;
        POSIX::_exit(0);
    }
    my $calls = 0;
    no warnings 'redefine';
    local *Developer::Dashboard::RuntimeManager::running_web = sub {
        return $calls++ == 0 ? { pid => $listener, port => 7908 } : undef;
    };
    local *Developer::Dashboard::RuntimeManager::_listener_pids_for_port = sub { return ($listener) };
    local *Developer::Dashboard::RuntimeManager::_find_legacy_web_processes = sub { return () };
    local *Developer::Dashboard::RuntimeManager::_pkill_perl = sub { return 1 };
    is( $manager->stop_web, $listener, 'stop_web returns the recorded pid while it also tracks listener pids on the bound port' );
    waitpid( $listener, 0 );
    ok( !kill( 0, $listener ), 'stop_web escalates listener-port pids to KILL when they remain alive after TERM' );
}

{
    no warnings 'redefine';
    local *Developer::Dashboard::RuntimeManager::web_state = sub {
        return {
            pid    => 111_111,
            host   => '127.0.0.1',
            port   => 7917,
            status => 'running',
        };
    };
    local *Developer::Dashboard::RuntimeManager::_listener_pids_for_port = sub {
        my ( undef, $port ) = @_;
        return $port == 7917 ? (333_333) : ();
    };
    local *Developer::Dashboard::RuntimeManager::_read_process_title = sub {
        my ( undef, $pid ) = @_;
        return $pid == 333_333 ? 'starman master' : undef;
    };
    local *Developer::Dashboard::RuntimeManager::_cleanup_web_files = sub { die "_cleanup_web_files should not run while a saved listener still exists\n" };
    my $running = $manager->running_web;
    is( $running->{pid}, 333_333, 'running_web falls back to the saved listener pid when the real listener no longer keeps the dashboard wrapper title' );
    is( $running->{port}, 7917, 'running_web keeps the persisted port when it resolves the live listener from saved state' );
    is( $running->{process_name}, 'starman master', 'running_web records the actual listener process title when using saved-state listener fallback' );
}

{
    my $late_listener = fork();
    die "fork failed: $!" if !defined $late_listener;
    if ( !$late_listener ) {
        local $SIG{TERM} = 'IGNORE';
        sleep 30;
        POSIX::_exit(0);
    }
    my $running_calls  = 0;
    my $listener_calls = 0;
    my $wait_calls     = 0;
    no warnings 'redefine';
    local *Developer::Dashboard::RuntimeManager::running_web = sub {
        return $running_calls++ == 0 ? { pid => $late_listener, port => 7918 } : undef;
    };
    local *Developer::Dashboard::RuntimeManager::_listener_pids_for_port = sub {
        return $listener_calls++ == 0 ? () : ($late_listener);
    };
    local *Developer::Dashboard::RuntimeManager::_find_legacy_web_processes = sub { return () };
    local *Developer::Dashboard::RuntimeManager::_pkill_perl = sub { return 1 };
    local *Developer::Dashboard::RuntimeManager::_wait_for_port_release = sub {
        return $wait_calls++ == 0 ? 0 : 1;
    };
    is( $manager->stop_web, $late_listener, 'stop_web returns the recorded pid when it has to re-probe the bound port for late listeners' );
    waitpid( $late_listener, 0 );
    ok( !kill( 0, $late_listener ), 'stop_web kills late-discovered listener pids after an initial port-release timeout' );
}

{
    my $listener = fork();
    die "fork failed: $!" if !defined $listener;
    if ( !$listener ) {
        local $SIG{TERM} = sub { exit 0 };

t/09-runtime-manager.t  view on Meta::CPAN

    ok( !$manager->_web_runtime_ready( 4321, 7890 ), '_web_runtime_ready returns false on Windows when a listener disappears before the confirmation threshold is reached' );
    is( $sleep_calls, 1, '_web_runtime_ready performs the Windows polling sleep before a later poll observes the vanished listener' );
}

{
    local $Developer::Dashboard::Platform::OS_NAME = 'MSWin32';
    my $sleep_calls = 0;
    no warnings 'redefine';
    local *Developer::Dashboard::RuntimeManager::_listener_pids_for_port = sub { return () };
    local *Developer::Dashboard::RuntimeManager::_port_accepting_connections = sub { return 0 };
    local *Developer::Dashboard::RuntimeManager::_runtime_stability_polls = sub { return 2 };
    local *Developer::Dashboard::RuntimeManager::_runtime_poll_interval = sub { return 0.01 };
    local *Developer::Dashboard::RuntimeManager::sleep = sub { $sleep_calls++; return 0 };
    ok( !$manager->_web_runtime_ready( 4321, 7890 ), '_web_runtime_ready returns false on Windows after exhausting the full startup poll budget with no listener' );
    is( $sleep_calls, 2, '_web_runtime_ready performs each Windows startup poll sleep before timing out without a listener' );
}

{
    local $Developer::Dashboard::Platform::OS_NAME = 'MSWin32';
    ok( !$manager->_web_runtime_matches_pid( { pid => 12 }, 99, undef ), '_web_runtime_matches_pid rejects Windows fallback checks when no listener port is available' );
    ok( $manager->_web_runtime_matches_pid( { pid => 12, port => 7890 }, 99, undef ), '_web_runtime_matches_pid reuses the runtime-reported listener port on Windows when no explicit listener port was supplied' );
    ok( $manager->_web_runtime_matches_pid( { pid => 12, port => 7890 }, 99, 7890 ), '_web_runtime_matches_pid accepts the Windows listener fallback when the running port matches the requested listener port' );
    ok( !$manager->_web_runtime_matches_pid( { pid => 12, port => 7890 }, 99, 7891 ), '_web_runtime_matches_pid rejects Windows fallback checks when the running port does not match the listener port' );
}

{
    local $Developer::Dashboard::Platform::OS_NAME = 'linux';
    ok( !$manager->_web_runtime_matches_pid( { pid => 12, port => 7890 }, 99, 7890 ), '_web_runtime_matches_pid stops before the Windows listener fallback on non-Windows hosts' );
}

{
    local $Developer::Dashboard::Platform::OS_NAME = 'linux';
    my $sleep_calls = 0;
    no warnings 'redefine';
    local *Developer::Dashboard::RuntimeManager::running_web = sub {
        return { pid => 77, port => 7892 };
    };
    local *Developer::Dashboard::RuntimeManager::_listener_pids_for_port = sub {
        my ( undef, $port ) = @_;
        return $port == 7892 ? (77) : ();
    };
    local *Developer::Dashboard::RuntimeManager::_port_accepting_connections = sub { return 0 };
    local *Developer::Dashboard::RuntimeManager::_runtime_stability_polls = sub { return 1 };
    local *Developer::Dashboard::RuntimeManager::_runtime_confirmation_polls = sub { return 1 };
    local *Developer::Dashboard::RuntimeManager::_runtime_poll_interval = sub { return 0.01 };
    local *Developer::Dashboard::RuntimeManager::sleep = sub { $sleep_calls++; return 0 };
    ok( $manager->_web_runtime_ready( 77, undef ), '_web_runtime_ready reuses the runtime-reported listener port when no explicit port argument was provided' );
    is( $sleep_calls, 0, '_web_runtime_ready does not need an extra poll sleep when the runtime-reported listener port is immediately ready' );
}

{
    local $Developer::Dashboard::Platform::OS_NAME = 'MSWin32';
    no warnings 'redefine';
    local *Developer::Dashboard::RuntimeManager::_listener_pids_for_port = sub {
        my ( undef, $port ) = @_;
        return () if $port != 7890;
        return (7123);
    };
    ok(
        $manager->_wait_for_windows_web_shutdown( undef, undef, [$$] ),
        '_wait_for_windows_web_shutdown reports the web runtime alive when a tracked listener pid is still running',
    );
    ok(
        $manager->_wait_for_windows_web_shutdown( undef, 7890, [] ),
        '_wait_for_windows_web_shutdown reports the web runtime alive while the listen port still has an owning pid',
    );
    ok(
        !$manager->_wait_for_windows_web_shutdown( undef, 7891, [] ),
        '_wait_for_windows_web_shutdown reports shutdown complete when there is no saved pid, listener pid, or live port owner',
    );
}

{
    no warnings 'redefine';
    local *Developer::Dashboard::RuntimeManager::stop_web = sub {
        my ( undef, %args ) = @_;
        return 5101;
    };
    my $stopped_web = $manager->stop_target( scope => 'web' );
    is( $stopped_web->{web_pid}, 5101, 'stop_target web scope reports the stopped web pid' );
    is( $stopped_web->{web}{status}, 'stopped', 'stop_target web scope reports stopped status' );

    local *Developer::Dashboard::RuntimeManager::stop_collectors = sub {
        my ( undef, %args ) = @_;
        return (
            { name => 'alpha.collector', pid => 6101, status => 'stopped' },
            { name => 'beta.collector',  pid => 6102, status => 'stopped' },
        );
    };
    my $stopped_collectors = $manager->stop_target( scope => 'collector' );
    is( scalar @{ $stopped_collectors->{collectors} }, 2, 'stop_target collector scope reports all stopped collectors' );
    is( $stopped_collectors->{target}, 'all', 'stop_target collector scope defaults its target label to all' );

    my $stopped_named = $manager->stop_target( scope => 'collector', name => 'beta.collector' );
    is( $stopped_named->{target}, 'beta.collector', 'stop_target collector scope preserves the requested collector name in the target field' );
    local *Developer::Dashboard::RuntimeManager::stop_collectors = sub { return (); };
    my $stopped_empty_collectors = $manager->stop_target( scope => 'collector' );
    is_deeply(
        $stopped_empty_collectors->{collectors},
        [
            {
                name    => 'all',
                status  => 'not running',
                details => 'no running collectors',
            },
        ],
        'stop_target collector scope still reports a summary row when no collectors are running',
    );

    {
        local $Developer::Dashboard::Platform::OS_NAME = 'MSWin32';
        local *Developer::Dashboard::RuntimeManager::stop_collectors = sub {
            return (
                { name => 'alpha.collector', pid => 6201, status => 'stopped' },
                { name => 'beta.collector',  pid => 6202, status => 'stopped' },
            );
        };
        my $windows_stopped_collectors = $manager->stop_target( scope => 'collector' );
        is_deeply(
            $windows_stopped_collectors->{collectors},
            [
                { name => 'alpha.collector', pid => 6201, status => 'stopped' },
                { name => 'beta.collector',  pid => 6202, status => 'stopped'  },
            ],
            'stop_target collector scope reports normal stopped collector rows on Windows',



( run in 0.933 second using v1.01-cache-2.11-cpan-14f38c9f855 )