-
8/10/2019 At8326GB Instalation Guide
1/643
my $thing = TAP::Whatever->new();
$thing->callback( event => sub {
# do something interesting } );
=head1 DESCRIPTION
C provides callback management.
=head1 METHODS
=head2 Class Methods
=cut
sub _initialize { my ( $self, $arg_for, $ok_callback ) = @_;
my %ok_map = map { $_ => 1 } @$ok_callback;
$self->{ok_callbacks} = \%ok_map;
if ( my $cb = delete $arg_for->{callbacks} ) { while ( my (
$event, $callback ) = each %$cb ) { $self->callback( $event,
$callback ); } }
return $self;}
=head3 C
Install a callback for a named event.
=cut
sub callback { my ( $self, $event, $callback ) = @_;
my %ok_map = %{ $self->{ok_callbacks} };
$self->_croak('No callbacks may be installed') unless
%ok_map;
$self->_croak( "Callback $event is not supported. Valid
callbacks are " . join( ', ', sort keys %ok_map ) ) unless exists
$ok_map{$event};
push @{ $self->{code_for}{$event} }, $callback;
return;}
sub _has_callbacks { my $self = shift; return keys %{
$self->{code_for} } != 0;}
-
8/10/2019 At8326GB Instalation Guide
2/643
sub _callback_for { my ( $self, $event ) = @_; return
$self->{code_for}{$event};}
sub _make_callback { my $self = shift; my $event = shift;
my $cb = $self->_callback_for($event); return unless defined
$cb; return map { $_->(@_) } @$cb;}
=head3 C
Return the current time using Time::HiRes if available.
=cut
sub get_time { return time() }
=head3 C
Return true if the time returned by get_time is high resolution
(i.e. if Time::HiRes is available).
=cut
sub time_is_hires { return GOT_TIME_HIRES }
1;package TAP::Harness;
use strict;use Carp;
use File::Spec;use File::Path;use IO::Handle;
use TAP::Base;
use vars qw($VERSION @ISA);
@ISA = qw(TAP::Base);
=head1 NAME
TAP::Harness - Run test scripts with statistics
=head1 VERSION
Version 3.17
=cut
$VERSION = '3.17';
-
8/10/2019 At8326GB Instalation Guide
3/643
$ENV{HARNESS_ACTIVE} = 1;$ENV{HARNESS_VERSION} = $VERSION;
END {
# For VMS. delete $ENV{HARNESS_ACTIVE}; delete
$ENV{HARNESS_VERSION};}
=head1 DESCRIPTION
This is a simple test harness which allows tests to be run and
resultsautomatically aggregated and output to STDOUT.
=head1 SYNOPSIS
use TAP::Harness;my $harness = TAP::Harness->new( \%args
);$harness->runtests(@tests);
=cut
my %VALIDATION_FOR;
my @FORMATTER_ARGS;sub _error { my $self = shift; return
$self->{error} unless @_; $self->{error} = shift;}
BEGIN {
@FORMATTER_ARGS = qw( directives verbosity timer failures
comments errors stdout color show_count normalize
);
%VALIDATION_FOR = ( lib => sub { my ( $self, $libs ) = @_;
$libs = [$libs] unless 'ARRAY' eq ref $libs;
return [ map {"-I$_"} @$libs ]; }, switches => sub { shift;
shift }, exec => sub { shift; shift }, merge => sub { shift;
shift }, aggregator_class => sub { shift; shift },
formatter_class => sub { shift; shift }, multiplexer_class
=> sub { shift; shift }, parser_class => sub { shift; shift
}, scheduler_class => sub { shift; shift }, formatter => sub
{ shift; shift }, jobs => sub { shift; shift }, test_args =>
sub { shift; shift }, ignore_exit => sub { shift; shift }, rules
=> sub { shift; shift }, );
-
8/10/2019 At8326GB Instalation Guide
4/643
for my $method ( sort keys %VALIDATION_FOR ) { no strict 'refs';
if ( $method eq 'lib' || $method eq 'switches' ) { *{$method} = sub
{ my $self = shift; unless (@_) { $self->{$method} ||= [];
return wantarray ? @{ $self->{$method} } : $self->{$method};
} $self->_croak("Too many arguments to method '$method'") if @_
> 1; my $args = shift; $args = [$args] unless ref $args;
$self->{$method} = $args; return $self; }; } else { *{$method} =
sub { my $self = shift; return $self->{$method} unless @_;
$self->{$method} = shift; }; } }
for my $method (@FORMATTER_ARGS) { no strict 'refs'; *{$method}
= sub { my $self = shift; return
$self->formatter->$method(@_); }; }}
##############################################################################
=head1 METHODS
=head2 Class Methods
=head3 C
my %args = ( verbosity => 1, lib => [ 'lib', 'blib/lib',
'blib/arch' ],)
my $harness = TAP::Harness->new( \%args );
The constructor returns a new C object. It accepts anoptional
hashref whose allowed keys are:
=over 4
=item * C
Set the verbosity level:
-
8/10/2019 At8326GB Instalation Guide
5/643
1 verbose Print individual test results to STDOUT. 0 normal -1
quiet Suppress some test output (mostly failures
while tests are running). -2 really quiet Suppress everything
but the tests summary. -3 silent Suppress everything.
=item * C
Append run time for each test to output. Uses L ifavailable.
=item * C
Show test failures (this is a no-op if C is selected).
=item * C
Show test comments (this is a no-op if C is selected).
=item * C
Update the running test count during testing.
=item * C
Set to a true value to normalize the TAP that is emitted in
verbose modes.
=item * C
Accepts a scalar value or array ref of scalar values indicating
whichpaths to allowed libraries should be included if Perl tests
areexecuted. Naturally, this only makes sense in the context of
testswritten in Perl.
=item * C
Accepts a scalar value or array ref of scalar values indicating
whichswitches should be included if Perl tests are executed.
Naturally, thisonly makes sense in the context of tests written in
Perl.
=item * C
A reference to an C style array of arguments to be passed to
eachtest program.
=item * C
Attempt to produce color output.
=item * C
Typically, Perl tests are run through this. However, anything
whichspits out TAP is fine. You can use this argument to specify
the name ofthe program (and optional switches) to run your tests
with:
exec => ['/usr/bin/ruby', '-w']
You can also pass a subroutine reference in order to determine
and
-
8/10/2019 At8326GB Instalation Guide
6/643
return the proper program to run based on a given test script.
Thesubroutine reference should expect the TAP::Harness object
itself as thefirst argument, and the file name as the second
argument. It shouldreturn an array reference containing the command
to be run and includingthe test file name. It can also simply
return C, in which caseTAP::Harness will fall back on executing the
test script in Perl:
exec => sub { my ( $harness, $test_file ) = @_;
# Let Perl tests run. return undef if $test_file =~ /[.]t$/;
return [ qw( /usr/bin/ruby -w ), $test_file ] if $test_file =~
/[.]rb$/; }
If the subroutine returns a scalar with a newline or a
filehandle, itwill be interpreted as raw TAP or as a TAP stream,
respectively.
=item * C
If C is true the harness will create parsers that merge
STDOUTand STDERR together for any processes they start.
=item * CThe name of the class to use to aggregate test results.
The default isL.
=item * C
The name of the class to use to format output. The default isL,
or L if the outputisn't a TTY.
=item * C
The name of the class to use to multiplex tests during parallel
testing.The default is L.
=item * C
The name of the class to use to parse TAP. The default isL.
=item * C
The name of the class to use to schedule test execution. The
default isL.
=item * C
If set C must be an object that is capable of formatting theTAP
output. See L for an example.
=item * C
If parse errors are found in the TAP output, a note of this will
bemade in the summary report. To see all of the parse errors, set
thisargument to true:
-
8/10/2019 At8326GB Instalation Guide
7/643
errors => 1
=item * C
If set to a true value, only test results with directives will
bedisplayed. This overrides other settings such as C orC.
=item * C
If set to a true value instruct C to ignore exit and waitstatus
from test scripts.
=item * C
The maximum number of parallel tests to run at any time. Which
testscan be run in parallel is controlled by C. The default is
torun only one test at a time.
=item * C
A reference to a hash of rules that control which tests may
beexecuted in parallel. This is an experimental feature and the
interface may change. $harness->rules( { par => [ { seq
=> '../ext/DB_File/t/*' }, { seq =>
'../ext/IO_Compress_Zlib/t/*' }, { seq => '../lib/CPANPLUS/*' },
{ seq => '../lib/ExtUtils/t/*' }, '*' ] } );
=item * C
A filehandle for catching standard output.
=back
Any keys for which the value is C will be ignored.
=cut
# new supplied by TAP::Base
{
my @legal_callback = qw( parser_args made_parser before_runtests
after_runtests after_test );
my %default_class = ( aggregator_class =>
'TAP::Parser::Aggregator',
-
8/10/2019 At8326GB Instalation Guide
8/643
formatter_class => 'TAP::Formatter::Console',
multiplexer_class => 'TAP::Parser::Multiplexer', parser_class
=> 'TAP::Parser', scheduler_class =>
'TAP::Parser::Scheduler', );
sub _initialize { my ( $self, $arg_for ) = @_; $arg_for ||=
{};
$self->SUPER::_initialize( $arg_for, \@legal_callback ); my
%arg_for = %$arg_for; # force a shallow copy
for my $name ( sort keys %VALIDATION_FOR ) { my $property =
delete $arg_for{$name}; if ( defined $property ) { my $validate =
$VALIDATION_FOR{$name};
my $value = $self->$validate($property); if (
$self->_error ) { $self->_croak; } $self->$name($value);
}
} $self->jobs(1) unless defined $self->jobs;
local $default_class{formatter_class} = 'TAP::Formatter::File'
unless -t ( $arg_for{stdout} || \*STDOUT ) &&
!$ENV{HARNESS_NOTTY};
while ( my ( $attr, $class ) = each %default_class ) {
$self->$attr( $self->$attr() || $class ); }
unless ( $self->formatter ) {
# This is a little bodge to preserve legacy behaviour. It's #
pretty horrible that we know which args are destined for # the
formatter. my %formatter_args = ( jobs => $self->jobs ); for
my $name (@FORMATTER_ARGS) { if ( defined( my $property = delete
$arg_for{$name} ) ) { $formatter_args{$name} = $property; } }
$self->formatter( $self->_construct(
$self->formatter_class, \%formatter_args ) );
}
if ( my @props = sort keys %arg_for ) {
$self->_croak("Unknown arguments to TAP::Harness::new
(@props)"); }
return $self; }}
-
8/10/2019 At8326GB Instalation Guide
9/643
##############################################################################
=head2 Instance Methods
=head3 C
$harness->runtests(@tests);
Accepts and array of C to be run. This should generally be
thenames of test files, but this is not required. Each element in
Cwill be passed to C as a C. SeeL for more information.
It is possible to provide aliases that will be displayed in
place of thetest name by supplying the test as a reference to an
array containingC>:
$harness->runtests( [ 't/foo.t', 'Foo Once' ], [ 't/foo.t',
'Foo Twice' ] );
Normally it is an error to attempt to run the same test twice.
Aliasesallow you to overcome this limitation by giving each run of
the test aunique name.
Tests will be run in the order found.If the environment variable
C is defined itshould name a directory into which a copy of the raw
TAP for each testwill be written. TAP is written to files named for
each test.Subdirectories will be created as needed.
Returns a L containing the test results.
=cut
sub runtests { my ( $self, @tests ) = @_;
my $aggregate = $self->_construct( $self->aggregator_class
);
$self->_make_callback( 'before_runtests', $aggregate );
$aggregate->start; $self->aggregate_tests( $aggregate, @tests
); $aggregate->stop; $self->summary($aggregate);
$self->_make_callback( 'after_runtests', $aggregate );
return $aggregate;}
=head3 C
Output the summary for a TAP::Parser::Aggregator.
=cut
sub summary { my ( $self, $aggregate ) = @_;
$self->formatter->summary($aggregate);}
-
8/10/2019 At8326GB Instalation Guide
10/643
sub _after_test { my ( $self, $aggregate, $job, $parser ) =
@_;
$self->_make_callback( 'after_test', $job->as_array_ref,
$parser ); $aggregate->add( $job->description, $parser
);}
sub _bailout { my ( $self, $result ) = @_; my $explanation =
$result->explanation; die "FAILED--Further testing stopped" . (
$explanation ? ": $explanation\n" : ".\n" );}
sub _aggregate_parallel { my ( $self, $aggregate, $scheduler ) =
@_;
my $jobs = $self->jobs; my $mux = $self->_construct(
$self->multiplexer_class );
RESULT: {
# Keep multiplexer topped up
FILL: while ( $mux->parsers < $jobs ) { my $job =
$scheduler->get_job;
# If we hit a spinner stop filling and start running. last FILL
if !defined $job || $job->is_spinner;
my ( $parser, $session ) = $self->make_parser($job);
$mux->add( $parser, [ $session, $job ] ); }
if ( my ( $parser, $stash, $result ) = $mux->next ) { my (
$session, $job ) = @$stash;
if ( defined $result ) { $session->result($result);
$self->_bailout($result) if $result->is_bailout; } else {
# End of parser. Automatically removed from the mux.
$self->finish_parser( $parser, $session );
$self->_after_test( $aggregate, $job, $parser );
$job->finish; } redo RESULT; }
}
return;}
sub _aggregate_single { my ( $self, $aggregate, $scheduler ) =
@_;
JOB: while ( my $job = $scheduler->get_job ) {
-
8/10/2019 At8326GB Instalation Guide
11/643
next JOB if $job->is_spinner;
my ( $parser, $session ) = $self->make_parser($job);
while ( defined( my $result = $parser->next ) ) {
$session->result($result); if ( $result->is_bailout ) {
# Keep reading until input is exhausted in the hope # of
allowing any pending diagnostics to show up. 1 while
$parser->next; $self->_bailout($result); } }
$self->finish_parser( $parser, $session );
$self->_after_test( $aggregate, $job, $parser );
$job->finish; }
return;}
=head3 C
$harness->aggregate_tests( $aggregate, @tests );
Run the named tests and display a summary of result. Tests will
be runin the order found.
Test results will be added to the supplied L.C may be called
multiple times to run several sets oftests. Multiple C instances
may be used to pass resultsto a single aggregator so that different
parts of a complex test suitemay be run using different C settings.
This is useful, forexample, in the case where some tests should run
in parallel but othersare unsuitable for parallel execution.
my $formatter = TAP::Formatter::Console->new; my $ser_harness
= TAP::Harness->new( { formatter => $formatter } ); my
$par_harness = TAP::Harness->new( { formatter => $formatter,
jobs => 9 } ); my $aggregator =
TAP::Parser::Aggregator->new;
$aggregator->start(); $ser_harness->aggregate_tests(
$aggregator, @ser_tests ); $par_harness->aggregate_tests(
$aggregator, @par_tests );
$aggregator->stop(); $formatter->summary($aggregator);
Note that for simpler testing requirements it will often be
possible toreplace the above code with a single call to C.
Each elements of the @tests array is either
=over
-
8/10/2019 At8326GB Instalation Guide
12/643
=item * the file name of a test script to run
=item * a reference to a [ file name, display name ] array
=back
When you supply a separate display name it becomes possible to
run atest more than once; the display name is effectively the alias
by whichthe test is known inside the harness. The harness doesn't
care if itruns the same script more than once when each invocation
uses adifferent name.
=cut
sub aggregate_tests { my ( $self, $aggregate, @tests ) = @_;
my $jobs = $self->jobs; my $scheduler =
$self->make_scheduler(@tests);
# #12458 local $ENV{HARNESS_IS_VERBOSE} = 1 if
$self->formatter->verbosity > 0;
# Formatter gets only names. $self->formatter->prepare(
map { $_->description } $scheduler->get_all );
if ( $self->jobs > 1 ) { $self->_aggregate_parallel(
$aggregate, $scheduler ); } else { $self->_aggregate_single(
$aggregate, $scheduler ); }
return;}
sub _add_descriptions { my $self = shift;
# Turn unwrapped scalars into anonymous arrays and copy the name
as # the description for tests that have only a name. return map {
@$_ == 1 ? [ $_->[0], $_->[0] ] : $_ } map { 'ARRAY' eq ref
$_ ? $_ : [$_] } @_;}
=head3 C
Called by the harness when it needs to create aL. Override in a
subclass to provide an
alternative scheduler. C is passed the list of teststhat was
passed to C.
=cut
sub make_scheduler { my ( $self, @tests ) = @_; return
$self->_construct( $self->scheduler_class, tests => [
$self->_add_descriptions(@tests) ],
-
8/10/2019 At8326GB Instalation Guide
13/643
rules => $self->rules );}
=head3 C
Gets or sets the number of concurrent test runs the harness
ishandling. By default, this value is 1 -- for parallel testing,
thisshould be set higher.
=cut
##############################################################################
=head1 SUBCLASSING
C is designed to be (mostly) easy to subclass. If youdon't like
how a particular feature functions, just override thedesired
methods.
=head2 Methods
TODO: This is out of date
The following methods are ones you may wish to override if you
want tosubclass C.
=head3 C
$harness->summary( \%args );
C prints the summary report after all tests are run. Theargument
is a hashref with the following keys:
=over 4
=item * C
This is created with Cnew >> and it the time the
testsstarted. You can print a useful summary time, if desired,
with:
$self->output( timestr( timediff( Benchmark->new,
$start_time ), 'nop' ) );
=item * C
This is an array reference of all test names. To get the Lobject
for individual tests:
my $aggregate = $args->{aggregate};
my $tests = $args->{tests};
for my $name ( @$tests ) { my ($parser) =
$aggregate->parsers($test); ... do something with $parser}
This is a bit clunky and will be cleaned up in a later
release.
=back
-
8/10/2019 At8326GB Instalation Guide
14/643
=cut
sub _get_parser_args { my ( $self, $job ) = @_; my $test_prog =
$job->filename; my %args = (); my @switches; @switches =
$self->lib if $self->lib; push @switches =>
$self->switches if $self->switches; $args{switches} =
\@switches; $args{spool} = $self->_open_spool($test_prog);
$args{merge} = $self->merge; $args{ignore_exit} =
$self->ignore_exit;
if ( my $exec = $self->exec ) { $args{exec} = ref $exec eq
'CODE' ? $exec->( $self, $test_prog ) : [ @$exec, $test_prog ];
if ( not defined $args{exec} ) { $args{source} = $test_prog; }
elsif ( ( ref( $args{exec} ) || "" ) ne "ARRAY" ) {
$args{source} = delete $args{exec}; } } else { $args{source} =
$test_prog; }
if ( defined( my $test_args = $self->test_args ) ) {
$args{test_args} = $test_args; }
return \%args;}
=head3 C
Make a new parser and display formatter session. Typically used
and/oroverridden in subclasses.
my ( $parser, $session ) = $harness->make_parser;
=cut
sub make_parser { my ( $self, $job ) = @_;
my $args = $self->_get_parser_args($job);
$self->_make_callback( 'parser_args', $args,
$job->as_array_ref ); my $parser = $self->_construct(
$self->parser_class, $args );
$self->_make_callback( 'made_parser', $parser,
$job->as_array_ref ); my $session =
$self->formatter->open_test( $job->description, $parser
);
return ( $parser, $session );}
-
8/10/2019 At8326GB Instalation Guide
15/643
=head3 C
Terminate use of a parser. Typically used and/or overridden
insubclasses. The parser isn't destroyed as a result of this.
=cut
sub finish_parser { my ( $self, $parser, $session ) = @_;
$session->close_test; $self->_close_spool($parser);
return $parser;}
sub _open_spool { my $self = shift; my $test = shift;
if ( my $spool_dir = $ENV{PERL_TEST_HARNESS_DUMP_TAP} ) {
my $spool = File::Spec->catfile( $spool_dir, $test );
# Make the directory my ( $vol, $dir, undef ) =
File::Spec->splitpath($spool); my $path =
File::Spec->catpath( $vol, $dir, '' ); eval { mkpath($path) };
$self->_croak($@) if $@;
my $spool_handle = IO::Handle->new; open( $spool_handle,
">$spool" ) or $self->_croak(" Can't write $spool ( $! )
");
return $spool_handle; }
return;}
sub _close_spool { my $self = shift; my ($parser) = @_;
if ( my $spool_handle = $parser->delete_spool ) {
close($spool_handle) or $self->_croak(" Error closing TAP spool
file( $! ) \n "); }
return;
}
sub _croak { my ( $self, $message ) = @_; unless ($message) {
$message = $self->_error; }
$self->SUPER::_croak($message);
return;
-
8/10/2019 At8326GB Instalation Guide
16/643
}
=head1 REPLACING
If you like the C utility and L but you want yourown harness,
all you need to do is write one and provide C andC methods. Then
you can use the C utility like so:
prove --harness My::Test::Harness
Note that while C accepts a list of tests (or things to
betested), C has a fairly rich set of arguments. You'll probably
wantto read over this code carefully to see how all of them are
being used.
=head1 SEE ALSO
L
=cut
1;
# vim:ts=4:sw=4:et:stapackage TAP::Object;
use strict;use vars qw($VERSION);
=head1 NAME
TAP::Object - Base class that provides common functionality to
all C modules
=head1 VERSION
Version 3.17
=cut
$VERSION = '3.17';
=head1 SYNOPSIS
package TAP::Whatever;
use strict; use vars qw(@ISA);
use TAP::Object;
@ISA = qw(TAP::Object);
# new() implementation by TAP::Object sub _initialize { my (
$self, @args) = @_; # initialize your object return $self; }
# ... later ...
-
8/10/2019 At8326GB Instalation Guide
17/643
my $obj = TAP::Whatever->new(@args);
=head1 DESCRIPTION
C provides a default constructor and exception model for allC
classes. Exceptions are raised using L.
=head1 METHODS
=head2 Class Methods
=head3 C
Create a new object. Any arguments passed to C will be passed on
to theL method. Returns a new object.
=cut
sub new { my $class = shift; my $self = bless {}, $class; return
$self->_initialize(@_);}
=head2 Instance Methods=head3 C
Initializes a new object. This method is a stub by default, you
should overrideit as appropriate.
I L expects you to return C or raise an exception. SeeL, and
L.
=cut
sub _initialize {
return $_[0];}
=head3 C
Raise an exception using C from L, eg:
$self->_croak( 'why me?', 'aaarrgh!' );
May also be called as a I method.
$class->_croak( 'this works too' );
=cut
sub _croak { my $proto = shift; require Carp; Carp::croak(@_);
return;}
=head3 C
-
8/10/2019 At8326GB Instalation Guide
18/643
Create a new instance of the specified class.
=cut
sub _construct { my ( $self, $class, @args ) = @_;
$self->_croak("Bad module name $class") unless $class =~ /^
\w+ (?: :: \w+ ) *$/x;
unless ( $class->can('new') ) { local $@; eval "require
$class"; $self->_croak("Can't load $class") if $@; }
return $class->new(@args);}
=head3 C
Create simple getter/setters.
__PACKAGE__->mk_methods(@method_names);=cut
sub mk_methods { my ( $class, @methods ) = @_; foreach my
$method_name (@methods) { my $method = "${class}::$method_name"; no
strict 'refs'; *$method = sub { my $self = shift;
$self->{$method_name} = shift if @_; return
$self->{$method_name};
}; }}
1;
package TAP::Parser;
use strict;use vars qw($VERSION @ISA);
use TAP::Base ();use TAP::Parser::Grammar ();
use TAP::Parser::Result ();use TAP::Parser::ResultFactory ();use
TAP::Parser::Source ();use TAP::Parser::Source::Perl ();use
TAP::Parser::Iterator ();use TAP::Parser::IteratorFactory ();
use Carp qw( confess );
=head1 NAME
-
8/10/2019 At8326GB Instalation Guide
19/643
TAP::Parser - Parse L output
=head1 VERSION
Version 3.17
=cut
$VERSION = '3.17';
my $DEFAULT_TAP_VERSION = 12;my $MAX_TAP_VERSION = 13;
$ENV{TAP_VERSION} = $MAX_TAP_VERSION;
END {
# For VMS. delete $ENV{TAP_VERSION};}
BEGIN { # making accessors @ISA = qw(TAP::Base);
__PACKAGE__->mk_methods( qw( _stream _spool exec exit
is_good_plan plan tests_planned tests_run wait version
in_todo start_time end_time skip_all source_class
perl_source_class grammar_class iterator_factory_class
result_factory_class ) );} # done making accessors
=head1 SYNOPSIS
use TAP::Parser;
my $parser = TAP::Parser->new( { source => $source }
);
while ( my $result = $parser->next ) { print
$result->as_string; }
-
8/10/2019 At8326GB Instalation Guide
20/643
=head1 DESCRIPTION
C is designed to produce a proper parse of TAP output. Foran
example of how to run tests through this module, see the
simpleharnesses C.
There's a wiki dedicated to the Test Anything Protocol:
L
It includes the TAP::Parser Cookbook:
L
=head1 METHODS
=head2 Class Methods
=head3 C
my $parser = TAP::Parser->new(\%args);
Returns a new C object.
The arguments should be a hashref with I of the following
keys:=over 4
=item * C
This is the preferred method of passing arguments to the
constructor. Todetermine how to handle the source, the following
steps are taken.
If the source contains a newline, it's assumed to be a string of
raw TAPoutput.
If the source is a reference, it's assumed to be something to
pass to
the L constructor. This is usedinternally and you should not use
it.
Otherwise, the parser does a C check to see if the source
exists. If so,it attempts to execute the source and read the output
as a stream. This is byfar the preferred method of using the
parser.
foreach my $file ( @test_files ) { my $parser =
TAP::Parser->new( { source => $file } ); # do stuff with the
parser}
=item * C
The value should be the complete TAP output.
=item * C
If passed an array reference, will attempt to create the
iterator bypassing a L object toL, using the array reference
strings asthe command arguments to L:
-
8/10/2019 At8326GB Instalation Guide
21/643
exec => [ '/usr/bin/ruby', 't/my_test.rb' ]
Note that C and C are mutually exclusive.
=back
The following keys are optional.
=over 4
=item * C
If present, each callback corresponding to a given result type
will be calledwith the result as the argument if the C method is
used:
my %callbacks = ( test => \&test_callback, plan =>
\&plan_callback, comment => \&comment_callback, bailout
=> \&bailout_callback, unknown =>
\&unknown_callback,);
my $aggregator = TAP::Parser::Aggregator->new;
foreach my $file ( @test_files ) { my $parser =
TAP::Parser->new( { source => $file, callbacks =>
\%callbacks, } ); $parser->run; $aggregator->add( $file,
$parser );}
=item * C
If using a Perl file as a source, optional switches may be
passed which willbe used when invoking the perl executable.
my $parser = TAP::Parser->new( { source => $test_file,
switches => '-Ilib',} );
=item * C
Used in conjunction with the C option to supply a reference toan
C style array of arguments to pass to the test program.
=item * C
If passed a filehandle will write a copy of all parsed TAP to
that handle.
=item * C
If false, STDERR is not captured (though it is 'relayed' to keep
itsomewhat synchronized with STDOUT.)
If true, STDERR and STDOUT are the same filehandle. This may
cause
-
8/10/2019 At8326GB Instalation Guide
22/643
breakage if STDERR contains anything resembling TAP format, but
doesallow exact synchronization.
Subtleties of this behavior may be platform-dependent and may
change inthe future.
=item * C
This option was introduced to let you easily customize which I
classthe parser should use. It defaults to L.
See also L.
=item * C
This option was introduced to let you easily customize which
Iclass the parser should use. It defaults to L.
See also L.
=item * C
This option was introduced to let you easily customize which I
classthe parser should use. It defaults to L.
See also L.
=item * C
This option was introduced to let you easily customize which
Ifactory class the parser should use. It defaults toL.
See also L.
=item * C
This option was introduced to let you easily customize which
Ifactory class the parser should use. It defaults toL.
See also L.
=back
=cut
# new() implementation supplied by TAP::Base
# This should make overriding behaviour of the Parser in
subclasses easier:
sub _default_source_class {'TAP::Parser::Source'}sub
_default_perl_source_class {'TAP::Parser::Source::Perl'}sub
_default_grammar_class {'TAP::Parser::Grammar'}sub
_default_iterator_factory_class {'TAP::Parser::IteratorFactory'}sub
_default_result_factory_class {'TAP::Parser::ResultFactory'}
##############################################################################
=head2 Instance Methods
-
8/10/2019 At8326GB Instalation Guide
23/643
=head3 C
my $parser = TAP::Parser->new( { source => $file } );
while ( my $result = $parser->next ) { print
$result->as_string, "\n"; }
This method returns the results of the parsing, one result at a
time. Notethat it is destructive. You can't rewind and examine
previous results.
If callbacks are used, they will be issued before this call
returns.
Each result returned is a subclass of L. See thatmodule and
related classes for more information on how to use them.
=cut
sub next { my $self = shift; return ( $self->{_iter} ||=
$self->_iter )->();}
##############################################################################
=head3 C $parser->run;
This method merely runs the parser and parses all of the
TAP.
=cut
sub run { my $self = shift; while ( defined( my $result =
$self->next ) ) {
# do nothing
}}
##############################################################################
=head3 C
Make a new L object and return it. Passes through anyarguments
given.
The C can be customized, as described in L.
=head3 C
Make a new L object and return it. Passes throughany arguments
given.
The C can be customized, as described in L.
=head3 C
Make a new L object and return it. Passes through anyarguments
given.
-
8/10/2019 At8326GB Instalation Guide
24/643
The C can be customized, as described in L.
=head3 C
Make a new L object using the parser'sL, and return it. Passes
through any argumentsgiven.
The C can be customized, as described in L.
=head3 C
Make a new L object using the parser'sL, and return it. Passes
through any argumentsgiven.
The C can be customized, as described in L.
=cut
# This should make overriding behaviour of the Parser in
subclasses easier:sub make_source {
shift->source_class->new(@_); }sub make_perl_source {
shift->perl_source_class->new(@_); }
sub make_grammar { shift->grammar_class->new(@_); }sub
make_iterator {
shift->iterator_factory_class->make_iterator(@_); }sub
make_result { shift->result_factory_class->make_result(@_);
}
sub _iterator_for_source { my ( $self, $source ) = @_;
# If the source has a get_stream method then use it. This makes
it # possible to pass a pre-existing source object to the parser's
# constructor. if ( UNIVERSAL::can( $source, 'can' ) &&
$source->can('get_stream') ) { return
$source->get_stream($self); }
else { return
$self->iterator_factory_class->make_iterator($source); }}
{
# of the following, anything beginning with an underscore is
strictly # internal and should not be exposed. my %initialize = (
version => $DEFAULT_TAP_VERSION, plan => '', # the test plan
(e.g., 1..3) tap => '', # the TAP
tests_run => 0, # actual current test numbers results =>
[], # TAP parser results skipped => [], # todo => [], #
passed => [], # failed => [], # actual_failed => [], # how
many tests really failed actual_passed => [], # how many tests
really passed todo_passed => [], # tests which unexpectedly
succeed parse_errors => [], # perfect TAP should have none
-
8/10/2019 At8326GB Instalation Guide
25/643
);
# We seem to have this list hanging around all over the place.
We could # probably get it from somewhere else to avoid the
repetition. my @legal_callback = qw( test version plan comment
bailout unknown yaml ALL ELSE EOF );
my @class_overrides = qw( source_class perl_source_class
grammar_class iterator_factory_class result_factory_class );
sub _initialize { my ( $self, $arg_for ) = @_;
# everything here is basically designed to convert any TAP
source to a # stream.
# Shallow copy my %args = %{ $arg_for || {} };
$self->SUPER::_initialize( \%args, \@legal_callback );
# get any class overrides out first:
for my $key (@class_overrides) { my $default_method =
"_default_$key"; my $val = delete $args{$key} ||
$self->$default_method(); $self->$key($val); }
my $stream = delete $args{stream}; my $tap = delete $args{tap};
my $source = delete $args{source}; my $exec = delete $args{exec};
my $merge = delete $args{merge}; my $spool = delete $args{spool};
my $switches = delete $args{switches};
my $ignore_exit = delete $args{ignore_exit}; my @test_args = @{
delete $args{test_args} || [] };
if ( 1 < grep {defined} $stream, $tap, $source, $exec ) {
$self->_croak( "You may only choose one of 'exec', 'stream',
'tap' or 'source'" ); }
if ( my @excess = sort keys %args ) {
-
8/10/2019 At8326GB Instalation Guide
26/643
$self->_croak("Unknown options: @excess"); }
if ($tap) { $stream = $self->_iterator_for_source( [ split
"\n" => $tap ] ); } elsif ($exec) { my $source =
$self->make_source; $source->source( [ @$exec, @test_args ]
); $source->merge($merge); # XXX should just be arguments?
$stream = $source->get_stream($self); } elsif ($source) { if (
$source =~ /\n/ ) { $stream = $self->_iterator_for_source( [
split "\n" => $source ] ); } elsif ( ref $source ) { $stream =
$self->_iterator_for_source($source); } elsif ( -e $source ) {
my $perl = $self->make_perl_source;
$perl->switches($switches)
if $switches; $perl->merge($merge); # XXX args to new()?
$perl->source( [ $source, @test_args ] ); $stream =
$perl->get_stream($self); } else { $self->_croak("Cannot
determine source for $source"); } }
unless ($stream) { $self->_croak('PANIC: could not determine
stream');
}
while ( my ( $k, $v ) = each %initialize ) { $self->{$k} =
'ARRAY' eq ref $v ? [] : $v; }
$self->_stream($stream); $self->_spool($spool);
$self->ignore_exit($ignore_exit);
return $self; }}
=head1 INDIVIDUAL RESULTS
If you've read this far in the docs, you've seen this:
while ( my $result = $parser->next ) { print
$result->as_string; }
Each result returned is a L subclass, referred to as
-
8/10/2019 At8326GB Instalation Guide
27/643
I.
=head2 Result types
Basically, you fetch individual results from the TAP. The six
types, withexamples of each, are as follows:
=over 4
=item * Version
TAP version 12
=item * Plan
1..42
=item * Pragma
pragma +strict
=item * Test
ok 3 - We should start with some foobar!
=item * Comment
# Hope we don't use up the foobar.
=item * Bailout
Bail out! We ran out of foobar!
=item * Unknown
... yo, this ain't TAP! ...
=back
Each result fetched is a result object of a different type.
There are commonmethods to each result object and different types
may have methods unique totheir type. Sometimes a type method may
be overridden in a subclass, but itsuse is guaranteed to be
identical.
=head2 Common type methods
=head3 C
Returns the type of result, such as C or C.
=head3 C
Prints a string representation of the token. This might not be
the exactoutput, however. Tests will have test numbers added if not
present, TODO andSKIP directives will be capitalized and, in
general, things will be cleanedup. If you need the original text
for the token, see the C method.
=head3 C
Returns the original line of text which was parsed.
-
8/10/2019 At8326GB Instalation Guide
28/643
=head3 C
Indicates whether or not this is the test plan line.
=head3 C
Indicates whether or not this is a test line.
=head3 C
Indicates whether or not this is a comment. Comments will
generally onlyappear in the TAP stream if STDERR is merged to
STDOUT. See theC option.
=head3 C
Indicates whether or not this is bailout line.
=head3 C
Indicates whether or not the current item is a YAML block.
=head3 C
Indicates whether or not the current line could be parsed.
=head3 C
if ( $result->is_ok ) { ... }
Reports whether or not a given result has passed. Anything which
is B atest result returns true. This is merely provided as a
convenient shortcutwhich allows you to do this:
my $parser = TAP::Parser->new( { source => $source }
);while ( my $result = $parser->next ) {
# only print failing results print $result->as_string unless
$result->is_ok;}
=head2 C methods
if ( $result->is_plan ) { ... }
If the above evaluates as true, the following methods will be
available on theC object.
=head3 C
if ( $result->is_plan ) { print $result->plan; }
This is merely a synonym for C.
=head3 C
my $directive = $result->directive;
-
8/10/2019 At8326GB Instalation Guide
29/643
If a SKIP directive is included with the plan, this method will
return it.
1..0 # SKIP: why bother?
=head3 C
my $explanation = $result->explanation;
If a SKIP directive was included with the plan, this method will
return theexplanation, if any.
=head2 C methods
if ( $result->is_pragma ) { ... }
If the above evaluates as true, the following methods will be
available on theC object.
=head3 C
Returns a list of pragmas each of which is a + or - followed by
thepragma name.=head2 C methods
if ( $result->is_comment ) { ... }
If the above evaluates as true, the following methods will be
available on theC object.
=head3 C
if ( $result->is_comment ) { my $comment =
$result->comment; print "I have something to say: $comment";
}
=head2 C methods
if ( $result->is_bailout ) { ... }
If the above evaluates as true, the following methods will be
available on theC object.
=head3 C
if ( $result->is_bailout ) { my $explanation =
$result->explanation; print "We bailed out because
($explanation)"; }
If, and only if, a token is a bailout token, you can get an
"explanation" viathis method. The explanation is the text after the
mystical "Bail out!" wordswhich appear in the tap output.
=head2 C methods
if ( $result->is_unknown ) { ... }
There are no unique methods for unknown results.
-
8/10/2019 At8326GB Instalation Guide
30/643
=head2 C methods
if ( $result->is_test ) { ... }
If the above evaluates as true, the following methods will be
available on theC object.
=head3 C
my $ok = $result->ok;
Returns the literal text of the C or C status.
=head3 C
my $test_number = $result->number;
Returns the number of the test, even if the original TAP output
did not supplythat number.
=head3 C
my $description = $result->description;
Returns the description of the test, if any. This is the portion
after thetest number but before the directive.
=head3 C
my $directive = $result->directive;
Returns either C or C if either directive was present for a
testline.
=head3 C
my $explanation = $result->explanation;
If a test had either a C or C directive, this method will
returnthe accompanying explantion, if present.
not ok 17 - 'Pigs can fly' # TODO not enough acid
For the above line, the explanation is I.
=head3 C
if ( $result->is_ok ) { ... }
Returns a boolean value indicating whether or not the test
passed. Rememberthat for TODO tests, the test always passes.
B this was formerly C. The latter method is deprecated andwill
issue a warning.
=head3 C
if ( $result->is_actual_ok ) { ... }
-
8/10/2019 At8326GB Instalation Guide
31/643
Returns a boolean value indicating whether or not the test
passed, regardlessof its TODO status.
B this was formerly C. The latter method is deprecatedand will
issue a warning.
=head3 C
if ( $test->is_unplanned ) { ... }
If a test number is greater than the number of planned tests,
this method willreturn true. Unplanned tests will I return false
for C,regardless of whether or not the test C (seeL for more
information about this).
=head3 C
if ( $result->has_skip ) { ... }
Returns a boolean value indicating whether or not this test had
a SKIPdirective.
=head3 C
if ( $result->has_todo ) { ... }Returns a boolean value
indicating whether or not this test had a TODOdirective.
Note that TODO tests I pass. If you need to know whether or
notthey really passed, check the C method.
=head3 C
if ( $parser->in_todo ) { ... }
True while the most recent result was a TODO. Becomes true
before the
TODO result is returned and stays true until just before the
next non-TODO test is returned.
=head1 TOTAL RESULTS
After parsing the TAP, there are many methods available to let
you dig throughthe results and determine what is meaningful to
you.
=head2 Individual Results
These results refer to individual tests which are run.
=head3 C
my @passed = $parser->passed; # the test numbers which
passedmy $passed = $parser->passed; # the number of tests which
passed
This method lets you know which (or how many) tests passed. If a
test failedbut had a TODO directive, it will be counted as a passed
test.
=cut
sub passed { @{ shift->{passed} } }
-
8/10/2019 At8326GB Instalation Guide
32/643
=head3 C
my @failed = $parser->failed; # the test numbers which
failedmy $failed = $parser->failed; # the number of tests which
failed
This method lets you know which (or how many) tests failed. If a
test passedbut had a TODO directive, it will B be counted as a
failed test.
=cut
sub failed { @{ shift->{failed} } }
=head3 C
# the test numbers which actually passedmy @actual_passed =
$parser->actual_passed;
# the number of tests which actually passedmy $actual_passed =
$parser->actual_passed;
This method lets you know which (or how many) tests actually
passed,regardless of whether or not a TODO directive was found.
=cutsub actual_passed { @{ shift->{actual_passed} }
}*actual_ok = \&actual_passed;
=head3 C
This method is a synonym for C.
=head3 C
# the test numbers which actually failedmy @actual_failed =
$parser->actual_failed;
# the number of tests which actually failedmy $actual_failed =
$parser->actual_failed;
This method lets you know which (or how many) tests actually
failed,regardless of whether or not a TODO directive was found.
=cut
sub actual_failed { @{ shift->{actual_failed} } }
##############################################################################
=head3 C
my @todo = $parser->todo; # the test numbers with todo
directivesmy $todo = $parser->todo; # the number of tests with
todo directives
This method lets you know which (or how many) tests had TODO
directives.
=cut
sub todo { @{ shift->{todo} } }
-
8/10/2019 At8326GB Instalation Guide
33/643
=head3 C
# the test numbers which unexpectedly succeededmy @todo_passed =
$parser->todo_passed;
# the number of tests which unexpectedly succeededmy
$todo_passed = $parser->todo_passed;
This method lets you know which (or how many) tests actually
passed but weredeclared as "TODO" tests.
=cut
sub todo_passed { @{ shift->{todo_passed} } }
##############################################################################
=head3 C
# deprecated in favor of 'todo_passed'. This method was horribly
misnamed.
This was a badly misnamed method. It indicates which TODO tests
unexpectedlysucceeded. Will now issue a warning and call C.
=cut
sub todo_failed { warn '"todo_failed" is deprecated. Please use
"todo_passed". See the docs.'; goto &todo_passed;}
=head3 C
my @skipped = $parser->skipped; # the test numbers with SKIP
directivesmy $skipped = $parser->skipped; # the number of tests
with SKIP directives
This method lets you know which (or how many) tests had SKIP
directives.
=cut
sub skipped { @{ shift->{skipped} } }
=head2 Pragmas
=head3 C
Get or set a pragma. To get the state of a pragma:
if ( $p->pragma('strict') ) { # be strict }
To set the state of a pragma:
$p->pragma('strict', 1); # enable strict mode
=cut
-
8/10/2019 At8326GB Instalation Guide
34/643
sub pragma { my ( $self, $pragma ) = splice @_, 0, 2;
return $self->{pragma}->{$pragma} unless @_;
if ( my $state = shift ) { $self->{pragma}->{$pragma} = 1;
} else { delete $self->{pragma}->{$pragma}; }
return;}
=head3 C
Get a list of all the currently enabled pragmas:
my @pragmas_enabled = $p->pragmas;
=cut
sub pragmas { sort keys %{ shift->{pragma} || {} } }
=head2 Summary Results
These results are "meta" information about the total results of
an individualtest program.
=head3 C
my $plan = $parser->plan;
Returns the test plan, if found.
=head3 C
Deprecated. Use C instead.
=cut
sub good_plan { warn 'good_plan() is deprecated. Please use
"is_good_plan()"'; goto &is_good_plan;}
##############################################################################
=head3 C
if ( $parser->is_good_plan ) { ... }
Returns a boolean value indicating whether or not the number of
tests plannedmatches the number of tests run.
B this was formerly C. The latter method is deprecated andwill
issue a warning.
And since we're on that subject ...
-
8/10/2019 At8326GB Instalation Guide
35/643
=head3 C
print $parser->tests_planned;
Returns the number of tests planned, according to the plan. For
example, aplan of '1..17' will mean that 17 tests were planned.
=head3 C
print $parser->tests_run;
Returns the number of tests which actually were run. Hopefully
this willmatch the number of Ctests_planned >>.
=head3 C
Returns a true value (actually the reason for skipping) if all
testswere skipped.
=head3 C
Returns the time when the Parser was created.
=head3 CReturns the time when the end of TAP input was seen.
=head3 C
if ( $parser->has_problems ) { ... }
This is a 'catch-all' method which returns true if any tests
have currentlyfailed, any TODO tests unexpectedly succeeded, or any
parse errors occurred.
=cut
sub has_problems { my $self = shift; return $self->failed ||
$self->parse_errors || ( !$self->ignore_exit && (
$self->wait || $self->exit ) );}
=head3 C
$parser->version;
Once the parser is done, this will return the version number for
theparsed TAP. Version numbers were introduced with TAP version 13
so if noversion number is found version 12 is assumed.
=head3 C
$parser->exit;
Once the parser is done, this will return the exit status. If
the parser ran
-
8/10/2019 At8326GB Instalation Guide
36/643
an executable, it returns the exit status of the executable.
=head3 C
$parser->wait;
Once the parser is done, this will return the wait status. If
the parser ranan executable, it returns the wait status of the
executable. Otherwise, thismererely returns the C status.
=head2 C
$parser->ignore_exit(1);
Tell the parser to ignore the exit status from the test when
determiningwhether the test passed. Normally tests with non-zero
exit status areconsidered to have failed even if all individual
tests passed. In caseswhere it is not possible to control the exit
value of the test scriptuse this option to ignore it.
=cut
sub ignore_exit { shift->pragma( 'ignore_exit', @_ ) }
=head3 Cmy @errors = $parser->parse_errors; # the parser
errorsmy $errors = $parser->parse_errors; # the number of
parser_errors
Fortunately, all TAP output is perfect. In the event that it is
not, thismethod will return parser errors. Note that a junk line
which the parser doesnot recognize is C an error. This allows this
parser to handle futureversions of TAP. The following are all TAP
errors reported by the parser:
=over 4
=item * Misplaced plan
The plan (for example, '1..5'), must only come at the beginning
or end of theTAP output.
=item * No plan
Gotta have a plan!
=item * More than one plan
1..3ok 1 - input file openednot ok 2 - first line of the input
valid # todo some data
ok 3 read the rest of the file1..3
Right. Very funny. Don't do that.
=item * Test numbers out of sequence
1..3ok 1 - input file openednot ok 2 - first line of the input
valid # todo some data
-
8/10/2019 At8326GB Instalation Guide
37/643
ok 2 read the rest of the file
That last test line above should have the number '3' instead of
'2'.
Note that it's perfectly acceptable for some lines to have test
numbers andothers to not have them. However, when a test number is
found, it must be insequence. The following is also an error:
1..3ok 1 - input file openednot ok - first line of the input
valid # todo some dataok 2 read the rest of the file
But this is not:
1..3ok - input file openednot ok - first line of the input valid
# todo some dataok 3 read the rest of the file
=back
=cut
sub parse_errors { @{ shift->{parse_errors} } }sub _add_error
{ my ( $self, $error ) = @_; push @{ $self->{parse_errors} }
=> $error; return $self;}
sub _make_state_table { my $self = shift; my %states; my
%planned_todo = ();
# These transitions are defaults for all states my
%state_globals = ( comment => {}, bailout => {}, yaml =>
{}, version => { act => sub { $self->_add_error( 'If TAP
version is present it must be the first line of output' ); },
},
unknown => { act => sub { my $unk = shift; if (
$self->pragma('strict') ) { $self->_add_error( 'Unknown TAP
token: "' . $unk->raw . '"' ); } }, }, pragma => {
-
8/10/2019 At8326GB Instalation Guide
38/643
act => sub { my ($pragma) = @_; for my $pr (
$pragma->pragmas ) { if ( $pr =~ /^ ([-+])(\w+) $/x ) {
$self->pragma( $2, $1 eq '+' ); } } }, }, );
# Provides default elements for transitions my %state_defaults =
( plan => { act => sub { my ($plan) = @_;
$self->tests_planned( $plan->tests_planned ); $self->plan(
$plan->plan ); if ( $plan->has_skip ) { $self->skip_all(
$plan->explanation || '(no reason given)' ); }
$planned_todo{$_}++ for @{ $plan->todo_list };
}, }, test => { act => sub { my ($test) = @_;
my ( $number, $tests_run ) = ( $test->number,
++$self->{tests_run} );
# Fake TODO state if ( defined $number && delete
$planned_todo{$number} ) { $test->set_directive('TODO'); }
my $has_todo = $test->has_todo;
$self->in_todo($has_todo); if ( defined( my $tests_planned =
$self->tests_planned ) ) { if ( $tests_run > $tests_planned )
{ $test->is_unplanned(1); } }
if ( defined $number ) { if ( $number != $tests_run ) { my
$count = $tests_run;
$self->_add_error( "Tests out of sequence. Found " .
"($number) but expected ($count)" ); } } else { $test->_number(
$number = $tests_run ); }
push @{ $self->{todo} } => $number if $has_todo; push @{
$self->{todo_passed} } => $number
-
8/10/2019 At8326GB Instalation Guide
39/643
if $test->todo_passed; push @{ $self->{skipped} } =>
$number if $test->has_skip;
push @{ $self->{ $test->is_ok ? 'passed' : 'failed' } }
=> $number; push @{ $self->{ $test->is_actual_ok ?
'actual_passed' : 'actual_failed' } } => $number; }, }, yaml
=> { act => sub { }, }, );
# Each state contains a hash the keys of which match a token
type. For # each token # type there may be: # act A coderef to run
# goto The new state to move to. Stay in this state if #
missing
# continue Goto the new state and run the new state for the #
current token %states = ( INIT => { version => { act =>
sub { my ($version) = @_; my $ver_num = $version->version; if (
$ver_num _add_error( "Explicit TAP version must be at least " .
"$ver_min. Got version $ver_num" );
$ver_num = $DEFAULT_TAP_VERSION; } if ( $ver_num >
$MAX_TAP_VERSION ) { $self->_add_error( "TAP specified version
$ver_num but " . "we don't know about versions later " . "than
$MAX_TAP_VERSION" ); $ver_num = $MAX_TAP_VERSION; }
$self->version($ver_num);
$self->_grammar->set_version($ver_num); }, goto =>
'PLAN'
}, plan => { goto => 'PLANNED' }, test => { goto =>
'UNPLANNED' }, }, PLAN => { plan => { goto => 'PLANNED' },
test => { goto => 'UNPLANNED' }, }, PLANNED => { test
=> { goto => 'PLANNED_AFTER_TEST' },
-
8/10/2019 At8326GB Instalation Guide
40/643
plan => { act => sub { my ($version) = @_;
$self->_add_error( 'More than one plan found in TAP output'); },
}, }, PLANNED_AFTER_TEST => { test => { goto =>
'PLANNED_AFTER_TEST' }, plan => { act => sub { }, continue
=> 'PLANNED' }, yaml => { goto => 'PLANNED' }, }, GOT_PLAN
=> { test => { act => sub { my ($plan) = @_; my $line =
$self->plan; $self->_add_error( "Plan ($line) must be at the
beginning " . "or end of the TAP output" );
$self->is_good_plan(0); }, continue => 'PLANNED'
}, plan => { continue => 'PLANNED' }, }, UNPLANNED => {
test => { goto => 'UNPLANNED_AFTER_TEST' }, plan => { goto
=> 'GOT_PLAN' }, }, UNPLANNED_AFTER_TEST => { test => {
act => sub { }, continue => 'UNPLANNED' }, plan => { act
=> sub { }, continue => 'UNPLANNED' }, yaml => { goto
=> 'PLANNED' }, }, );
# Apply globals and defaults to state table for my $name ( keys
%states ) {
# Merge with globals my $st = { %state_globals, %{
$states{$name} } };
# Add defaults for my $next ( sort keys %{$st} ) { if ( my
$default = $state_defaults{$next} ) { for my $def ( sort keys
%{$default} ) { $st->{$next}->{$def} ||= $default->{$def};
}
} }
# Stuff back in table $states{$name} = $st; }
return \%states;}
-
8/10/2019 At8326GB Instalation Guide
41/643
=head3 C
Get an a list of file handles which can be passed to C
todetermine the readiness of this parser.
=cut
sub get_select_handles {
shift->_stream->get_select_handles }
sub _grammar { my $self = shift; return $self->{_grammar} =
shift if @_;
return $self->{_grammar} ||= $self->make_grammar( { stream
=> $self->_stream, parser => $self, version =>
$self->version } );}
sub _iter { my $self = shift; my $stream =
$self->_stream;
my $grammar = $self->_grammar; my $spool = $self->_spool;
my $state = 'INIT'; my $state_table =
$self->_make_state_table;
$self->start_time( $self->get_time );
# Make next_state closure my $next_state = sub { my $token =
shift; my $type = $token->type; TRANS: { my $state_spec =
$state_table->{$state}
or die "Illegal state: $state";
if ( my $next = $state_spec->{$type} ) { if ( my $act =
$next->{act} ) { $act->($token); } if ( my $cont =
$next->{continue} ) { $state = $cont; redo TRANS; } elsif ( my
$goto = $next->{goto} ) { $state = $goto; }
} else { confess("Unhandled token type: $type\n"); } } return
$token; };
# Handle end of stream - which means either pop a block or
finish my $end_handler = sub {
-
8/10/2019 At8326GB Instalation Guide
42/643
$self->exit( $stream->exit ); $self->wait(
$stream->wait ); $self->_finish; return; };
# Finally make the closure that we return. For performance
reasons # there are two versions of the returned function: one that
handles # callbacks and one that does not. if (
$self->_has_callbacks ) { return sub { my $result = eval {
$grammar->tokenize }; $self->_add_error($@) if $@;
if ( defined $result ) { $result =
$next_state->($result);
if ( my $code = $self->_callback_for( $result->type ) ) {
$_->($result) for @{$code}; } else { $self->_make_callback(
'ELSE', $result ); }
$self->_make_callback( 'ALL', $result ); # Echo TAP to spool
file print {$spool} $result->raw, "\n" if $spool; } else {
$result = $end_handler->(); $self->_make_callback( 'EOF',
$self ) unless defined $result; }
return $result; };
} # _has_callbacks else { return sub { my $result = eval {
$grammar->tokenize }; $self->_add_error($@) if $@;
if ( defined $result ) { $result =
$next_state->($result);
# Echo TAP to spool file print {$spool} $result->raw, "\n" if
$spool; } else {
$result = $end_handler->(); }
return $result; }; } # no callbacks}
sub _finish { my $self = shift;
-
8/10/2019 At8326GB Instalation Guide
43/643
$self->end_time( $self->get_time );
# Avoid leaks $self->_stream(undef);
$self->_grammar(undef);
# If we just delete the iter we won't get a fault if it's
recreated. # Instead we set it to a sub that returns an infinite #
stream of undef. This segfaults on 5.5.4, presumably because #
we're still executing the closure that gets replaced and it hasn't
# been protected with a refcount. $self->{_iter} = sub {return}
if $] >= 5.006;
# sanity checks if ( !$self->plan ) {
$self->_add_error('No plan found in TAP output'); } else {
$self->is_good_plan(1) unless defined $self->is_good_plan; }
if ( $self->tests_run != ( $self->tests_planned || 0 ) ) {
$self->is_good_plan(0);
if ( defined( my $planned = $self->tests_planned ) ) { my
$ran = $self->tests_run; $self->_add_error( "Bad plan. You
planned $planned tests but ran $ran."); } } if (
$self->tests_run != ( $self->passed + $self->failed ) )
{
# this should never happen my $actual = $self->tests_run; my
$passed = $self->passed; my $failed = $self->failed;
$self->_croak( "Panic: planned test count ($actual) did not
equal "
. "sum of passed ($passed) and failed ($failed) tests!" ); }
$self->is_good_plan(0) unless defined $self->is_good_plan;
return $self;}
=head3 C
Delete and return the spool.
my $fh = $parser->delete_spool;
=cut
sub delete_spool { my $self = shift;
return delete $self->{_spool};}
##############################################################################
-
8/10/2019 At8326GB Instalation Guide
44/643
-
8/10/2019 At8326GB Instalation Guide
45/643
Invoked if Cis_yaml >> returns true.
=item * C
Invoked if Cis_unknown >> returns true.
=item * C
If a result does not have a callback defined for it, this
callback willbe invoked. Thus, if all of the previous result types
are specified ascallbacks, this callback will I be invoked.
=item * C
This callback will always be invoked and this will happen for
eachresult after one of the above callbacks is invoked. For
example, ifL is loaded, you could use the following to color
yourtest output:
my %callbacks = ( test => sub { my $test = shift; if (
$test->is_ok && not $test->directive ) { # normal
passing test
print color 'green'; } elsif ( !$test->is_ok ) { # even if
it's TODO print color 'white on_red'; } elsif ( $test->has_skip
) { print color 'white on_blue';
} elsif ( $test->has_todo ) { print color 'white'; } },
ELSE => sub { # plan, comment, and so on (anything which
isn't a test line) print color 'black on_white'; }, ALL => sub {
# now print them print shift->as_string; print color 'reset';
print "\n"; },);
=item * C
Invoked when there are no more lines to be parsed. Since there
is noaccompanying L object the C object ispassed instead.
=back
=head1 TAP GRAMMAR
If you're looking for an EBNF grammar, see L.
-
8/10/2019 At8326GB Instalation Guide
46/643
=head1 BACKWARDS COMPATABILITY
The Perl-QA list attempted to ensure backwards compatability
withL. However, there are some minor differences.
=head2 Differences
=over 4
=item * TODO plans
A little-known feature of L is that it supported TODOlists in
the plan:
1..2 todo 2ok 1 - We have liftoffnot ok 2 - Anti-gravity device
activated
Under L, test number 2 would I because it waslisted as a TODO
test on the plan line. However, we are not aware ofanyone actually
using this feature and hard-coding test numbers isdiscouraged
because it's very easy to add a test and break the testnumber
sequence. This makes test suites very fragile. Instead, the
following should be used:1..2ok 1 - We have liftoffnot ok 2 -
Anti-gravity device activated # TODO
=item * 'Missing' tests
It rarely happens, but sometimes a harness might
encounter'missing tests:
ok 1ok 2
ok 15ok 16ok 17
L would report tests 3-14 as having failed. For theC, these
tests are not considered failed because they'venever run. They're
reported as parse failures (tests out of sequence).
=back
=head1 SUBCLASSING
If you find you need to provide custom functionality (as you
would have using
L), you're in luck: C and friends aredesigned to be easily
subclassed.
Before you start, it's important to know a few things:
=over 2
=item 1
All C objects inherit from L.
-
8/10/2019 At8326GB Instalation Guide
47/643
=item 2
Most C classes have a I section to guide you.
=item 3
Note that C is designed to be the central 'maker' - ie: it
isresponsible for creating new objects in the C namespace.
This makes it possible for you to have a single point of
configuring whatsubclasses should be used, which in turn means that
in many cases you'll findyou only need to sub-class one of the
parser's components.
=item 4
By subclassing, you may end up overriding undocumented methods.
That's nota bad thing per se, but be forewarned that undocumented
methods may changewithout warning from one release to the next - we
cannot guarantee backwardscompatability. If any I method needs
changing, it will bedeprecated first, and changed in a later
release.
=back
=head2 Parser Components=head3 Sources
A TAP parser consumes input from a I. There are currently two
typesof sources: L for general non-perl commands, andL. You can
subclass both of them. You'll need tocustomize your parser by
setting the C & Cparameters. See L for more details.
If you need to customize the objects on creation, subclass L
andoverride L or L.
=head3 Iterators
A TAP parser uses I to loop through the I provided by
theparser's I. There are quite a few types of Iterators
available.Choosing which class to use is the responsibility of the
I.
To create your own iterators you'll have to subclassL and L.
Then you'llneed to customize the class used by your parser by
setting theC parameter. See L for more details.
If you need to customize the objects on creation, subclass L
andoverride L.
=head3 Results
A TAP parser creates Ls as it iterates through theinput I. There
are quite a few result types available; choosingwhich class to use
is the responsibility of the I.
To create your own result types you have two options:
=over 2
-
8/10/2019 At8326GB Instalation Guide
48/643
=item option 1
Subclass L and register your new result type/class withthe
default L.
=item option 2
Subclass L itself and implement your ownL creation logic. Then
you'll need to customize theclass used by your parser by setting
the C parameter.See L for more details.
=back
If you need to customize the objects on creation, subclass L
andoverride L.
=head3 Grammar
L is the heart of the parser - it tokenizes the TAPinput I and
produces results. If you need to customize its behaviouryou should
probably familiarize yourself with the source first.
Enoughlecturing.
Subclass L and customize your parser by setting theC parameter.
See L for more details.
If you need to customize the objects on creation, subclass L
andoverride L
=head1 ACKNOWLEDGEMENTS
All of the following have helped. Bug reports, patches,
(im)moralsupport, or just words of encouragement have all been
forthcoming.
=over 4
=item * Michael Schwern
=item * Andy Lester
=item * chromatic
=item * GEOFFR
=item * Shlomi Fish
=item * Torsten Schoenfeld
=item * Jerry Gay
=item * Aristotle
=item * Adam Kennedy
=item * Yves Orton
=item * Adrian Howard
-
8/10/2019 At8326GB Instalation Guide
49/643
=item * Sean & Lil
=item * Andreas J. Koenig
=item * Florian Ragwitz
=item * Corion
=item * Mark Stosberg
=item * Matt Kraai
=item * David Wheeler
=item * Alex Vandiver
=back
=head1 AUTHORS
Curtis "Ovid" Poe
Andy Armstong
Eric Wilhelm @ Michael Peters
Leif Eriksen
Steve Purkis
Nicholas Clark
=head1 BUGS
Please report any bugs or feature requests to
C, or through the web interface atL.We will be notified, and
then you'll automatically be notified ofprogress on your bug as we
make changes.
Obviously, bugs which include patches are best. If you prefer,
you canpatch against bleed by via anonymous checkout of the latest
version:
svn checkout http://svn.hexten.net/tapx
=head1 COPYRIGHT & LICENSE
Copyright 2006-2008 Curtis "Ovid" Poe, all rights reserved.
This program is free software; you can redistribute it and/or
modify itunder the same terms as Perl itself.
=cut
1;package TAP::Formatter::Base;
use strict;
-
8/10/2019 At8326GB Instalation Guide
50/643
use TAP::Base ();use POSIX qw(strftime);
use vars qw($VERSION @ISA);
my $MAX_ERRORS = 5;my %VALIDATION_FOR;
BEGIN { @ISA = qw(TAP::Base);
%VALIDATION_FOR = ( directives => sub { shift; shift },
verbosity => sub { shift; shift }, normalize => sub { shift;
shift }, timer => sub { shift; shift }, failures => sub {
shift; shift }, comments => sub { shift; shift }, errors =>
sub { shift; shift }, color => sub { shift; shift }, jobs =>
sub { shift; shift }, show_count => sub { shift; shift }, stdout
=> sub { my ( $self, $ref ) = @_;
$self->_croak("option 'stdout' needs a filehandle") unless (
ref $ref || '' ) eq 'GLOB' or eval { $ref->can('print') };
return $ref; }, );
my @getter_setters = qw( _longest _printed_summary_header
_colorizer );
__PACKAGE__->mk_methods( @getter_setters, keys
%VALIDATION_FOR );}
=head1 NAME
TAP::Formatter::Console - Harness output delegate for default
console output
=head1 VERSION
Version 3.17
=cut
$VERSION = '3.17';
=head1 DESCRIPTION
This provides console orientated output formatting for
TAP::Harness.
=head1 SYNOPSIS
use TAP::Formatter::Console;my $harness =
TAP::Formatter::Console->new( \%args );
-
8/10/2019 At8326GB Instalation Guide
51/643
=cut
sub _initialize { my ( $self, $arg_for ) = @_; $arg_for ||=
{};
$self->SUPER::_initialize($arg_for); my %arg_for = %$arg_for;
# force a shallow copy
$self->verbosity(0);
for my $name ( keys %VALIDATION_FOR ) { my $property = delete
$arg_for{$name}; if ( defined $property ) { my $validate =
$VALIDATION_FOR{$name}; $self->$name(
$self->$validate($property) ); } }
if ( my @props = keys %arg_for ) { $self->_croak( "Unknown
arguments to " . __PACKAGE__ . "::new (@props)" ); }
$self->stdout( \*STDOUT ) unless $self->stdout;
if ( $self->color ) { require TAP::Formatter::Color;
$self->_colorizer( TAP::Formatter::Color->new ); }
return $self;}
sub verbose { shift->verbosity >= 1 }sub quiet {
shift->verbosity verbosity verbosity 1,)my $harness =
TAP::Formatter::Console->new( \%args );
The constructor returns a new C object. Ifa L is created with no
C aC is automatically created. If any of thefollowing options were
given to TAP::Harness->new they well be passed tothis
constructor which accepts an optional hashref whose allowed keys
are:
=over 4
=item * C
-
8/10/2019 At8326GB Instalation Guide
52/643
Set the verbosity level.
=item * C
Printing individual test results to STDOUT.
=item * C
Append run time for each test to output. Uses L if
available.
=item * C
Show test failures (this is a no-op if C is selected).
=item * C
Show test comments (this is a no-op if C is selected).
=item * C
Suppressing some test output (mostly failures while tests are
running).
=item * C
Suppressing everything but the tests summary.
=item * C
Suppressing all output.
=item * C
If parse errors are found in the TAP output, a note of this will
be madein the summary report. To see all of the parse errors, set
this argument totrue:
errors => 1
=item * C
If set to a true value, only test results with directives will
be displayed.This overrides other settings such as C, C, or C.
=item * C
A filehandle for catching standard output.
=item * C
If defined specifies whether color output is desired. If C is
notdefined it will default to color output if color support is
available onthe current platform and output is not being
redirected.
=item * C
The number of concurrent jobs this formatter will handle.
=item * C
-
8/10/2019 At8326GB Instalation Guide
53/643
Boolean value. If false, disables the C test count which shows
up whiletests are running.
=back
Any keys for which the value is C will be ignored.
=cut
# new supplied by TAP::Base
=head3 C
Called by Test::Harness before any test output is generated.
This is an advisory and may not be called in the case where
tests arebeing supplied to Test::Harness by an iterator.
=cut
sub prepare { my ( $self, @tests ) = @_;
my $longest = 0;
foreach my $test (@tests) { $longest = length $test if length
$test > $longest; }
$self->_longest($longest);}
sub _format_now { strftime "[%H:%M:%S]", localtime }
sub _format_name { my ( $self, $test ) = @_; my $name =
$test;
my $periods = '.' x ( $self->_longest + 2 - length $test );
$periods = " $periods ";
if ( $self->timer ) { my $stamp = $self->_format_now();
return "$stamp $name$periods"; } else { return "$name$periods";
}
}
=head3 C
Called to create a new test session. A test session looks like
this:
my $session = $formatter->open_test( $test, $parser ); while
( defined( my $result = $parser->next ) ) {
$session->result($result); exit 1 if $result->is_bailout; }
$session->close_test;
-
8/10/2019 At8326GB Instalation Guide
54/643
=cut
sub open_test { die "Unimplemented.";}
sub _output_success { my ( $self, $msg ) = @_;
$self->_output($msg);}
=head3 C
$harness->summary( $aggregate );
C prints the summary report after all tests are run. The
argument isan aggregate.
=cut
sub summary { my ( $self, $aggregate ) = @_;
return if $self->silent; my @t = $aggregate->descriptions;
my $tests = \@t;
my $runtime = $aggregate->elapsed_timestr;
my $total = $aggregate->total; my $passed =
$aggregate->passed;
if ( $self->timer ) { $self->_output(
$self->_format_now(), "\n" ); }
# TODO: Check this condition still works when all subtests pass
but # the exit status is nonzero
if ( $aggregate->all_passed ) {
$self->_output_success("All tests successful.\n"); }
# ~TODO option where $aggregate->skipped generates reports if
( $total != $passed or $aggregate->has_problems ) {
$self->_output("\nTest Summary Report");
$self->_output("\n-------------------\n"); foreach my $test
(@$tests) {
$self->_printed_summary_header(0); my ($parser) =
$aggregate->parsers($test); $self->_output_summary_failure(
'failed', [ ' Failed test: ', ' Failed tests: ' ], $test, $parser
); $self->_output_summary_failure( 'todo_passed', " TODO passed:
", $test, $parser
-
8/10/2019 At8326GB Instalation Guide
55/643
);
# ~TODO this cannot be the default
#$self->_output_summary_failure( 'skipped', " Tests skipped: "
);
if ( my $exit = $parser->exit ) {
$self->_summary_test_header( $test, $parser );
$self->_failure_output(" Non-zero exit status: $exit\n"); }
elsif ( my $wait = $parser->wait ) {
$self->_summary_test_header( $test, $parser );
$self->_failure_output(" Non-zero wait status: $wait\n"); }
if ( my @errors = $parser->parse_errors ) { my $explain; if (
@errors > $MAX_ERRORS && !$self->errors ) { $explain
= "Displayed the first $MAX_ERRORS of " . scalar(@errors) . " TAP
syntax errors.\n" . "Re-run prove with the -p option to see them
all.\n"; splice @errors, $MAX_ERRORS; }
$self->_summary_test_header( $test, $parser );
$self->_failure_output( sprintf " Parse errors: %s\n", shift
@errors ); foreach my $error (@errors) { my $spaces = ' ' x 16;
$self->_failure_output("$spaces$error\n"); }
$self->_failure_output($explain) if $explain; } } }
my $files = @$tests; $self->_output("Files=$files,
Tests=$total, $runtime\n"); my $status = $aggregate->get_status;
$self->_output("Result: $status\n");}
sub _output_summary_failure { my ( $self, $method, $name, $test,
$parser ) = @_;
# ugly hack. Must rethink this :( my $output = $method eq
'failed' ? '_failure_output' : '_output';
if ( my @r = $parser->$method() ) {
$self->_summary_test_header( $test, $parser ); my (
$singular, $plural ) = 'ARRAY' eq ref $name ? @$name : ( $name,
$name ); $self->$output( @r == 1 ? $singular : $plural ); my
@results = $self->_balanced_range( 40, @r ); $self->$output(
sprintf "%s\n" => shift @results ); my $spaces = ' ' x 16; while
(@results) { $self->$output( sprintf "$spaces%s\n" => shift
@results ); }
-
8/10/2019 At8326GB Instalation Guide
56/643
}}
sub _summary_test_header { my ( $self, $test, $parser ) = @_;
return if $self->_printed_summary_header; my $spaces = ' ' x (
$self->_longest - length $test ); $spaces = ' ' unless $spaces;
my $output = $self->_get_output_method($parser);
$self->$output( sprintf "$test$spaces(Wstat: %d Tests: %d
Failed: %d)\n", $parser->wait, $parser->tests_run, scalar
$parser->failed ); $self->_printed_summary_header(1);}
sub _output { my $self = shift;
print { $self->stdout } @_;}
sub _failure_output { my $self = shift;
$self->_output(@_);}
sub _balanced_range { my ( $self, $limit, @range ) = @_; @range
= $self->_range(@range); my $line = ""; my @lines; my $curr = 0;
while (@range) { if ( $curr < $limit ) { my $range = ( shift
@range ) . ", ";
$line .= $range; $curr += length $range; } elsif (@range) {
$line =~ s/, $//; push @lines => $line; $line = ''; $curr = 0; }
} if ($line) { $line =~ s/, $//; push @lines => $line;
} return @lines;}
sub _range { my ( $self, @numbers ) = @_;
# shouldn't be needed, but subclasses might call this @numbers =
sort { $a $b } @numbers; my ( $min, @range );
-
8/10/2019 At8326GB Instalation Guide
57/643
foreach my $i ( 0 .. $#numbers ) { my $num = $numbers[$i]; my
$next = $numbers[ $i + 1 ]; if ( defined $next && $next ==
$num + 1 ) { if ( !defined $min ) { $min = $num; } } elsif (
defined $min ) { push @range => "$min-$num"; undef $min; } else
{ push @range => $num; } } return @range;}
sub _get_output_method { my ( $self, $parser ) = @_; return
$parser->has_problems ? '_failure_output' : '_output';}
1;package TAP::Formatter::Color;
use strict;use vars qw($VERSION @ISA);
use constant IS_WIN32 => ( $^O =~ /^(MS)?Win32$/ );
@ISA = qw(TAP::Object);
my $NO_COLOR;
BEGIN { $NO_COLOR = 0;
if (IS_WIN32) { eval 'use Win32::Console'; if ($@) { $NO_COLOR =
$@; } else { my $console = Win32::Console->new(
STD_OUTPUT_HANDLE() );
# eval here because we might not know about these variables my
$fg = eval '$FG_LIGHTGRAY';
my $bg = eval '$BG_BLACK';
*set_color = sub { my ( $self, $output, $color ) = @_;
my $var; if ( $color eq 'reset' ) { $fg = eval '$FG_LIGHTGRAY';
$bg = eval '$BG_BLACK'; }
-
8/10/2019 At8326GB Instalation Guide
58/643
elsif ( $color =~ /^on_(.+)$/ ) { $bg = eval '$BG_' . uc($1); }
else { $fg = eval '$FG_' . uc($color); }
# In case of colors that aren't defined
$self->set_color('reset') unless defined $bg && defined
$fg;
$console->Attr( $bg | $fg ); }; } } else { eval 'use
Term::ANSIColor'; if ($@) { $NO_COLOR = $@; } else { *set_color =
sub { my ( $self, $output, $color ) = @_; $output->(
color($color) );
}; } }
if ($NO_COLOR) { *set_color = sub { }; }}
=head1 NAME
TAP::Formatter::Color - Run Perl test scripts with color
=head1 VERSION
Version 3.17
=cut
$VERSION = '3.17';
=head1 DESCRIPTION
Note that this harness is I. You may not like the colors
I'vechosen and I haven't yet provided an easy way to override
them.
This test harness is the same as L, but test results are
outputin color. Passing tests are printed in green. Failing tests
are in red.Skipped tests are blue on a white background and TODO
tests are printed inwhite.
If L cannot be found (or L if runningunder Windows) tests will
be run without color.
=head1 SYNOPSIS
-
8/10/2019 At8326GB Instalation Guide
59/643
use TAP::Formatter::Color;my $harness =
TAP::Formatter::Color->new( \%args
);$harness->runtests(@tests);
=head1 METHODS
=head2 Class Methods
=head3 C
The constructor returns a new C object. IfL is not installed,
returns undef.
=cut
# new() implementation supplied by TAP::Object
sub _initialize { my $self = shift;
if ($NO_COLOR) {
# shorten that message a bit ( my $error = $NO_COLOR ) =~ s/ in
\@INC .*//s;
warn "Note: Cannot run tests in color: $error\n"; return; #
abort object construction }
return $self;}
##############################################################################
=head3 C
Test::Formatter::Color->can_color()
Returns a boolean indicating whether or not this module can
actuallygenerate colored output. This will be false if it could not
load themodules needed for the current platform.
=cut
sub can_color { return !$NO_COLOR;}
=head3 C
Set the output color.
=cut
1;package TAP::Formatter::Console;
use strict;use TAP::Formatter::Base ();use POSIX
qw(strftime);
-
8/10/2019 At8326GB Instalation Guide
60/643
use vars qw($VERSION @ISA);
@ISA = qw(TAP::Formatter::Base);
=head1 NAME
TAP::Formatter::Console - Harness output delegate for default
console output
=head1 VERSION
Version 3.17
=cut
$VERSION = '3.17';
=head1 DESCRIPTION
This provides console orientated output formatting for
TAP::Harness.
=head1 SYNOPSIS
use TAP::Formatter::Console;my $harness =
TAP::Formatter::Console->new( \%args );
=head2 C>
See L
=cut
sub open_test { my ( $self, $test, $parser ) = @_;
my $class = $self->jobs > 1 ?
'TAP::Formatter::Console::ParallelSession'
: 'TAP::Formatter::Console::Session';
eval "require $class"; $self->_croak($@) if $@;
my $session = $class->new( { name => $test, formatter
=> $self, parser => $parser, show_count =>
$self->show_count, } );
$session->header;
return $session;}
# Use _colorizer delegate to set output color. NOP if we have no
delegatesub _set_colors { my ( $self, @colors ) = @_; if ( my
$colorizer = $self->_colorizer ) { my $output_func =
$self->{_output_func} ||= sub {
-
8/10/2019 At8326GB Instalation Guide
61/643
$self->_output(@_); }; $colorizer->set_color(
$output_func, $_ ) for @colors; }}
sub _output_success { my ( $self, $msg ) = @_;
$self->_set_colors('green'); $self->_output($msg);
$self->_set_colors('reset');}
sub _failure_output { my $self = shift;
$self->_set_colors('red'); my $out = join '', @_; my
$has_newline = chomp $out; $self->_output($out);
$self->_set_colors('reset'); $self->_output($/) if
$has_newline;}
1;package TAP::Formatter::File;
use strict;use TAP::Formatter::Base ();use
TAP::Formatter::File::Session;use POSIX qw(strftime);
use vars qw($VERSION @ISA);
@ISA = qw(TAP::Formatter::Base);
=head1 NAME
TAP::Formatter::File - Harness output delegate for file
output
=head1 VERSION
Version 3.17
=cut
$VERSION = '3.17';
=head1 DESCRIPTION
This provides file orientated output formatting for
TAP::Harness.
=head1 SYNOPSIS
use TAP::Formatter::File;my $harness =
TAP::Formatter::File->new( \%args );
=head2 C>
See L
-
8/10/2019 At8326GB Instalation Guide
62/643
=cut
sub open_test { my ( $self, $test, $parser ) = @_;
my $session = TAP::Formatter::File::Session->new( { name
=> $test, formatter => $self, parser => $parser, } );
$session->header;
return $session;}
sub _should_show_count { return 0;}
1;package TAP::Formatter::Session;
use strict;use TAP::Base;
use vars qw($VERSION @ISA);
@ISA = qw(TAP::Base);
my @ACCESSOR;
BEGIN {
@ACCESSOR = qw( name formatter parser show_count );
for my $method (@ACCESSOR) { no strict 'refs'; *$method = sub {
shift->{$method} }; }}
=head1 NAME
TAP::Formatter::Session - Abstract base class for harness output
delegate
=head1 VERSION
Version 3.17
=cut
$VERSION = '3.17';
=head1 METHODS
=head2 Class Methods
-
8/10/2019 At8326GB Instalation Guide
63/643
-
8/10/2019 At8326GB Instalation Guide
64/643
=head3 C
Called by C to clear the line showing test progress, or the
paralleltest ruler, prior to printing the final test result.
=cut
sub header { }
sub result { }
sub close_test { }
sub clear_for_close { }
sub _should_show_count { my $self = shift; return
!$self->formatter->verbose && -t
$self->formatter->stdout &&
!$ENV{HARNESS_NOTTY};}
sub _format_for_output { my ( $self, $result ) = @_; return
$self->formatter->normalize ? $result->as_string :
$result->raw;}
sub _output_test_failure { my ( $self, $parser ) = @_; my
$formatter = $self->formatter; return if
$formatter->really_quiet;
my $tests_run = $parser->tests_run; my $tests_planned =
$parser->tests_planned;
my $total = defined $tests_planned ? $tests_planned :
$tests_run;
my $passed = $parser->passed;
# The total number of fails includes any tests that were planned
but # didn't run my $failed = $parser->failed + $total -
$tests_run; my $exit = $parser->exit;
if ( my $exit = $parser->exit ) {
my $wstat = $parser->wait; my $status = sprintf( "%d (wstat
%d, 0x%x)", $exit, $wstat, $wstat );
$formatter->_failure_output("Dubious, test returned $status\n");
}
if ( $failed == 0 ) { $formatter->_failure_output( $total ?
"All $total subtests passed " : 'No subtests run '
-
8/10/2019 At8326GB Instalation Guide
65/643
); } else { $formatter->_failure_output("Failed
$failed/$total subtests "); if ( !$total ) {
$formatter->_failure_output("\nNo tests run!"); } }
if ( my $skipped = $parser->skipped ) { $passed -= $skipped;
my $test = 'subtest' . ( $skipped != 1 ? 's' : '' );
$formatter->_output( "\n\t(less $skipped skipped $test: $passed
okay)"); }
if ( my $failed = $parser->todo_passed ) { my $test = $failed
> 1 ? 'tests' : 'test'; $formatter->_output( "\n\t($failed
TODO $test unexpectedly succeeded)"); }
$formatter->_output("\n");}
1;package TAP::Formatter::Console::ParallelSession;
use strict;use File::Spec;use File::Path;use
TAP::Formatter::Console::Session;use Carp;
use constant WIDTH => 72; # Because Eric saysuse vars
qw($VERSION @ISA);
@ISA = qw(TAP::Formatter::Console::Session);
my %shared;
sub _initialize { my ( $self, $arg_for ) = @_;
$self->SUPER::_initialize($arg_for); my $formatter =
$self->formatter;
# Horrid bodge. This creates our shared context per harness.
Maybe # TAP::Harness should give us this? my $context =
$shared{$formatter} ||= $self->_create_shared_context;
push @{ $context->{active} }, $self;
return $self;}
sub _create_shared_context { my $self = shift; return { active
=> [], tests => 0,
-
8/10/2019 At8326GB Instalation Guide
66/643
fails => 0, };}
=head1 NAME