Developer-Dashboard
view release on metacpan or search on metacpan
script/check-all-metric-coverage view on Meta::CPAN
#!/usr/bin/env perl
# IMPORTANT: this checker has to tell four different failures apart, because
# responding to one of them as if it were another is what costs whole rounds.
# A coverage SHORTFALL is a fact about the code. An UNREADABLE report is a fact
# about the report. An INSTRUMENT FAILURE is a fact about the environment: the
# coverage database was written by one serializer and is being read by another,
# and re-running produces the identical error. Each gets its own exit status so
# a caller never has to parse prose to know which one it is.
use strict;
use warnings;
use Config;
use File::Spec;
use Getopt::Long qw(GetOptions);
use constant EXIT_CLEAN => 0;
use constant EXIT_SHORTFALL => 1;
use constant EXIT_UNREADABLE => 2;
use constant EXIT_INSTRUMENT => 3;
use constant EXIT_STALE => 4;
# The parse errors Devel::Cover surfaces when the reader's serializer is not the
# one that wrote the database. Neither text names the real cause, which is why
# they are matched here and translated into a verdict that does.
my @INSTRUMENT_SIGNATURES = (
qr/File is not a perl storable/i,
qr/Bad Sereal header/i,
qr/Storable binary image v\d+\.\d+ more recent than I am/i,
qr/Can't load either JSON or Storable/i,
);
exit main(@ARGV);
# Purpose: classify a Devel::Cover text report arriving on standard input.
# Input: the raw @ARGV list; only --database is accepted, naming the coverage
# database to sniff when an instrument failure has to be explained.
# Output: an exit code - 0 all four metrics at 100.0, 1 a genuine shortfall,
# 2 the report could not be read, 3 the instrument could not read its
# own database.
sub main {
my @argv = @_;
my $database = 'cover_db';
local @ARGV = @argv;
GetOptions( 'database=s' => \$database )
or return _unreadable('unrecognized option; only --database is accepted');
return _unreadable( 'unexpected argument: ' . join ' ', @ARGV ) if @ARGV;
my @lines = <STDIN>;
# Echo the report first and flush it, so the verdict written to standard
# error cannot appear above the table it is a verdict about. Standard output
# is block-buffered whenever this runs in a pipeline, which is always.
local $| = 1;
print @lines;
my $signature = _instrument_signature( \@lines );
return _instrument( $signature, $database ) if defined $signature;
return _unreadable('no coverage report arrived on standard input; the report was never produced')
if !grep { /\S/ } @lines;
return _check_report( \@lines );
}
# Purpose: find the first input line that is a serializer parse error rather
# than report content.
# Input: an array reference of report lines.
# Output: the offending line with trailing whitespace removed, or undef.
sub _instrument_signature {
my ($lines) = @_;
for my $line ( @{$lines} ) {
for my $signature (@INSTRUMENT_SIGNATURES) {
next if $line !~ $signature;
my $trimmed = $line;
$trimmed =~ s/\s+\z//;
return $trimmed;
}
}
return undef;
}
# Purpose: report a serializer mismatch as an instrument failure, naming the
# format on disk, the format this reader would choose, and every
# Devel::Cover::DB::IO this process can see.
# Input: the offending reader message and the coverage database directory.
# Output: EXIT_INSTRUMENT, after writing the verdict to standard error.
sub _instrument {
my ( $message, $database ) = @_;
my ( $written, $source ) = _database_format($database);
my $read = _reader_format();
my @paths = _io_module_paths();
print {*STDERR} <<"VERDICT";
coverage gate: INSTRUMENT FAILURE - the coverage database could not be read.
coverage gate: the reader said: $message
coverage gate: written as: $written ($source)
coverage gate: read as: $read (the serializer this process resolves)
coverage gate: Devel/Cover/DB/IO.pm visible here: @{[ join '; ', @paths ]}
coverage gate: Devel::Cover::DB::IO chooses Sereal, then JSON, then Storable at
coverage gate: BEGIN from \@INC, and never records the choice beside the data, so
( run in 1.068 second using v1.01-cache-2.11-cpan-5c0b1e786e0 )