Developer-Dashboard

 view release on metacpan or  search on metacpan

script/coverage-gate  view on Meta::CPAN

    return @paths;
}

exit main(@ARGV);

# Purpose: find the git checkout root containing a directory.
# Input:   a directory path.
# Output:  the canonical root, or undef when the path is not inside a checkout.
#
# Walks up looking for .git rather than shelling out to git, so the gate gains no
# subprocess and no PATH dependency. It tests for EXISTENCE, not for a directory:
# a linked worktree carries .git as a FILE, and this project works every ticket in
# a worktree, so a -d test would report every sandbox as "not a checkout" and the
# guard below would never fire where it matters most.
sub _checkout_root {
    my ($dir) = @_;
    return undef if !defined $dir || $dir eq '';

    # CANONICALISE FIRST, and this is not tidiness - it is the whole
    # correctness of the comparison below. $repository arrives as
    # "<root>/script/.." because it is built with File::Spec->updir, and
    # rel2abs does NOT collapse "..". Testing -e on "<root>/script/../.git"
    # succeeds, so without abs_path this returns the uncollapsed string, which
    # then compares UNEQUAL to the caller's "<root>" and the gate refuses to
    # run in its own checkout. Measured exactly that way before this line
    # existed.
    my $here = Cwd::abs_path($dir);
    return undef if !defined $here;
    my %seen;
    while ( defined $here && !$seen{$here}++ ) {
        return $here if -e File::Spec->catfile( $here, '.git' );
        # abs_path, NOT rel2abs, and the difference is a hang. rel2abs does not
        # collapse "..", so the walk produced "/tmp/x/..", then "/tmp/x/../..",
        # then "/tmp/x/../../.." - a string that grows for ever and never equals
        # its predecessor, so the loop never terminated and the %seen guard never
        # fired because every value was new. It only bites when NOTHING is found,
        # which is why a checkout resolved instantly and a temp directory hung.
        my $up = Cwd::abs_path( File::Spec->catdir( $here, File::Spec->updir ) );
        last if !defined $up || $up eq $here;
        $here = $up;
    }
    return undef;
}


# Purpose: run the four-metric coverage gate end to end inside one environment.
# Input: the raw @ARGV list - optional --database, --dry-run and --help, plus
#        optional test paths which default to the whole t/ tree.
# Output: an exit code - 0 every metric at 100.0, 1 a genuine shortfall, 2 the
#         gate could not run or could not read its report, 3 the coverage
#         instrument could not read its own database, 4 another gate already
#         holds the database and this run refused rather than corrupt it, 5 the
#         tree moved while the suite ran so the number describes no single state
#         of it.
sub main {
    my @argv = @_;

    my $database = 'cover_db';
    my ( $dry_run, $help ) = ( 0, 0 );

    local @ARGV = @argv;
    GetOptions(
        'database=s' => \$database,
        'dry-run'    => \$dry_run,
        'help'       => \$help,
    ) or return _usage('unrecognized option');

    if ($help) {
        print _usage_text();
        return EXIT_CLEAN;
    }

    my @tests = @ARGV ? @ARGV : ('t');

    my $script_directory = dirname( File::Spec->rel2abs(__FILE__) );
    my $repository       = File::Spec->catdir( $script_directory, File::Spec->updir );
    my $checker          = File::Spec->catfile( $script_directory, 'check-all-metric-coverage' );
    return _unusable("the coverage checker is missing: $checker") if !-f $checker;

    # WHICH TREE IS ABOUT TO BE GRADED - refuse rather than choose silently (DD-744).
    #
    # This gate derives its repository from its OWN file location, which is
    # deliberate: run from anywhere, "grade the repository I belong to" is the
    # sane reading. It stops being sane when the caller is standing in a
    # DIFFERENT checkout, because then the tool makes a choice the caller never
    # saw - it grades another tree, succeeds, and prints a normal-looking result.
    #
    # THE CONDITION IS NARROW ON PURPOSE, and widening it breaks real specs.
    # Refusing whenever the two paths differ would break every spec that copies
    # this gate into a throwaway directory and runs it from the real checkout -
    # t/151-coverage-gate-launch-boundary.t does exactly that six times. A
    # temp directory is not a checkout, so requiring BOTH sides to be checkouts
    # leaves those runs untouched and still catches the case that matters.
    my $caller_checkout = _checkout_root( File::Spec->curdir );
    my $repo_checkout   = _checkout_root($repository);

    # COMPARE CASE-INSENSITIVELY WHERE THE FILESYSTEM IS. On Windows and on a
    # case-tolerant macOS volume, C:\proj and c:\proj are ONE directory, and
    # abs_path does not promise a canonical case - so a plain `ne` would report
    # a divergence that does not exist and refuse the gate in its OWN checkout.
    # That is a false refusal: safe in direction (it declines rather than
    # grading the wrong tree) but it would block every legitimate run there.
    #
    # File::Spec->case_tolerant() is FALSE on Linux, so this leaves the
    # behaviour on this host exactly as the specs already measure it.
    my $diverged =
      File::Spec->case_tolerant()
      ? ( lc($caller_checkout) ne lc($repo_checkout) )
      : ( $caller_checkout ne $repo_checkout );

    if ( defined $caller_checkout && defined $repo_checkout && $diverged ) {
        return _unusable(
                  "this gate would grade $repo_checkout, but you are standing in "
                . "$caller_checkout - two different checkouts. Run that checkout's own "
                . 'script/coverage-gate instead, or pass --database explicitly if you '
                . 'really mean to grade the other tree' );
    }

    chdir $repository or return _unusable("cannot enter the repository root $repository: $!");

    my $instrument = _instrument_module();



( run in 0.598 second using v1.01-cache-2.11-cpan-5c0b1e786e0 )