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 )