| Current File : /home/mmdealscpanel/yummmdeals.com/TAP.zip |
PK �[-26�� � Base.pmnu �[��� package TAP::Base;
use strict;
use warnings;
use base 'TAP::Object';
=head1 NAME
TAP::Base - Base class that provides common functionality to L<TAP::Parser>
and L<TAP::Harness>
=head1 VERSION
Version 3.42
=cut
our $VERSION = '3.42';
use constant GOT_TIME_HIRES => do {
eval 'use Time::HiRes qw(time);';
$@ ? 0 : 1;
};
=head1 SYNOPSIS
package TAP::Whatever;
use base 'TAP::Base';
# ... later ...
my $thing = TAP::Whatever->new();
$thing->callback( event => sub {
# do something interesting
} );
=head1 DESCRIPTION
C<TAP::Base> 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<callback>
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;
}
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<get_time>
Return the current time using Time::HiRes if available.
=cut
sub get_time { return time() }
=head3 C<time_is_hires>
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 }
=head3 C<get_times>
Return array reference of the four-element list of CPU seconds,
as with L<perlfunc/times>.
=cut
sub get_times { return [ times() ] }
1;
PK �[^Ȳ۱� �� Parser.pmnu �[��� package TAP::Parser;
use strict;
use warnings;
use TAP::Parser::Grammar ();
use TAP::Parser::Result ();
use TAP::Parser::ResultFactory ();
use TAP::Parser::Source ();
use TAP::Parser::Iterator ();
use TAP::Parser::IteratorFactory ();
use TAP::Parser::SourceHandler::Executable ();
use TAP::Parser::SourceHandler::Perl ();
use TAP::Parser::SourceHandler::File ();
use TAP::Parser::SourceHandler::RawTAP ();
use TAP::Parser::SourceHandler::Handle ();
use Carp qw( confess );
use base 'TAP::Base';
=encoding utf8
=head1 NAME
TAP::Parser - Parse L<TAP|Test::Harness::TAP> output
=head1 VERSION
Version 3.42
=cut
our $VERSION = '3.42';
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
__PACKAGE__->mk_methods(
qw(
_iterator
_spool
exec
exit
is_good_plan
plan
tests_planned
tests_run
wait
version
in_todo
start_time
end_time
start_times
end_times
skip_all
grammar_class
result_factory_class
iterator_factory_class
)
);
sub _stream { # deprecated
my $self = shift;
$self->_iterator(@_);
}
} # done making accessors
=head1 SYNOPSIS
use TAP::Parser;
my $parser = TAP::Parser->new( { source => $source } );
while ( my $result = $parser->next ) {
print $result->as_string;
}
=head1 DESCRIPTION
C<TAP::Parser> is designed to produce a proper parse of TAP output. For
an example of how to run tests through this module, see the simple
harnesses C<examples/>.
There's a wiki dedicated to the Test Anything Protocol:
L<http://testanything.org>
It includes the TAP::Parser Cookbook:
L<http://testanything.org/testing-with-tap/perl/tap::parser-cookbook.html>
=head1 METHODS
=head2 Class Methods
=head3 C<new>
my $parser = TAP::Parser->new(\%args);
Returns a new C<TAP::Parser> object.
The arguments should be a hashref with I<one> of the following keys:
=over 4
=item * C<source>
I<CHANGED in 3.18>
This is the preferred method of passing input to the constructor.
The C<source> is used to create a L<TAP::Parser::Source> that is passed to the
L</iterator_factory_class> which in turn figures out how to handle the source and
creates a <TAP::Parser::Iterator> for it. The iterator is used by the parser to
read in the TAP stream.
To configure the I<IteratorFactory> use the C<sources> parameter below.
Note that C<source>, C<tap> and C<exec> are I<mutually exclusive>.
=item * C<tap>
I<CHANGED in 3.18>
The value should be the complete TAP output.
The I<tap> is used to create a L<TAP::Parser::Source> that is passed to the
L</iterator_factory_class> which in turn figures out how to handle the source and
creates a <TAP::Parser::Iterator> for it. The iterator is used by the parser to
read in the TAP stream.
To configure the I<IteratorFactory> use the C<sources> parameter below.
Note that C<source>, C<tap> and C<exec> are I<mutually exclusive>.
=item * C<exec>
Must be passed an array reference.
The I<exec> array ref is used to create a L<TAP::Parser::Source> that is passed
to the L</iterator_factory_class> which in turn figures out how to handle the
source and creates a <TAP::Parser::Iterator> for it. The iterator is used by
the parser to read in the TAP stream.
By default the L<TAP::Parser::SourceHandler::Executable> class will create a
L<TAP::Parser::Iterator::Process> object to handle the source. This passes the
array reference strings as command arguments to L<IPC::Open3::open3|IPC::Open3>:
exec => [ '/usr/bin/ruby', 't/my_test.rb' ]
If any C<test_args> are given they will be appended to the end of the command
argument list.
To configure the I<IteratorFactory> use the C<sources> parameter below.
Note that C<source>, C<tap> and C<exec> are I<mutually exclusive>.
=back
The following keys are optional.
=over 4
=item * C<sources>
I<NEW to 3.18>.
If set, C<sources> must be a hashref containing the names of the
L<TAP::Parser::SourceHandler>s to load and/or configure. The values are a
hash of configuration that will be accessible to the source handlers via
L<TAP::Parser::Source/config_for>.
For example:
sources => {
Perl => { exec => '/path/to/custom/perl' },
File => { extensions => [ '.tap', '.txt' ] },
MyCustom => { some => 'config' },
}
This will cause C<TAP::Parser> to pass custom configuration to two of the built-
in source handlers - L<TAP::Parser::SourceHandler::Perl>,
L<TAP::Parser::SourceHandler::File> - and attempt to load the C<MyCustom>
class. See L<TAP::Parser::IteratorFactory/load_handlers> for more detail.
The C<sources> parameter affects how C<source>, C<tap> and C<exec> parameters
are handled.
See L<TAP::Parser::IteratorFactory>, L<TAP::Parser::SourceHandler> and subclasses for
more details.
=item * C<callback>
If present, each callback corresponding to a given result type will be called
with the result as the argument if the C<run> 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;
for my $file ( @test_files ) {
my $parser = TAP::Parser->new(
{
source => $file,
callbacks => \%callbacks,
}
);
$parser->run;
$aggregator->add( $file, $parser );
}
=item * C<switches>
If using a Perl file as a source, optional switches may be passed which will
be used when invoking the perl executable.
my $parser = TAP::Parser->new( {
source => $test_file,
switches => [ '-Ilib' ],
} );
=item * C<test_args>
Used in conjunction with the C<source> and C<exec> option to supply a reference
to an C<@ARGV> style array of arguments to pass to the test program.
=item * C<spool>
If passed a filehandle will write a copy of all parsed TAP to that handle.
=item * C<merge>
If false, STDERR is not captured (though it is 'relayed' to keep it
somewhat synchronized with STDOUT.)
If true, STDERR and STDOUT are the same filehandle. This may cause
breakage if STDERR contains anything resembling TAP format, but does
allow exact synchronization.
Subtleties of this behavior may be platform-dependent and may change in
the future.
=item * C<grammar_class>
This option was introduced to let you easily customize which I<grammar> class
the parser should use. It defaults to L<TAP::Parser::Grammar>.
See also L</make_grammar>.
=item * C<result_factory_class>
This option was introduced to let you easily customize which I<result>
factory class the parser should use. It defaults to
L<TAP::Parser::ResultFactory>.
See also L</make_result>.
=item * C<iterator_factory_class>
I<CHANGED in 3.18>
This option was introduced to let you easily customize which I<iterator>
factory class the parser should use. It defaults to
L<TAP::Parser::IteratorFactory>.
=back
=cut
# new() implementation supplied by TAP::Base
# This should make overriding behaviour of the Parser in subclasses easier:
sub _default_grammar_class {'TAP::Parser::Grammar'}
sub _default_result_factory_class {'TAP::Parser::ResultFactory'}
sub _default_iterator_factory_class {'TAP::Parser::IteratorFactory'}
##############################################################################
=head2 Instance Methods
=head3 C<next>
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. Note
that 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<TAP::Parser::Result>. See that
module and related classes for more information on how to use them.
=cut
sub next {
my $self = shift;
return ( $self->{_iter} ||= $self->_iter )->();
}
##############################################################################
=head3 C<run>
$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_grammar>
Make a new L<TAP::Parser::Grammar> object and return it. Passes through any
arguments given.
The C<grammar_class> can be customized, as described in L</new>.
=head3 C<make_result>
Make a new L<TAP::Parser::Result> object using the parser's
L<TAP::Parser::ResultFactory>, and return it. Passes through any arguments
given.
The C<result_factory_class> can be customized, as described in L</new>.
=head3 C<make_iterator_factory>
I<NEW to 3.18>.
Make a new L<TAP::Parser::IteratorFactory> object and return it. Passes through
any arguments given.
C<iterator_factory_class> can be customized, as described in L</new>.
=cut
# This should make overriding behaviour of the Parser in subclasses easier:
sub make_iterator_factory { shift->iterator_factory_class->new(@_); }
sub make_grammar { shift->grammar_class->new(@_); }
sub make_result { shift->result_factory_class->make_result(@_); }
{
# 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)
tests_run => 0, # actual current test numbers
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
);
# 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(
grammar_class
result_factory_class
iterator_factory_class
);
sub _initialize {
my ( $self, $arg_for ) = @_;
# everything here is basically designed to convert any TAP source to a
# TAP::Parser::Iterator.
# 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 $iterator = delete $args{iterator};
$iterator ||= delete $args{stream}; # deprecated
my $tap = delete $args{tap};
my $version = delete $args{version};
my $raw_source = delete $args{source};
my $sources = delete $args{sources};
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} $iterator, $tap, $raw_source, $exec ) {
$self->_croak(
"You may only choose one of 'exec', 'tap', 'source' or 'iterator'"
);
}
if ( my @excess = sort keys %args ) {
$self->_croak("Unknown options: @excess");
}
# convert $tap & $exec to $raw_source equiv.
my $type = '';
my $source = TAP::Parser::Source->new;
if ($tap) {
$type = 'raw TAP';
$source->raw( \$tap );
}
elsif ($exec) {
$type = 'exec ' . $exec->[0];
$source->raw( { exec => $exec } );
}
elsif ($raw_source) {
$type = 'source ' . ref($raw_source) || $raw_source;
$source->raw( ref($raw_source) ? $raw_source : \$raw_source );
}
elsif ($iterator) {
$type = 'iterator ' . ref($iterator);
}
if ( $source->raw ) {
my $src_factory = $self->make_iterator_factory($sources);
$source->merge($merge)->switches($switches)
->test_args($test_args);
$iterator = $src_factory->make_iterator($source);
}
unless ($iterator) {
$self->_croak(
"PANIC: could not determine iterator for input $type");
}
while ( my ( $k, $v ) = each %initialize ) {
$self->{$k} = 'ARRAY' eq ref $v ? [] : $v;
}
$self->version($version) if $version;
$self->_iterator($iterator);
$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<TAP::Parser::Result> subclass, referred to as
I<result types>.
=head2 Result types
Basically, you fetch individual results from the TAP. The six types, with
examples 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 common
methods to each result object and different types may have methods unique to
their type. Sometimes a type method may be overridden in a subclass, but its
use is guaranteed to be identical.
=head2 Common type methods
=head3 C<type>
Returns the type of result, such as C<comment> or C<test>.
=head3 C<as_string>
Prints a string representation of the token. This might not be the exact
output, however. Tests will have test numbers added if not present, TODO and
SKIP directives will be capitalized and, in general, things will be cleaned
up. If you need the original text for the token, see the C<raw> method.
=head3 C<raw>
Returns the original line of text which was parsed.
=head3 C<is_plan>
Indicates whether or not this is the test plan line.
=head3 C<is_test>
Indicates whether or not this is a test line.
=head3 C<is_comment>
Indicates whether or not this is a comment. Comments will generally only
appear in the TAP stream if STDERR is merged to STDOUT. See the
C<merge> option.
=head3 C<is_bailout>
Indicates whether or not this is bailout line.
=head3 C<is_yaml>
Indicates whether or not the current item is a YAML block.
=head3 C<is_unknown>
Indicates whether or not the current line could be parsed.
=head3 C<is_ok>
if ( $result->is_ok ) { ... }
Reports whether or not a given result has passed. Anything which is B<not> a
test result returns true. This is merely provided as a convenient shortcut
which 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<plan> methods
if ( $result->is_plan ) { ... }
If the above evaluates as true, the following methods will be available on the
C<$result> object.
=head3 C<plan>
if ( $result->is_plan ) {
print $result->plan;
}
This is merely a synonym for C<as_string>.
=head3 C<directive>
my $directive = $result->directive;
If a SKIP directive is included with the plan, this method will return it.
1..0 # SKIP: why bother?
=head3 C<explanation>
my $explanation = $result->explanation;
If a SKIP directive was included with the plan, this method will return the
explanation, if any.
=head2 C<pragma> methods
if ( $result->is_pragma ) { ... }
If the above evaluates as true, the following methods will be available on the
C<$result> object.
=head3 C<pragmas>
Returns a list of pragmas each of which is a + or - followed by the
pragma name.
=head2 C<comment> methods
if ( $result->is_comment ) { ... }
If the above evaluates as true, the following methods will be available on the
C<$result> object.
=head3 C<comment>
if ( $result->is_comment ) {
my $comment = $result->comment;
print "I have something to say: $comment";
}
=head2 C<bailout> methods
if ( $result->is_bailout ) { ... }
If the above evaluates as true, the following methods will be available on the
C<$result> object.
=head3 C<explanation>
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" via
this method. The explanation is the text after the mystical "Bail out!" words
which appear in the tap output.
=head2 C<unknown> methods
if ( $result->is_unknown ) { ... }
There are no unique methods for unknown results.
=head2 C<test> methods
if ( $result->is_test ) { ... }
If the above evaluates as true, the following methods will be available on the
C<$result> object.
=head3 C<ok>
my $ok = $result->ok;
Returns the literal text of the C<ok> or C<not ok> status.
=head3 C<number>
my $test_number = $result->number;
Returns the number of the test, even if the original TAP output did not supply
that number.
=head3 C<description>
my $description = $result->description;
Returns the description of the test, if any. This is the portion after the
test number but before the directive.
=head3 C<directive>
my $directive = $result->directive;
Returns either C<TODO> or C<SKIP> if either directive was present for a test
line.
=head3 C<explanation>
my $explanation = $result->explanation;
If a test had either a C<TODO> or C<SKIP> directive, this method will return
the accompanying explanation, if present.
not ok 17 - 'Pigs can fly' # TODO not enough acid
For the above line, the explanation is I<not enough acid>.
=head3 C<is_ok>
if ( $result->is_ok ) { ... }
Returns a boolean value indicating whether or not the test passed. Remember
that for TODO tests, the test always passes.
B<Note:> this was formerly C<passed>. The latter method is deprecated and
will issue a warning.
=head3 C<is_actual_ok>
if ( $result->is_actual_ok ) { ... }
Returns a boolean value indicating whether or not the test passed, regardless
of its TODO status.
B<Note:> this was formerly C<actual_passed>. The latter method is deprecated
and will issue a warning.
=head3 C<is_unplanned>
if ( $test->is_unplanned ) { ... }
If a test number is greater than the number of planned tests, this method will
return true. Unplanned tests will I<always> return false for C<is_ok>,
regardless of whether or not the test C<has_todo> (see
L<TAP::Parser::Result::Test> for more information about this).
=head3 C<has_skip>
if ( $result->has_skip ) { ... }
Returns a boolean value indicating whether or not this test had a SKIP
directive.
=head3 C<has_todo>
if ( $result->has_todo ) { ... }
Returns a boolean value indicating whether or not this test had a TODO
directive.
Note that TODO tests I<always> pass. If you need to know whether or not
they really passed, check the C<is_actual_ok> method.
=head3 C<in_todo>
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 through
the results and determine what is meaningful to you.
=head2 Individual Results
These results refer to individual tests which are run.
=head3 C<passed>
my @passed = $parser->passed; # the test numbers which passed
my $passed = $parser->passed; # the number of tests which passed
This method lets you know which (or how many) tests passed. If a test failed
but had a TODO directive, it will be counted as a passed test.
=cut
sub passed {
return @{ $_[0]->{passed} }
if ref $_[0]->{passed};
return wantarray ? 1 .. $_[0]->{passed} : $_[0]->{passed};
}
=head3 C<failed>
my @failed = $parser->failed; # the test numbers which failed
my $failed = $parser->failed; # the number of tests which failed
This method lets you know which (or how many) tests failed. If a test passed
but had a TODO directive, it will B<NOT> be counted as a failed test.
=cut
sub failed { @{ shift->{failed} } }
=head3 C<actual_passed>
# the test numbers which actually passed
my @actual_passed = $parser->actual_passed;
# the number of tests which actually passed
my $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.
=cut
sub actual_passed {
return @{ $_[0]->{actual_passed} }
if ref $_[0]->{actual_passed};
return wantarray ? 1 .. $_[0]->{actual_passed} : $_[0]->{actual_passed};
}
*actual_ok = \&actual_passed;
=head3 C<actual_ok>
This method is a synonym for C<actual_passed>.
=head3 C<actual_failed>
# the test numbers which actually failed
my @actual_failed = $parser->actual_failed;
# the number of tests which actually failed
my $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<todo>
my @todo = $parser->todo; # the test numbers with todo directives
my $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} } }
=head3 C<todo_passed>
# the test numbers which unexpectedly succeeded
my @todo_passed = $parser->todo_passed;
# the number of tests which unexpectedly succeeded
my $todo_passed = $parser->todo_passed;
This method lets you know which (or how many) tests actually passed but were
declared as "TODO" tests.
=cut
sub todo_passed { @{ shift->{todo_passed} } }
##############################################################################
=head3 C<todo_failed>
# deprecated in favor of 'todo_passed'. This method was horribly misnamed.
This was a badly misnamed method. It indicates which TODO tests unexpectedly
succeeded. Will now issue a warning and call C<todo_passed>.
=cut
sub todo_failed {
warn
'"todo_failed" is deprecated. Please use "todo_passed". See the docs.';
goto &todo_passed;
}
=head3 C<skipped>
my @skipped = $parser->skipped; # the test numbers with SKIP directives
my $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<pragma>
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
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<pragmas>
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 individual
test program.
=head3 C<plan>
my $plan = $parser->plan;
Returns the test plan, if found.
=head3 C<good_plan>
Deprecated. Use C<is_good_plan> instead.
=cut
sub good_plan {
warn 'good_plan() is deprecated. Please use "is_good_plan()"';
goto &is_good_plan;
}
##############################################################################
=head3 C<is_good_plan>
if ( $parser->is_good_plan ) { ... }
Returns a boolean value indicating whether or not the number of tests planned
matches the number of tests run.
B<Note:> this was formerly C<good_plan>. The latter method is deprecated and
will issue a warning.
And since we're on that subject ...
=head3 C<tests_planned>
print $parser->tests_planned;
Returns the number of tests planned, according to the plan. For example, a
plan of '1..17' will mean that 17 tests were planned.
=head3 C<tests_run>
print $parser->tests_run;
Returns the number of tests which actually were run. Hopefully this will
match the number of C<< $parser->tests_planned >>.
=head3 C<skip_all>
Returns a true value (actually the reason for skipping) if all tests
were skipped.
=head3 C<start_time>
Returns the wall-clock time when the Parser was created.
=head3 C<end_time>
Returns the wall-clock time when the end of TAP input was seen.
=head3 C<start_times>
Returns the CPU times (like L<perlfunc/times> when the Parser was created.
=head3 C<end_times>
Returns the CPU times (like L<perlfunc/times> when the end of TAP
input was seen.
=head3 C<has_problems>
if ( $parser->has_problems ) {
...
}
This is a 'catch-all' method which returns true if any tests have currently
failed, 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<version>
$parser->version;
Once the parser is done, this will return the version number for the
parsed TAP. Version numbers were introduced with TAP version 13 so if no
version number is found version 12 is assumed.
=head3 C<exit>
$parser->exit;
Once the parser is done, this will return the exit status. If the parser ran
an executable, it returns the exit status of the executable.
=head3 C<wait>
$parser->wait;
Once the parser is done, this will return the wait status. If the parser ran
an executable, it returns the wait status of the executable. Otherwise, this
merely returns the C<exit> status.
=head2 C<ignore_exit>
$parser->ignore_exit(1);
Tell the parser to ignore the exit status from the test when determining
whether the test passed. Normally tests with non-zero exit status are
considered to have failed even if all individual tests passed. In cases
where it is not possible to control the exit value of the test script
use this option to ignore it.
=cut
sub ignore_exit { shift->pragma( 'ignore_exit', @_ ) }
=head3 C<parse_errors>
my @errors = $parser->parse_errors; # the parser errors
my $errors = $parser->parse_errors; # the number of parser_errors
Fortunately, all TAP output is perfect. In the event that it is not, this
method will return parser errors. Note that a junk line which the parser does
not recognize is C<not> an error. This allows this parser to handle future
versions 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 the
TAP output.
=item * No plan
Gotta have a plan!
=item * More than one plan
1..3
ok 1 - input file opened
not ok 2 - first line of the input valid # todo some data
ok 3 read the rest of the file
1..3
Right. Very funny. Don't do that.
=item * Test numbers out of sequence
1..3
ok 1 - input file opened
not ok 2 - first line of the input valid # todo some data
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 and
others to not have them. However, when a test number is found, it must be in
sequence. The following is also an error:
1..3
ok 1 - input file opened
not ok - first line of the input valid # todo some data
ok 2 read the rest of the file
But this is not:
1..3
ok - input file opened
not ok - first line of the input valid # todo some data
ok 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 => {
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
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 <= $DEFAULT_TAP_VERSION ) {
my $ver_min = $DEFAULT_TAP_VERSION + 1;
$self->_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' },
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 => 'UNPLANNED' },
},
);
# 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;
}
=head3 C<get_select_handles>
Get an a list of file handles which can be passed to C<select> to
determine the readiness of this parser.
=cut
sub get_select_handles { shift->_iterator->get_select_handles }
sub _grammar {
my $self = shift;
return $self->{_grammar} = shift if @_;
return $self->{_grammar} ||= $self->make_grammar(
{ iterator => $self->_iterator,
parser => $self,
version => $self->version
}
);
}
sub _iter {
my $self = shift;
my $iterator = $self->_iterator;
my $grammar = $self->_grammar;
my $spool = $self->_spool;
my $state = 'INIT';
my $state_table = $self->_make_state_table;
$self->start_time( $self->get_time );
$self->start_times( $self->get_times );
# 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 {
$self->exit( $iterator->exit );
$self->wait( $iterator->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;
$self->end_time( $self->get_time );
$self->end_times( $self->get_times );
# Avoid leaks
$self->_iterator(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;
unless ( $self->parse_errors ) {
# Optimise storage where possible
if ( $self->tests_run == @{$self->{passed}} ) {
$self->{passed} = $self->tests_run;
}
if ( $self->tests_run == @{$self->{actual_passed}} ) {
$self->{actual_passed} = $self->tests_run;
}
}
return $self;
}
=head3 C<delete_spool>
Delete and return the spool.
my $fh = $parser->delete_spool;
=cut
sub delete_spool {
my $self = shift;
return delete $self->{_spool};
}
##############################################################################
=head1 CALLBACKS
As mentioned earlier, a "callback" key may be added to the
C<TAP::Parser> constructor. If present, each callback corresponding to a
given result type will be called with the result as the argument if the
C<run> method is used. The callback is expected to be a subroutine
reference (or anonymous subroutine) which is invoked with the parser
result as its argument.
my %callbacks = (
test => \&test_callback,
plan => \&plan_callback,
comment => \&comment_callback,
bailout => \&bailout_callback,
unknown => \&unknown_callback,
);
my $aggregator = TAP::Parser::Aggregator->new;
for my $file ( @test_files ) {
my $parser = TAP::Parser->new(
{
source => $file,
callbacks => \%callbacks,
}
);
$parser->run;
$aggregator->add( $file, $parser );
}
Callbacks may also be added like this:
$parser->callback( test => \&test_callback );
$parser->callback( plan => \&plan_callback );
The following keys allowed for callbacks. These keys are case-sensitive.
=over 4
=item * C<test>
Invoked if C<< $result->is_test >> returns true.
=item * C<version>
Invoked if C<< $result->is_version >> returns true.
=item * C<plan>
Invoked if C<< $result->is_plan >> returns true.
=item * C<comment>
Invoked if C<< $result->is_comment >> returns true.
=item * C<bailout>
Invoked if C<< $result->is_unknown >> returns true.
=item * C<yaml>
Invoked if C<< $result->is_yaml >> returns true.
=item * C<unknown>
Invoked if C<< $result->is_unknown >> returns true.
=item * C<ELSE>
If a result does not have a callback defined for it, this callback will
be invoked. Thus, if all of the previous result types are specified as
callbacks, this callback will I<never> be invoked.
=item * C<ALL>
This callback will always be invoked and this will happen for each
result after one of the above callbacks is invoked. For example, if
L<Term::ANSIColor> is loaded, you could use the following to color your
test 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<EOF>
Invoked when there are no more lines to be parsed. Since there is no
accompanying L<TAP::Parser::Result> object the C<TAP::Parser> object is
passed instead.
=back
=head1 TAP GRAMMAR
If you're looking for an EBNF grammar, see L<TAP::Parser::Grammar>.
=head1 BACKWARDS COMPATIBILITY
The Perl-QA list attempted to ensure backwards compatibility with
L<Test::Harness>. However, there are some minor differences.
=head2 Differences
=over 4
=item * TODO plans
A little-known feature of L<Test::Harness> is that it supported TODO
lists in the plan:
1..2 todo 2
ok 1 - We have liftoff
not ok 2 - Anti-gravity device activated
Under L<Test::Harness>, test number 2 would I<pass> because it was
listed as a TODO test on the plan line. However, we are not aware of
anyone actually using this feature and hard-coding test numbers is
discouraged because it's very easy to add a test and break the test
number sequence. This makes test suites very fragile. Instead, the
following should be used:
1..2
ok 1 - We have liftoff
not ok 2 - Anti-gravity device activated # TODO
=item * 'Missing' tests
It rarely happens, but sometimes a harness might encounter
'missing tests:
ok 1
ok 2
ok 15
ok 16
ok 17
L<Test::Harness> would report tests 3-14 as having failed. For the
C<TAP::Parser>, these tests are not considered failed because they've
never 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<Test::Harness::Straps>), you're in luck: C<TAP::Parser> and friends are
designed to be easily plugged-into and/or subclassed.
Before you start, it's important to know a few things:
=over 2
=item 1
All C<TAP::*> objects inherit from L<TAP::Object>.
=item 2
Many C<TAP::*> classes have a I<SUBCLASSING> section to guide you.
=item 3
Note that C<TAP::Parser> is designed to be the central "maker" - ie: it is
responsible for creating most new objects in the C<TAP::Parser::*> namespace.
This makes it possible for you to have a single point of configuring what
subclasses should be used, which means that in many cases you'll find
you only need to sub-class one of the parser's components.
The exception to this rule are I<SourceHandlers> & I<Iterators>, but those are
both created with customizable I<IteratorFactory>.
=item 4
By subclassing, you may end up overriding undocumented methods. That's not
a bad thing per se, but be forewarned that undocumented methods may change
without warning from one release to the next - we cannot guarantee backwards
compatibility. If any I<documented> method needs changing, it will be
deprecated first, and changed in a later release.
=back
=head2 Parser Components
=head3 Sources
A TAP parser consumes input from a single I<raw source> of TAP, which could come
from anywhere (a file, an executable, a database, an IO handle, a URI, etc..).
The source gets bundled up in a L<TAP::Parser::Source> object which gathers some
meta data about it. The parser then uses a L<TAP::Parser::IteratorFactory> to
determine which L<TAP::Parser::SourceHandler> to use to turn the raw source
into a stream of TAP by way of L</Iterators>.
If you simply want C<TAP::Parser> to handle a new source of TAP you probably
don't need to subclass C<TAP::Parser> itself. Rather, you'll need to create a
new L<TAP::Parser::SourceHandler> class, and just plug it into the parser using
the I<sources> param to L</new>. Before you start writing one, read through
L<TAP::Parser::IteratorFactory> to get a feel for how the system works first.
If you find you really need to use your own iterator factory you can still do
so without sub-classing C<TAP::Parser> by setting L</iterator_factory_class>.
If you just need to customize the objects on creation, subclass L<TAP::Parser>
and override L</make_iterator_factory>.
Note that C<make_source> & C<make_perl_source> have been I<DEPRECATED> and
are now removed.
=head3 Iterators
A TAP parser uses I<iterators> to loop through the I<stream> of TAP read in
from the I<source> it was given. There are a few types of Iterators available
by default, all sub-classes of L<TAP::Parser::Iterator>. Choosing which
iterator to use is the responsibility of the I<iterator factory>, though it
simply delegates to the I<Source Handler> it uses.
If you're writing your own L<TAP::Parser::SourceHandler>, you may need to
create your own iterators too. If so you'll need to subclass
L<TAP::Parser::Iterator>.
Note that L</make_iterator> has been I<DEPRECATED> and is now removed.
=head3 Results
A TAP parser creates L<TAP::Parser::Result>s as it iterates through the
input I<stream>. There are quite a few result types available; choosing
which class to use is the responsibility of the I<result factory>.
To create your own result types you have two options:
=over 2
=item option 1
Subclass L<TAP::Parser::Result> and register your new result type/class with
the default L<TAP::Parser::ResultFactory>.
=item option 2
Subclass L<TAP::Parser::ResultFactory> itself and implement your own
L<TAP::Parser::Result> creation logic. Then you'll need to customize the
class used by your parser by setting the C<result_factory_class> parameter.
See L</new> for more details.
=back
If you need to customize the objects on creation, subclass L<TAP::Parser> and
override L</make_result>.
=head3 Grammar
L<TAP::Parser::Grammar> is the heart of the parser. It tokenizes the TAP
input I<stream> and produces results. If you need to customize its behaviour
you should probably familiarize yourself with the source first. Enough
lecturing.
Subclass L<TAP::Parser::Grammar> and customize your parser by setting the
C<grammar_class> parameter. See L</new> for more details.
If you need to customize the objects on creation, subclass L<TAP::Parser> and
override L</make_grammar>
=head1 ACKNOWLEDGMENTS
All of the following have helped. Bug reports, patches, (im)moral
support, 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
=item * Sean & Lil
=item * Andreas J. Koenig
=item * Florian Ragwitz
=item * Corion
=item * Mark Stosberg
=item * Matt Kraai
=item * David Wheeler
=item * Alex Vandiver
=item * Cosimo Streppone
=item * Ville Skyttä
=back
=head1 AUTHORS
Curtis "Ovid" Poe <ovid@cpan.org>
Andy Armstong <andy@hexten.net>
Eric Wilhelm @ <ewilhelm at cpan dot org>
Michael Peters <mpeters at plusthree dot com>
Leif Eriksen <leif dot eriksen at bigpond dot com>
Steve Purkis <spurkis@cpan.org>
Nicholas Clark <nick@ccl4.org>
Lee Johnson <notfadeaway at btinternet dot com>
Philippe Bruhat <book@cpan.org>
=head1 BUGS
Please report any bugs or feature requests to
C<bug-test-harness@rt.cpan.org>, or through the web interface at
L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Test-Harness>.
We will be notified, and then you'll automatically be notified of
progress on your bug as we make changes.
Obviously, bugs which include patches are best. If you prefer, you can
patch against bleed by via anonymous checkout of the latest version:
git clone git://github.com/Perl-Toolchain-Gang/Test-Harness.git
=head1 COPYRIGHT & LICENSE
Copyright 2006-2008 Curtis "Ovid" Poe, all rights reserved.
This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.
=cut
1;
PK �[�d}a^. ^. Formatter/Base.pmnu �[��� package TAP::Formatter::Base;
use strict;
use warnings;
use base 'TAP::Base';
use POSIX qw(strftime);
my $MAX_ERRORS = 5;
my %VALIDATION_FOR;
BEGIN {
%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 $self->_is_filehandle($ref);
return $ref;
},
);
sub _is_filehandle {
my ( $self, $ref ) = @_;
return 0 if !defined $ref;
return 1 if ref $ref eq 'GLOB'; # lexical filehandle
return 1 if !ref $ref && ref \$ref eq 'GLOB'; # bare glob like *STDOUT
return 1 if eval { $ref->can('print') };
return 0;
}
my @getter_setters = qw(
_longest
_printed_summary_header
_colorizer
);
__PACKAGE__->mk_methods( @getter_setters, keys %VALIDATION_FOR );
}
=head1 NAME
TAP::Formatter::Base - Base class for harness output delegates
=head1 VERSION
Version 3.42
=cut
our $VERSION = '3.42';
=head1 DESCRIPTION
This provides console orientated output formatting for TAP::Harness.
=head1 SYNOPSIS
use TAP::Formatter::Console;
my $harness = TAP::Formatter::Console->new( \%args );
=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 <= -1 }
sub really_quiet { shift->verbosity <= -2 }
sub silent { shift->verbosity <= -3 }
=head1 METHODS
=head2 Class Methods
=head3 C<new>
my %args = (
verbose => 1,
)
my $harness = TAP::Formatter::Console->new( \%args );
The constructor returns a new C<TAP::Formatter::Console> object. If
a L<TAP::Harness> is created with no C<formatter> a
C<TAP::Formatter::Console> is automatically created. If any of the
following options were given to TAP::Harness->new they well be passed to
this constructor which accepts an optional hashref whose allowed keys are:
=over 4
=item * C<verbosity>
Set the verbosity level.
=item * C<verbose>
Printing individual test results to STDOUT.
=item * C<timer>
Append run time for each test to output. Uses L<Time::HiRes> if available.
=item * C<failures>
Show test failures (this is a no-op if C<verbose> is selected).
=item * C<comments>
Show test comments (this is a no-op if C<verbose> is selected).
=item * C<quiet>
Suppressing some test output (mostly failures while tests are running).
=item * C<really_quiet>
Suppressing everything but the tests summary.
=item * C<silent>
Suppressing all output.
=item * C<errors>
If parse errors are found in the TAP output, a note of this will be made
in the summary report. To see all of the parse errors, set this argument to
true:
errors => 1
=item * C<directives>
If set to a true value, only test results with directives will be displayed.
This overrides other settings such as C<verbose>, C<failures>, or C<comments>.
=item * C<stdout>
A filehandle for catching standard output.
=item * C<color>
If defined specifies whether color output is desired. If C<color> is not
defined it will default to color output if color support is available on
the current platform and output is not being redirected.
=item * C<jobs>
The number of concurrent jobs this formatter will handle.
=item * C<show_count>
Boolean value. If false, disables the C<X/Y> test count which shows up while
tests are running.
=back
Any keys for which the value is C<undef> will be ignored.
=cut
# new supplied by TAP::Base
=head3 C<prepare>
Called by Test::Harness before any test output is generated.
This is an advisory and may not be called in the case where tests are
being supplied to Test::Harness by an iterator.
=cut
sub prepare {
my ( $self, @tests ) = @_;
my $longest = 0;
for 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<open_test>
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;
=cut
sub open_test {
die "Unimplemented.";
}
sub _output_success {
my ( $self, $msg ) = @_;
$self->_output($msg);
}
=head3 C<summary>
$harness->summary( $aggregate );
C<summary> prints the summary report after all tests are run. The first
argument is an aggregate to summarise. An optional second argument may
be set to a true value to indicate that the summary is being output as a
result of an interrupted test run.
=cut
sub summary {
my ( $self, $aggregate, $interrupted ) = @_;
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" );
}
$self->_failure_output("Test run interrupted!\n")
if $interrupted;
# 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");
for 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
);
# ~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
);
for 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 );
}
}
}
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);
my $wait = $parser->wait;
defined $wait or $wait = '(none)';
$self->$output(
sprintf "$test$spaces(Wstat: %s Tests: %d Failed: %d)\n",
$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 );
for 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;
PK �[�7�R R Formatter/File.pmnu �[��� package TAP::Formatter::File;
use strict;
use warnings;
use TAP::Formatter::File::Session;
use POSIX qw(strftime);
use base 'TAP::Formatter::Base';
=head1 NAME
TAP::Formatter::File - Harness output delegate for file output
=head1 VERSION
Version 3.42
=cut
our $VERSION = '3.42';
=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<< open_test >>
See L<TAP::Formatter::Base>
=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;
PK �[�z1�� � Formatter/Console.pmnu �[��� package TAP::Formatter::Console;
use strict;
use warnings;
use base 'TAP::Formatter::Base';
use POSIX qw(strftime);
=head1 NAME
TAP::Formatter::Console - Harness output delegate for default console output
=head1 VERSION
Version 3.42
=cut
our $VERSION = '3.42';
=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<< open_test >>
See L<TAP::Formatter::Base>
=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 delegate
sub _set_colors {
my ( $self, @colors ) = @_;
if ( my $colorizer = $self->_colorizer ) {
my $output_func = $self->{_output_func} ||= sub {
$self->_output(@_);
};
$colorizer->set_color( $output_func, $_ ) for @colors;
}
}
sub _failure_color {
my ($self) = @_;
return $ENV{'HARNESS_SUMMARY_COLOR_FAIL'} || 'red';
}
sub _success_color {
my ($self) = @_;
return $ENV{'HARNESS_SUMMARY_COLOR_SUCCESS'} || 'green';
}
sub _output_success {
my ( $self, $msg ) = @_;
$self->_set_colors( $self->_success_color() );
$self->_output($msg);
$self->_set_colors('reset');
}
sub _failure_output {
my $self = shift;
$self->_set_colors( $self->_failure_color() );
my $out = join '', @_;
my $has_newline = chomp $out;
$self->_output($out);
$self->_set_colors('reset');
$self->_output($/)
if $has_newline;
}
1;
PK �[���| | Formatter/Session.pmnu �[��� package TAP::Formatter::Session;
use strict;
use warnings;
use base '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.42
=cut
our $VERSION = '3.42';
=head1 METHODS
=head2 Class Methods
=head3 C<new>
my %args = (
formatter => $self,
)
my $harness = TAP::Formatter::Console::Session->new( \%args );
The constructor returns a new C<TAP::Formatter::Console::Session> object.
=over 4
=item * C<formatter>
=item * C<parser>
=item * C<name>
=item * C<show_count>
=back
=cut
sub _initialize {
my ( $self, $arg_for ) = @_;
$arg_for ||= {};
$self->SUPER::_initialize($arg_for);
my %arg_for = %$arg_for; # force a shallow copy
for my $name (@ACCESSOR) {
$self->{$name} = delete $arg_for{$name};
}
if ( !defined $self->show_count ) {
$self->{show_count} = 1; # defaults to true
}
if ( $self->show_count ) { # but may be a damned lie!
$self->{show_count} = $self->_should_show_count;
}
if ( my @props = sort keys %arg_for ) {
$self->_croak(
"Unknown arguments to " . __PACKAGE__ . "::new (@props)" );
}
return $self;
}
=head3 C<header>
Output test preamble
=head3 C<result>
Called by the harness for each line of TAP it receives.
=head3 C<close_test>
Called to close a test session.
=head3 C<clear_for_close>
Called by C<close_test> to clear the line showing test progress, or the parallel
test ruler, prior to printing the final test result.
=head3 C<time_report>
Return a formatted string about the elapsed (wall-clock) time
and about the consumed CPU time.
=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 '
);
}
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");
}
sub _make_ok_line {
my ( $self, $suffix ) = @_;
return "ok$suffix\n";
}
sub time_report {
my ( $self, $formatter, $parser ) = @_;
my @time_report;
if ( $formatter->timer ) {
my $start_time = $parser->start_time;
my $end_time = $parser->end_time;
if ( defined $start_time and defined $end_time ) {
my $elapsed = $end_time - $start_time;
push @time_report,
$self->time_is_hires
? sprintf( ' %8d ms', $elapsed * 1000 )
: sprintf( ' %8s s', $elapsed || '<1' );
}
my $start_times = $parser->start_times();
my $end_times = $parser->end_times();
my $usr = $end_times->[0] - $start_times->[0];
my $sys = $end_times->[1] - $start_times->[1];
my $cusr = $end_times->[2] - $start_times->[2];
my $csys = $end_times->[3] - $start_times->[3];
push @time_report,
sprintf('(%5.2f usr %5.2f sys + %5.2f cusr %5.2f csys = %5.2f CPU)',
$usr, $sys, $cusr, $csys,
$usr + $sys + $cusr + $csys);
}
return "@time_report";
}
1;
PK �[�·�� � Formatter/Console/Session.pmnu �[��� package TAP::Formatter::Console::Session;
use strict;
use warnings;
use base 'TAP::Formatter::Session';
my @ACCESSOR;
BEGIN {
my @CLOSURE_BINDING = qw( header result clear_for_close close_test );
for my $method (@CLOSURE_BINDING) {
no strict 'refs';
*$method = sub {
my $self = shift;
return ( $self->{_closures} ||= $self->_closures )->{$method}
->(@_);
};
}
}
=head1 NAME
TAP::Formatter::Console::Session - Harness output delegate for default console output
=head1 VERSION
Version 3.42
=cut
our $VERSION = '3.42';
=head1 DESCRIPTION
This provides console orientated output formatting for TAP::Harness.
=cut
sub _get_output_result {
my $self = shift;
my @color_map = (
{ test => sub { $_->is_test && !$_->is_ok },
colors => ['red'],
},
{ test => sub { $_->is_test && $_->has_skip },
colors => [
'white',
'on_blue'
],
},
{ test => sub { $_->is_test && $_->has_todo },
colors => ['yellow'],
},
);
my $formatter = $self->formatter;
my $parser = $self->parser;
return $formatter->_colorizer
? sub {
my $result = shift;
for my $col (@color_map) {
local $_ = $result;
if ( $col->{test}->() ) {
$formatter->_set_colors( @{ $col->{colors} } );
last;
}
}
$formatter->_output( $self->_format_for_output($result) );
$formatter->_set_colors('reset');
}
: sub {
$formatter->_output( $self->_format_for_output(shift) );
};
}
sub _closures {
my $self = shift;
my $parser = $self->parser;
my $formatter = $self->formatter;
my $pretty = $formatter->_format_name( $self->name );
my $show_count = $self->show_count;
my $really_quiet = $formatter->really_quiet;
my $quiet = $formatter->quiet;
my $verbose = $formatter->verbose;
my $directives = $formatter->directives;
my $failures = $formatter->failures;
my $comments = $formatter->comments;
my $output_result = $self->_get_output_result;
my $output = '_output';
my $plan = '';
my $newline_printed = 0;
my $last_status_printed = 0;
return {
header => sub {
$formatter->_output($pretty)
unless $really_quiet;
},
result => sub {
my $result = shift;
if ( $result->is_bailout ) {
$formatter->_failure_output(
"Bailout called. Further testing stopped: "
. $result->explanation
. "\n" );
}
return if $really_quiet;
my $is_test = $result->is_test;
# These are used in close_test - but only if $really_quiet
# is false - so it's safe to only set them here unless that
# relationship changes.
if ( !$plan ) {
my $planned = $parser->tests_planned || '?';
$plan = "/$planned ";
}
$output = $formatter->_get_output_method($parser);
if ( $show_count and $is_test ) {
my $number = $result->number;
my $now = CORE::time;
# Print status roughly once per second.
# We will always get the first number as a side effect of
# $last_status_printed starting with the value 0, which $now
# will never be. (Unless someone sets their clock to 1970)
if ( $last_status_printed != $now ) {
$formatter->$output("\r$pretty$number$plan");
$last_status_printed = $now;
}
}
if (!$quiet
&& ( $verbose
|| ( $is_test && $failures && !$result->is_ok )
|| ( $comments && $result->is_comment )
|| ( $directives && $result->has_directive ) )
)
{
unless ($newline_printed) {
$formatter->_output("\n");
$newline_printed = 1;
}
$output_result->($result);
$formatter->_output("\n");
}
},
clear_for_close => sub {
my $spaces
= ' ' x length( '.' . $pretty . $plan . $parser->tests_run );
$formatter->$output("\r$spaces");
},
close_test => sub {
if ( $show_count && !$really_quiet ) {
$self->clear_for_close;
$formatter->$output("\r$pretty");
}
# Avoid circular references
$self->parser(undef);
$self->{_closures} = {};
return if $really_quiet;
if ( my $skip_all = $parser->skip_all ) {
$formatter->_output("skipped: $skip_all\n");
}
elsif ( $parser->has_problems ) {
$self->_output_test_failure($parser);
}
else {
my $time_report = $self->time_report($formatter, $parser);
$formatter->_output( $self->_make_ok_line($time_report) );
}
},
};
}
=head2 C<< clear_for_close >>
=head2 C<< close_test >>
=head2 C<< header >>
=head2 C<< result >>
=cut
1;
PK �[|0sW W $ Formatter/Console/ParallelSession.pmnu �[��� package TAP::Formatter::Console::ParallelSession;
use strict;
use warnings;
use File::Spec;
use File::Path;
use Carp;
use base 'TAP::Formatter::Console::Session';
use constant WIDTH => 72; # Because Eric says
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,
fails => 0,
};
}
=head1 NAME
TAP::Formatter::Console::ParallelSession - Harness output delegate for parallel console output
=head1 VERSION
Version 3.42
=cut
our $VERSION = '3.42';
=head1 DESCRIPTION
This provides console orientated output formatting for L<TAP::Harness>
when run with multiple L<TAP::Harness/jobs>.
=head1 SYNOPSIS
=cut
=head1 METHODS
=head2 Class Methods
=head3 C<header>
Output test preamble
=cut
sub header {
}
sub _clear_ruler {
my $self = shift;
$self->formatter->_output( "\r" . ( ' ' x WIDTH ) . "\r" );
}
my $now = 0;
my $start;
my $trailer = '... )===';
my $chop_length = WIDTH - length $trailer;
sub _output_ruler {
my ( $self, $refresh ) = @_;
my $new_now = time;
return if $new_now == $now and !$refresh;
$now = $new_now;
$start ||= $now;
my $formatter = $self->formatter;
return if $formatter->really_quiet;
my $context = $shared{$formatter};
my $ruler = sprintf '===( %7d;%d ', $context->{tests}, $now - $start;
for my $active ( @{ $context->{active} } ) {
my $parser = $active->parser;
my $tests = $parser->tests_run;
my $planned = $parser->tests_planned || '?';
$ruler .= sprintf '%' . length($planned) . "d/$planned ", $tests;
}
chop $ruler; # Remove a trailing space
$ruler .= ')===';
if ( length $ruler > WIDTH ) {
$ruler =~ s/(.{$chop_length}).*/$1$trailer/o;
}
else {
$ruler .= '=' x ( WIDTH - length($ruler) );
}
$formatter->_output("\r$ruler");
}
=head3 C<result>
Called by the harness for each line of TAP it receives .
=cut
sub result {
my ( $self, $result ) = @_;
my $formatter = $self->formatter;
# my $really_quiet = $formatter->really_quiet;
# my $show_count = $self->_should_show_count;
if ( $result->is_test ) {
my $context = $shared{$formatter};
$context->{tests}++;
my $active = $context->{active};
if ( @$active == 1 ) {
# There is only one test, so use the serial output format.
return $self->SUPER::result($result);
}
$self->_output_ruler( $self->parser->tests_run == 1 );
}
elsif ( $result->is_bailout ) {
$formatter->_failure_output(
"Bailout called. Further testing stopped: "
. $result->explanation
. "\n" );
}
}
=head3 C<clear_for_close>
=cut
sub clear_for_close {
my $self = shift;
my $formatter = $self->formatter;
return if $formatter->really_quiet;
my $context = $shared{$formatter};
if ( @{ $context->{active} } == 1 ) {
$self->SUPER::clear_for_close;
}
else {
$self->_clear_ruler;
}
}
=head3 C<close_test>
=cut
sub close_test {
my $self = shift;
my $name = $self->name;
my $parser = $self->parser;
my $formatter = $self->formatter;
my $context = $shared{$formatter};
$self->SUPER::close_test;
my $active = $context->{active};
my @pos = grep { $active->[$_]->name eq $name } 0 .. $#$active;
die "Can't find myself" unless @pos;
splice @$active, $pos[0], 1;
if ( @$active > 1 ) {
$self->_output_ruler(1);
}
elsif ( @$active == 1 ) {
# Print out "test/name.t ...."
$active->[0]->SUPER::header;
}
else {
# $self->formatter->_output("\n");
delete $shared{$formatter};
}
}
1;
PK �[E�Tb Formatter/Color.pmnu �[��� package TAP::Formatter::Color;
use strict;
use warnings;
use constant IS_WIN32 => ( $^O =~ /^(MS)?Win32$/ );
use base 'TAP::Object';
my $NO_COLOR;
BEGIN {
$NO_COLOR = 0;
eval 'require Term::ANSIColor';
if ($@) {
$NO_COLOR = $@;
};
if (IS_WIN32) {
eval 'use Win32::Console::ANSI';
if ($@) {
$NO_COLOR = $@;
}
};
if ($NO_COLOR) {
*set_color = sub { };
} else {
*set_color = sub {
my ( $self, $output, $color ) = @_;
$output->( Term::ANSIColor::color($color) );
};
}
}
=head1 NAME
TAP::Formatter::Color - Run Perl test scripts with color
=head1 VERSION
Version 3.42
=cut
our $VERSION = '3.42';
=head1 DESCRIPTION
Note that this harness is I<experimental>. You may not like the colors I've
chosen and I haven't yet provided an easy way to override them.
This test harness is the same as L<TAP::Harness>, but test results are output
in 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 in
white.
If L<Term::ANSIColor> cannot be found (and L<Win32::Console::ANSI> if running
under Windows) tests will be run without color.
=head1 SYNOPSIS
use TAP::Formatter::Color;
my $harness = TAP::Formatter::Color->new( \%args );
$harness->runtests(@tests);
=head1 METHODS
=head2 Class Methods
=head3 C<new>
The constructor returns a new C<TAP::Formatter::Color> object. If
L<Term::ANSIColor> 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<can_color>
Test::Formatter::Color->can_color()
Returns a boolean indicating whether or not this module can actually
generate colored output. This will be false if it could not load the
modules needed for the current platform.
=cut
sub can_color {
return !$NO_COLOR;
}
=head3 C<set_color>
Set the output color.
=cut
1;
PK �[eSUp� � Formatter/File/Session.pmnu �[��� package TAP::Formatter::File::Session;
use strict;
use warnings;
use base 'TAP::Formatter::Session';
=head1 NAME
TAP::Formatter::File::Session - Harness output delegate for file output
=head1 VERSION
Version 3.42
=cut
our $VERSION = '3.42';
=head1 DESCRIPTION
This provides file orientated output formatting for L<TAP::Harness>.
It is particularly important when running with parallel tests, as it
ensures that test results are not interleaved, even when run
verbosely.
=cut
=head1 METHODS
=head2 result
Stores results for later output, all together.
=cut
sub result {
my $self = shift;
my $result = shift;
my $parser = $self->parser;
my $formatter = $self->formatter;
if ( $result->is_bailout ) {
$formatter->_failure_output(
"Bailout called. Further testing stopped: "
. $result->explanation
. "\n" );
return;
}
if (!$formatter->quiet
&& ( $formatter->verbose
|| ( $result->is_test && $formatter->failures && !$result->is_ok )
|| ( $formatter->comments && $result->is_comment )
|| ( $result->has_directive && $formatter->directives ) )
)
{
$self->{results} .= $self->_format_for_output($result) . "\n";
}
}
=head2 close_test
When the test file finishes, outputs the summary, together.
=cut
sub close_test {
my $self = shift;
# Avoid circular references
$self->parser(undef);
my $parser = $self->parser;
my $formatter = $self->formatter;
my $pretty = $formatter->_format_name( $self->name );
return if $formatter->really_quiet;
if ( my $skip_all = $parser->skip_all ) {
$formatter->_output( $pretty . "skipped: $skip_all\n" );
}
elsif ( $parser->has_problems ) {
$formatter->_output(
$pretty . ( $self->{results} ? "\n" . $self->{results} : "\n" ) );
$self->_output_test_failure($parser);
}
else {
my $time_report = $self->time_report($formatter, $parser);
$formatter->_output( $pretty
. ( $self->{results} ? "\n" . $self->{results} : "" )
. $self->_make_ok_line($time_report) );
}
}
1;
PK �[�p p Parser/Result/YAML.pmnu �[��� package TAP::Parser::Result::YAML;
use strict;
use warnings;
use base 'TAP::Parser::Result';
=head1 NAME
TAP::Parser::Result::YAML - YAML result token.
=head1 VERSION
Version 3.42
=cut
our $VERSION = '3.42';
=head1 DESCRIPTION
This is a subclass of L<TAP::Parser::Result>. A token of this class will be
returned if a YAML block is encountered.
1..1
ok 1 - woo hooo!
C<1..1> is the plan. Gotta have a plan.
=head1 OVERRIDDEN METHODS
Mainly listed here to shut up the pitiful screams of the pod coverage tests.
They keep me awake at night.
=over 4
=item * C<as_string>
=item * C<raw>
=back
=cut
##############################################################################
=head2 Instance Methods
=head3 C<data>
if ( $result->is_yaml ) {
print $result->data;
}
Return the parsed YAML data for this result
=cut
sub data { shift->{data} }
1;
PK �[���� � Parser/Result/Comment.pmnu �[��� package TAP::Parser::Result::Comment;
use strict;
use warnings;
use base 'TAP::Parser::Result';
=head1 NAME
TAP::Parser::Result::Comment - Comment result token.
=head1 VERSION
Version 3.42
=cut
our $VERSION = '3.42';
=head1 DESCRIPTION
This is a subclass of L<TAP::Parser::Result>. A token of this class will be
returned if a comment line is encountered.
1..1
ok 1 - woo hooo!
# this is a comment
=head1 OVERRIDDEN METHODS
Mainly listed here to shut up the pitiful screams of the pod coverage tests.
They keep me awake at night.
=over 4
=item * C<as_string>
Note that this method merely returns the comment preceded by a '# '.
=back
=cut
##############################################################################
=head2 Instance Methods
=head3 C<comment>
if ( $result->is_comment ) {
my $comment = $result->comment;
print "I have something to say: $comment";
}
=cut
sub comment { shift->{comment} }
sub as_string { shift->{raw} }
1;
PK �[�-�&� � Parser/Result/Pragma.pmnu �[��� package TAP::Parser::Result::Pragma;
use strict;
use warnings;
use base 'TAP::Parser::Result';
=head1 NAME
TAP::Parser::Result::Pragma - TAP pragma token.
=head1 VERSION
Version 3.42
=cut
our $VERSION = '3.42';
=head1 DESCRIPTION
This is a subclass of L<TAP::Parser::Result>. A token of this class will be
returned if a pragma is encountered.
TAP version 13
pragma +strict, -foo
Pragmas are only supported from TAP version 13 onwards.
=head1 OVERRIDDEN METHODS
Mainly listed here to shut up the pitiful screams of the pod coverage tests.
They keep me awake at night.
=over 4
=item * C<as_string>
=item * C<raw>
=back
=cut
##############################################################################
=head2 Instance Methods
=head3 C<pragmas>
if ( $result->is_pragma ) {
@pragmas = $result->pragmas;
}
=cut
sub pragmas {
my @pragmas = @{ shift->{pragmas} };
return wantarray ? @pragmas : \@pragmas;
}
1;
PK �[k*r�| | Parser/Result/Bailout.pmnu �[��� package TAP::Parser::Result::Bailout;
use strict;
use warnings;
use base 'TAP::Parser::Result';
=head1 NAME
TAP::Parser::Result::Bailout - Bailout result token.
=head1 VERSION
Version 3.42
=cut
our $VERSION = '3.42';
=head1 DESCRIPTION
This is a subclass of L<TAP::Parser::Result>. A token of this class will be
returned if a bail out line is encountered.
1..5
ok 1 - woo hooo!
Bail out! Well, so much for "woo hooo!"
=head1 OVERRIDDEN METHODS
Mainly listed here to shut up the pitiful screams of the pod coverage tests.
They keep me awake at night.
=over 4
=item * C<as_string>
=back
=cut
##############################################################################
=head2 Instance Methods
=head3 C<explanation>
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" via
this method. The explanation is the text after the mystical "Bail out!" words
which appear in the tap output.
=cut
sub explanation { shift->{bailout} }
sub as_string { shift->{bailout} }
1;
PK �[��� Parser/Result/Plan.pmnu �[��� package TAP::Parser::Result::Plan;
use strict;
use warnings;
use base 'TAP::Parser::Result';
=head1 NAME
TAP::Parser::Result::Plan - Plan result token.
=head1 VERSION
Version 3.42
=cut
our $VERSION = '3.42';
=head1 DESCRIPTION
This is a subclass of L<TAP::Parser::Result>. A token of this class will be
returned if a plan line is encountered.
1..1
ok 1 - woo hooo!
C<1..1> is the plan. Gotta have a plan.
=head1 OVERRIDDEN METHODS
Mainly listed here to shut up the pitiful screams of the pod coverage tests.
They keep me awake at night.
=over 4
=item * C<as_string>
=item * C<raw>
=back
=cut
##############################################################################
=head2 Instance Methods
=head3 C<plan>
if ( $result->is_plan ) {
print $result->plan;
}
This is merely a synonym for C<as_string>.
=cut
sub plan { '1..' . shift->{tests_planned} }
##############################################################################
=head3 C<tests_planned>
my $planned = $result->tests_planned;
Returns the number of tests planned. For example, a plan of C<1..17> will
cause this method to return '17'.
=cut
sub tests_planned { shift->{tests_planned} }
##############################################################################
=head3 C<directive>
my $directive = $plan->directive;
If a SKIP directive is included with the plan, this method will return it.
1..0 # SKIP: why bother?
=cut
sub directive { shift->{directive} }
##############################################################################
=head3 C<has_skip>
if ( $result->has_skip ) { ... }
Returns a boolean value indicating whether or not this test has a SKIP
directive.
=head3 C<explanation>
my $explanation = $plan->explanation;
If a SKIP directive was included with the plan, this method will return the
explanation, if any.
=cut
sub explanation { shift->{explanation} }
=head3 C<todo_list>
my $todo = $result->todo_list;
for ( @$todo ) {
...
}
=cut
sub todo_list { shift->{todo_list} }
1;
PK �[/�ߐ� � Parser/Result/Unknown.pmnu �[��� package TAP::Parser::Result::Unknown;
use strict;
use warnings;
use base 'TAP::Parser::Result';
=head1 NAME
TAP::Parser::Result::Unknown - Unknown result token.
=head1 VERSION
Version 3.42
=cut
our $VERSION = '3.42';
=head1 DESCRIPTION
This is a subclass of L<TAP::Parser::Result>. A token of this class will be
returned if the parser does not recognize the token line. For example:
1..5
VERSION 7
ok 1 - woo hooo!
... woo hooo! is cool!
In the above "TAP", the second and fourth lines will generate "Unknown"
tokens.
=head1 OVERRIDDEN METHODS
Mainly listed here to shut up the pitiful screams of the pod coverage tests.
They keep me awake at night.
=over 4
=item * C<as_string>
=item * C<raw>
=back
=cut
1;
PK �[|Az�� � Parser/Result/Test.pmnu �[��� package TAP::Parser::Result::Test;
use strict;
use warnings;
use base 'TAP::Parser::Result';
=head1 NAME
TAP::Parser::Result::Test - Test result token.
=head1 VERSION
Version 3.42
=cut
our $VERSION = '3.42';
=head1 DESCRIPTION
This is a subclass of L<TAP::Parser::Result>. A token of this class will be
returned if a test line is encountered.
1..1
ok 1 - woo hooo!
=head1 OVERRIDDEN METHODS
This class is the workhorse of the L<TAP::Parser> system. Most TAP lines will
be test lines and if C<< $result->is_test >>, then you have a bunch of methods
at your disposal.
=head2 Instance Methods
=cut
##############################################################################
=head3 C<ok>
my $ok = $result->ok;
Returns the literal text of the C<ok> or C<not ok> status.
=cut
sub ok { shift->{ok} }
##############################################################################
=head3 C<number>
my $test_number = $result->number;
Returns the number of the test, even if the original TAP output did not supply
that number.
=cut
sub number { shift->{test_num} }
sub _number {
my ( $self, $number ) = @_;
$self->{test_num} = $number;
}
##############################################################################
=head3 C<description>
my $description = $result->description;
Returns the description of the test, if any. This is the portion after the
test number but before the directive.
=cut
sub description { shift->{description} }
##############################################################################
=head3 C<directive>
my $directive = $result->directive;
Returns either C<TODO> or C<SKIP> if either directive was present for a test
line.
=cut
sub directive { shift->{directive} }
##############################################################################
=head3 C<explanation>
my $explanation = $result->explanation;
If a test had either a C<TODO> or C<SKIP> directive, this method will return
the accompanying explanation, if present.
not ok 17 - 'Pigs can fly' # TODO not enough acid
For the above line, the explanation is I<not enough acid>.
=cut
sub explanation { shift->{explanation} }
##############################################################################
=head3 C<is_ok>
if ( $result->is_ok ) { ... }
Returns a boolean value indicating whether or not the test passed. Remember
that for TODO tests, the test always passes.
If the test is unplanned, this method will always return false. See
C<is_unplanned>.
=cut
sub is_ok {
my $self = shift;
return if $self->is_unplanned;
# TODO directives reverse the sense of a test.
return $self->has_todo ? 1 : $self->ok !~ /not/;
}
##############################################################################
=head3 C<is_actual_ok>
if ( $result->is_actual_ok ) { ... }
Returns a boolean value indicating whether or not the test passed, regardless
of its TODO status.
=cut
sub is_actual_ok {
my $self = shift;
return $self->{ok} !~ /not/;
}
##############################################################################
=head3 C<actual_passed>
Deprecated. Please use C<is_actual_ok> instead.
=cut
sub actual_passed {
warn 'actual_passed() is deprecated. Please use "is_actual_ok()"';
goto &is_actual_ok;
}
##############################################################################
=head3 C<todo_passed>
if ( $test->todo_passed ) {
# test unexpectedly succeeded
}
If this is a TODO test and an 'ok' line, this method returns true.
Otherwise, it will always return false (regardless of passing status on
non-todo tests).
This is used to track which tests unexpectedly succeeded.
=cut
sub todo_passed {
my $self = shift;
return $self->has_todo && $self->is_actual_ok;
}
##############################################################################
=head3 C<todo_failed>
# deprecated in favor of 'todo_passed'. This method was horribly misnamed.
This was a badly misnamed method. It indicates which TODO tests unexpectedly
succeeded. Will now issue a warning and call C<todo_passed>.
=cut
sub todo_failed {
warn 'todo_failed() is deprecated. Please use "todo_passed()"';
goto &todo_passed;
}
##############################################################################
=head3 C<has_skip>
if ( $result->has_skip ) { ... }
Returns a boolean value indicating whether or not this test has a SKIP
directive.
=head3 C<has_todo>
if ( $result->has_todo ) { ... }
Returns a boolean value indicating whether or not this test has a TODO
directive.
=head3 C<as_string>
print $result->as_string;
This method prints the test as a string. It will probably be similar, but
not necessarily identical, to the original test line. Directives are
capitalized, some whitespace may be trimmed and a test number will be added if
it was not present in the original line. If you need the original text of the
test line, use the C<raw> method.
=cut
sub as_string {
my $self = shift;
my $string = $self->ok . " " . $self->number;
if ( my $description = $self->description ) {
$string .= " $description";
}
if ( my $directive = $self->directive ) {
my $explanation = $self->explanation;
$string .= " # $directive $explanation";
}
return $string;
}
##############################################################################
=head3 C<is_unplanned>
if ( $test->is_unplanned ) { ... }
$test->is_unplanned(1);
If a test number is greater than the number of planned tests, this method will
return true. Unplanned tests will I<always> return false for C<is_ok>,
regardless of whether or not the test C<has_todo>.
Note that if tests have a trailing plan, it is not possible to set this
property for unplanned tests as we do not know it's unplanned until the plan
is reached:
print <<'END';
ok 1
ok 2
1..1
END
=cut
sub is_unplanned {
my $self = shift;
return ( $self->{unplanned} || '' ) unless @_;
$self->{unplanned} = !!shift;
return $self;
}
1;
PK �[C;M� � Parser/Result/Version.pmnu �[��� package TAP::Parser::Result::Version;
use strict;
use warnings;
use base 'TAP::Parser::Result';
=head1 NAME
TAP::Parser::Result::Version - TAP syntax version token.
=head1 VERSION
Version 3.42
=cut
our $VERSION = '3.42';
=head1 DESCRIPTION
This is a subclass of L<TAP::Parser::Result>. A token of this class will be
returned if a version line is encountered.
TAP version 13
ok 1
not ok 2
The first version of TAP to include an explicit version number is 13.
=head1 OVERRIDDEN METHODS
Mainly listed here to shut up the pitiful screams of the pod coverage tests.
They keep me awake at night.
=over 4
=item * C<as_string>
=item * C<raw>
=back
=cut
##############################################################################
=head2 Instance Methods
=head3 C<version>
if ( $result->is_version ) {
print $result->version;
}
This is merely a synonym for C<as_string>.
=cut
sub version { shift->{version} }
1;
PK �[�$� � Parser/Scheduler/Spinner.pmnu �[��� package TAP::Parser::Scheduler::Spinner;
use strict;
use warnings;
use Carp;
=head1 NAME
TAP::Parser::Scheduler::Spinner - A no-op job.
=head1 VERSION
Version 3.42
=cut
our $VERSION = '3.42';
=head1 SYNOPSIS
use TAP::Parser::Scheduler::Spinner;
=head1 DESCRIPTION
A no-op job. Returned by C<TAP::Parser::Scheduler> as an instruction to
the harness to spin (keep executing tests) while the scheduler can't
return a real job.
=head1 METHODS
=head2 Class Methods
=head3 C<new>
my $job = TAP::Parser::Scheduler::Spinner->new;
Ignores any arguments and returns a new C<TAP::Parser::Scheduler::Spinner> object.
=cut
sub new { bless {}, shift }
=head2 Instance Methods
=head3 C<is_spinner>
Returns true indicating that is a 'spinner' job. Spinners are returned
when the scheduler still has pending jobs but can't (because of locking)
return one right now.
=cut
sub is_spinner {1}
=head1 SEE ALSO
L<TAP::Parser::Scheduler>, L<TAP::Parser::Scheduler::Job>
=cut
1;
PK �[i"dj� � Parser/Scheduler/Job.pmnu �[��� package TAP::Parser::Scheduler::Job;
use strict;
use warnings;
use Carp;
=head1 NAME
TAP::Parser::Scheduler::Job - A single testing job.
=head1 VERSION
Version 3.42
=cut
our $VERSION = '3.42';
=head1 SYNOPSIS
use TAP::Parser::Scheduler::Job;
=head1 DESCRIPTION
Represents a single test 'job'.
=head1 METHODS
=head2 Class Methods
=head3 C<new>
my $job = TAP::Parser::Scheduler::Job->new(
$filename, $description
);
Given the filename and description of a test as scalars, returns a new
L<TAP::Parser::Scheduler::Job> object.
=cut
sub new {
my ( $class, $name, $desc, @ctx ) = @_;
return bless {
filename => $name,
description => $desc,
@ctx ? ( context => \@ctx ) : (),
}, $class;
}
=head2 Instance Methods
=head3 C<on_finish>
$self->on_finish(\&method).
Register a closure to be called when this job is destroyed. The callback
will be passed the C<TAP::Parser::Scheduler::Job> object as it's only argument.
=cut
sub on_finish {
my ( $self, $cb ) = @_;
$self->{on_finish} = $cb;
}
=head3 C<finish>
$self->finish;
Called when a job is complete to unlock it. If a callback has been registered
with C<on_finish>, it calls it. Otherwise, it does nothing.
=cut
sub finish {
my $self = shift;
if ( my $cb = $self->{on_finish} ) {
$cb->($self);
}
}
=head2 Attributes
$self->filename;
$self->description;
$self->context;
These are all "getters" which return the data set for these attributes during object construction.
=head3 C<filename>
=head3 C<description>
=head3 C<context>
=cut
sub filename { shift->{filename} }
sub description { shift->{description} }
sub context { @{ shift->{context} || [] } }
=head3 C<as_array_ref>
For backwards compatibility in callbacks.
=cut
sub as_array_ref {
my $self = shift;
return [ $self->filename, $self->description, $self->{context} ||= [] ];
}
=head3 C<is_spinner>
$self->is_spinner;
Returns false indicating that this is a real job rather than a
'spinner'. Spinners are returned when the scheduler still has pending
jobs but can't (because of locking) return one right now.
=cut
sub is_spinner {0}
1;
PK �[*H$ $ Parser/Aggregator.pmnu �[��� package TAP::Parser::Aggregator;
use strict;
use warnings;
use Benchmark;
use base 'TAP::Object';
=head1 NAME
TAP::Parser::Aggregator - Aggregate TAP::Parser results
=head1 VERSION
Version 3.42
=cut
our $VERSION = '3.42';
=head1 SYNOPSIS
use TAP::Parser::Aggregator;
my $aggregate = TAP::Parser::Aggregator->new;
$aggregate->add( 't/00-load.t', $load_parser );
$aggregate->add( 't/10-lex.t', $lex_parser );
my $summary = <<'END_SUMMARY';
Passed: %s
Failed: %s
Unexpectedly succeeded: %s
END_SUMMARY
printf $summary,
scalar $aggregate->passed,
scalar $aggregate->failed,
scalar $aggregate->todo_passed;
=head1 DESCRIPTION
C<TAP::Parser::Aggregator> collects parser objects and allows
reporting/querying their aggregate results.
=head1 METHODS
=head2 Class Methods
=head3 C<new>
my $aggregate = TAP::Parser::Aggregator->new;
Returns a new C<TAP::Parser::Aggregator> object.
=cut
# new() implementation supplied by TAP::Object
my %SUMMARY_METHOD_FOR;
BEGIN { # install summary methods
%SUMMARY_METHOD_FOR = map { $_ => $_ } qw(
failed
parse_errors
passed
skipped
todo
todo_passed
total
wait
exit
);
$SUMMARY_METHOD_FOR{total} = 'tests_run';
$SUMMARY_METHOD_FOR{planned} = 'tests_planned';
for my $method ( keys %SUMMARY_METHOD_FOR ) {
next if 'total' eq $method;
no strict 'refs';
*$method = sub {
my $self = shift;
return wantarray
? @{ $self->{"descriptions_for_$method"} }
: $self->{$method};
};
}
} # end install summary methods
sub _initialize {
my ($self) = @_;
$self->{parser_for} = {};
$self->{parse_order} = [];
for my $summary ( keys %SUMMARY_METHOD_FOR ) {
$self->{$summary} = 0;
next if 'total' eq $summary;
$self->{"descriptions_for_$summary"} = [];
}
return $self;
}
##############################################################################
=head2 Instance Methods
=head3 C<add>
$aggregate->add( $description => $parser );
The C<$description> is usually a test file name (but only by
convention.) It is used as a unique identifier (see e.g.
L<"parsers">.) Reusing a description is a fatal error.
The C<$parser> is a L<TAP::Parser|TAP::Parser> object.
=cut
sub add {
my ( $self, $description, $parser ) = @_;
if ( exists $self->{parser_for}{$description} ) {
$self->_croak( "You already have a parser for ($description)."
. " Perhaps you have run the same test twice." );
}
push @{ $self->{parse_order} } => $description;
$self->{parser_for}{$description} = $parser;
while ( my ( $summary, $method ) = each %SUMMARY_METHOD_FOR ) {
# Slightly nasty. Instead we should maybe have 'cooked' accessors
# for results that may be masked by the parser.
next
if ( $method eq 'exit' || $method eq 'wait' )
&& $parser->ignore_exit;
if ( my $count = $parser->$method() ) {
$self->{$summary} += $count;
push @{ $self->{"descriptions_for_$summary"} } => $description;
}
}
return $self;
}
##############################################################################
=head3 C<parsers>
my $count = $aggregate->parsers;
my @parsers = $aggregate->parsers;
my @parsers = $aggregate->parsers(@descriptions);
In scalar context without arguments, this method returns the number of parsers
aggregated. In list context without arguments, returns the parsers in the
order they were added.
If C<@descriptions> is given, these correspond to the keys used in each
call to the add() method. Returns an array of the requested parsers (in
the requested order) in list context or an array reference in scalar
context.
Requesting an unknown identifier is a fatal error.
=cut
sub parsers {
my $self = shift;
return $self->_get_parsers(@_) if @_;
my $descriptions = $self->{parse_order};
my @parsers = @{ $self->{parser_for} }{@$descriptions};
# Note: Because of the way context works, we must assign the parsers to
# the @parsers array or else this method does not work as documented.
return @parsers;
}
sub _get_parsers {
my ( $self, @descriptions ) = @_;
my @parsers;
for my $description (@descriptions) {
$self->_croak("A parser for ($description) could not be found")
unless exists $self->{parser_for}{$description};
push @parsers => $self->{parser_for}{$description};
}
return wantarray ? @parsers : \@parsers;
}
=head3 C<descriptions>
Get an array of descriptions in the order in which they were added to
the aggregator.
=cut
sub descriptions { @{ shift->{parse_order} || [] } }
=head3 C<start>
Call C<start> immediately before adding any results to the aggregator.
Among other times it records the start time for the test run.
=cut
sub start {
my $self = shift;
$self->{start_time} = Benchmark->new;
}
=head3 C<stop>
Call C<stop> immediately after adding all test results to the aggregator.
=cut
sub stop {
my $self = shift;
$self->{end_time} = Benchmark->new;
}
=head3 C<elapsed>
Elapsed returns a L<Benchmark> object that represents the running time
of the aggregated tests. In order for C<elapsed> to be valid you must
call C<start> before running the tests and C<stop> immediately
afterwards.
=cut
sub elapsed {
my $self = shift;
require Carp;
Carp::croak
q{Can't call elapsed without first calling start and then stop}
unless defined $self->{start_time} && defined $self->{end_time};
return timediff( $self->{end_time}, $self->{start_time} );
}
=head3 C<elapsed_timestr>
Returns a formatted string representing the runtime returned by
C<elapsed()>. This lets the caller not worry about Benchmark.
=cut
sub elapsed_timestr {
my $self = shift;
my $elapsed = $self->elapsed;
return timestr($elapsed);
}
=head3 C<all_passed>
Return true if all the tests passed and no parse errors were detected.
=cut
sub all_passed {
my $self = shift;
return
$self->total
&& $self->total == $self->passed
&& !$self->has_errors;
}
=head3 C<get_status>
Get a single word describing the status of the aggregated tests.
Depending on the outcome of the tests returns 'PASS', 'FAIL' or
'NOTESTS'. This token is understood by L<CPAN::Reporter>.
=cut
sub get_status {
my $self = shift;
my $total = $self->total;
my $passed = $self->passed;
return
( $self->has_errors || $total != $passed ) ? 'FAIL'
: $total ? 'PASS'
: 'NOTESTS';
}
##############################################################################
=head2 Summary methods
Each of the following methods will return the total number of corresponding
tests if called in scalar context. If called in list context, returns the
descriptions of the parsers which contain the corresponding tests (see C<add>
for an explanation of description.
=over 4
=item * failed
=item * parse_errors
=item * passed
=item * planned
=item * skipped
=item * todo
=item * todo_passed
=item * wait
=item * exit
=back
For example, to find out how many tests unexpectedly succeeded (TODO tests
which passed when they shouldn't):
my $count = $aggregate->todo_passed;
my @descriptions = $aggregate->todo_passed;
Note that C<wait> and C<exit> are the totals of the wait and exit
statuses of each of the tests. These values are totalled only to provide
a true value if any of them are non-zero.
=cut
##############################################################################
=head3 C<total>
my $tests_run = $aggregate->total;
Returns the total number of tests run.
=cut
sub total { shift->{total} }
##############################################################################
=head3 C<has_problems>
if ( $parser->has_problems ) {
...
}
Identical to C<has_errors>, but also returns true if any TODO tests
unexpectedly succeeded. This is more akin to "warnings".
=cut
sub has_problems {
my $self = shift;
return $self->todo_passed
|| $self->has_errors;
}
##############################################################################
=head3 C<has_errors>
if ( $parser->has_errors ) {
...
}
Returns true if I<any> of the parsers failed. This includes:
=over 4
=item * Failed tests
=item * Parse errors
=item * Bad exit or wait status
=back
=cut
sub has_errors {
my $self = shift;
return
$self->failed
|| $self->parse_errors
|| $self->exit
|| $self->wait;
}
##############################################################################
=head3 C<todo_failed>
# deprecated in favor of 'todo_passed'. This method was horribly misnamed.
This was a badly misnamed method. It indicates which TODO tests unexpectedly
succeeded. Will now issue a warning and call C<todo_passed>.
=cut
sub todo_failed {
warn
'"todo_failed" is deprecated. Please use "todo_passed". See the docs.';
goto &todo_passed;
}
=head1 See Also
L<TAP::Parser>
L<TAP::Harness>
=cut
1;
PK �[�=^2= 2= Parser/Grammar.pmnu �[��� package TAP::Parser::Grammar;
use strict;
use warnings;
use TAP::Parser::ResultFactory ();
use TAP::Parser::YAMLish::Reader ();
use base 'TAP::Object';
=head1 NAME
TAP::Parser::Grammar - A grammar for the Test Anything Protocol.
=head1 VERSION
Version 3.42
=cut
our $VERSION = '3.42';
=head1 SYNOPSIS
use TAP::Parser::Grammar;
my $grammar = $self->make_grammar({
iterator => $tap_parser_iterator,
parser => $tap_parser,
version => 12,
});
my $result = $grammar->tokenize;
=head1 DESCRIPTION
C<TAP::Parser::Grammar> tokenizes lines from a L<TAP::Parser::Iterator> and
constructs L<TAP::Parser::Result> subclasses to represent the tokens.
Do not attempt to use this class directly. It won't make sense. It's mainly
here to ensure that we will be able to have pluggable grammars when TAP is
expanded at some future date (plus, this stuff was really cluttering the
parser).
=head1 METHODS
=head2 Class Methods
=head3 C<new>
my $grammar = TAP::Parser::Grammar->new({
iterator => $iterator,
parser => $parser,
version => $version,
});
Returns L<TAP::Parser> grammar object that will parse the TAP stream from the
specified iterator. Both C<iterator> and C<parser> are required arguments.
If C<version> is not set it defaults to C<12> (see L</set_version> for more
details).
=cut
# new() implementation supplied by TAP::Object
sub _initialize {
my ( $self, $args ) = @_;
$self->{iterator} = $args->{iterator}; # TODO: accessor
$self->{iterator} ||= $args->{stream}; # deprecated
$self->{parser} = $args->{parser}; # TODO: accessor
$self->set_version( $args->{version} || 12 );
return $self;
}
my %language_for;
{
# XXX the 'not' and 'ok' might be on separate lines in VMS ...
my $ok = qr/(?:not )?ok\b/;
my $num = qr/\d+/;
my %v12 = (
version => {
syntax => qr/^TAP\s+version\s+(\d+)\s*\z/i,
handler => sub {
my ( $self, $line ) = @_;
my $version = $1;
return $self->_make_version_token( $line, $version, );
},
},
plan => {
syntax => qr/^1\.\.(\d+)\s*(.*)\z/,
handler => sub {
my ( $self, $line ) = @_;
my ( $tests_planned, $tail ) = ( $1, $2 );
my $explanation = undef;
my $skip = '';
if ( $tail =~ /^todo((?:\s+\d+)+)/ ) {
my @todo = split /\s+/, _trim($1);
return $self->_make_plan_token(
$line, $tests_planned, 'TODO',
'', \@todo
);
}
elsif ( 0 == $tests_planned ) {
$skip = 'SKIP';
# If we can't match # SKIP the directive should be undef.
($explanation) = $tail =~ /^#\s*SKIP\S*\s+(.*)/i;
}
elsif ( $tail !~ /^\s*$/ ) {
return $self->_make_unknown_token($line);
}
$explanation = '' unless defined $explanation;
return $self->_make_plan_token(
$line, $tests_planned, $skip,
$explanation, []
);
},
},
# An optimization to handle the most common test lines without
# directives.
simple_test => {
syntax => qr/^($ok) \ ($num) (?:\ ([^#]+))? \z/x,
handler => sub {
my ( $self, $line ) = @_;
my ( $ok, $num, $desc ) = ( $1, $2, $3 );
return $self->_make_test_token(
$line, $ok, $num,
$desc
);
},
},
test => {
syntax => qr/^($ok) \s* ($num)? \s* (.*) \z/x,
handler => sub {
my ( $self, $line ) = @_;
my ( $ok, $num, $desc ) = ( $1, $2, $3 );
my ( $dir, $explanation ) = ( '', '' );
if ($desc =~ m/^ ( [^\\\#]* (?: \\. [^\\\#]* )* )
\# \s* (SKIP|TODO) \b \s* (.*) $/ix
)
{
( $desc, $dir, $explanation ) = ( $1, $2, $3 );
}
return $self->_make_test_token(
$line, $ok, $num, $desc,
$dir, $explanation
);
},
},
comment => {
syntax => qr/^#(.*)/,
handler => sub {
my ( $self, $line ) = @_;
my $comment = $1;
return $self->_make_comment_token( $line, $comment );
},
},
bailout => {
syntax => qr/^\s*Bail out!\s*(.*)/,
handler => sub {
my ( $self, $line ) = @_;
my $explanation = $1;
return $self->_make_bailout_token(
$line,
$explanation
);
},
},
);
my %v13 = (
%v12,
plan => {
syntax => qr/^1\.\.(\d+)\s*(?:\s*#\s*SKIP\b(.*))?\z/i,
handler => sub {
my ( $self, $line ) = @_;
my ( $tests_planned, $explanation ) = ( $1, $2 );
my $skip
= ( 0 == $tests_planned || defined $explanation )
? 'SKIP'
: '';
$explanation = '' unless defined $explanation;
return $self->_make_plan_token(
$line, $tests_planned, $skip,
$explanation, []
);
},
},
yaml => {
syntax => qr/^ (\s+) (---.*) $/x,
handler => sub {
my ( $self, $line ) = @_;
my ( $pad, $marker ) = ( $1, $2 );
return $self->_make_yaml_token( $pad, $marker );
},
},
pragma => {
syntax =>
qr/^ pragma \s+ ( [-+] \w+ \s* (?: , \s* [-+] \w+ \s* )* ) $/x,
handler => sub {
my ( $self, $line ) = @_;
my $pragmas = $1;
return $self->_make_pragma_token( $line, $pragmas );
},
},
);
%language_for = (
'12' => {
tokens => \%v12,
},
'13' => {
tokens => \%v13,
setup => sub {
shift->{iterator}->handle_unicode;
},
},
);
}
##############################################################################
=head2 Instance Methods
=head3 C<set_version>
$grammar->set_version(13);
Tell the grammar which TAP syntax version to support. The lowest
supported version is 12. Although 'TAP version' isn't valid version 12
syntax it is accepted so that higher version numbers may be parsed.
=cut
sub set_version {
my $self = shift;
my $version = shift;
if ( my $language = $language_for{$version} ) {
$self->{version} = $version;
$self->{tokens} = $language->{tokens};
if ( my $setup = $language->{setup} ) {
$self->$setup();
}
$self->_order_tokens;
}
else {
require Carp;
Carp::croak("Unsupported syntax version: $version");
}
}
# Optimization to put the most frequent tokens first.
sub _order_tokens {
my $self = shift;
my %copy = %{ $self->{tokens} };
my @ordered_tokens = grep {defined}
map { delete $copy{$_} } qw( simple_test test comment plan );
push @ordered_tokens, values %copy;
$self->{ordered_tokens} = \@ordered_tokens;
}
##############################################################################
=head3 C<tokenize>
my $token = $grammar->tokenize;
This method will return a L<TAP::Parser::Result> object representing the
current line of TAP.
=cut
sub tokenize {
my $self = shift;
my $line = $self->{iterator}->next;
unless ( defined $line ) {
delete $self->{parser}; # break circular ref
return;
}
my $token;
for my $token_data ( @{ $self->{ordered_tokens} } ) {
if ( $line =~ $token_data->{syntax} ) {
my $handler = $token_data->{handler};
$token = $self->$handler($line);
last;
}
}
$token = $self->_make_unknown_token($line) unless $token;
return $self->{parser}->make_result($token);
}
##############################################################################
=head3 C<token_types>
my @types = $grammar->token_types;
Returns the different types of tokens which this grammar can parse.
=cut
sub token_types {
my $self = shift;
return keys %{ $self->{tokens} };
}
##############################################################################
=head3 C<syntax_for>
my $syntax = $grammar->syntax_for($token_type);
Returns a pre-compiled regular expression which will match a chunk of TAP
corresponding to the token type. For example (not that you should really pay
attention to this, C<< $grammar->syntax_for('comment') >> will return
C<< qr/^#(.*)/ >>.
=cut
sub syntax_for {
my ( $self, $type ) = @_;
return $self->{tokens}->{$type}->{syntax};
}
##############################################################################
=head3 C<handler_for>
my $handler = $grammar->handler_for($token_type);
Returns a code reference which, when passed an appropriate line of TAP,
returns the lexed token corresponding to that line. As a result, the basic
TAP parsing loop looks similar to the following:
my @tokens;
my $grammar = TAP::Grammar->new;
LINE: while ( defined( my $line = $parser->_next_chunk_of_tap ) ) {
for my $type ( $grammar->token_types ) {
my $syntax = $grammar->syntax_for($type);
if ( $line =~ $syntax ) {
my $handler = $grammar->handler_for($type);
push @tokens => $grammar->$handler($line);
next LINE;
}
}
push @tokens => $grammar->_make_unknown_token($line);
}
=cut
sub handler_for {
my ( $self, $type ) = @_;
return $self->{tokens}->{$type}->{handler};
}
sub _make_version_token {
my ( $self, $line, $version ) = @_;
return {
type => 'version',
raw => $line,
version => $version,
};
}
sub _make_plan_token {
my ( $self, $line, $tests_planned, $directive, $explanation, $todo ) = @_;
if ( $directive eq 'SKIP'
&& 0 != $tests_planned
&& $self->{version} < 13 )
{
warn
"Specified SKIP directive in plan but more than 0 tests ($line)\n";
}
return {
type => 'plan',
raw => $line,
tests_planned => $tests_planned,
directive => $directive,
explanation => _trim($explanation),
todo_list => $todo,
};
}
sub _make_test_token {
my ( $self, $line, $ok, $num, $desc, $dir, $explanation ) = @_;
return {
ok => $ok,
# forcing this to be an integer (and not a string) reduces memory
# consumption. RT #84939
test_num => ( defined $num ? 0 + $num : undef ),
description => _trim($desc),
directive => ( defined $dir ? uc $dir : '' ),
explanation => _trim($explanation),
raw => $line,
type => 'test',
};
}
sub _make_unknown_token {
my ( $self, $line ) = @_;
return {
raw => $line,
type => 'unknown',
};
}
sub _make_comment_token {
my ( $self, $line, $comment ) = @_;
return {
type => 'comment',
raw => $line,
comment => _trim($comment)
};
}
sub _make_bailout_token {
my ( $self, $line, $explanation ) = @_;
return {
type => 'bailout',
raw => $line,
bailout => _trim($explanation)
};
}
sub _make_yaml_token {
my ( $self, $pad, $marker ) = @_;
my $yaml = TAP::Parser::YAMLish::Reader->new;
my $iterator = $self->{iterator};
# Construct a reader that reads from our input stripping leading
# spaces from each line.
my $leader = length($pad);
my $strip = qr{ ^ (\s{$leader}) (.*) $ }x;
my @extra = ($marker);
my $reader = sub {
return shift @extra if @extra;
my $line = $iterator->next;
return $2 if $line =~ $strip;
return;
};
my $data = $yaml->read($reader);
# Reconstitute input. This is convoluted. Maybe we should just
# record it on the way in...
chomp( my $raw = $yaml->get_raw );
$raw =~ s/^/$pad/mg;
return {
type => 'yaml',
raw => $raw,
data => $data
};
}
sub _make_pragma_token {
my ( $self, $line, $pragmas ) = @_;
return {
type => 'pragma',
raw => $line,
pragmas => [ split /\s*,\s*/, _trim($pragmas) ],
};
}
sub _trim {
my $data = shift;
return '' unless defined $data;
$data =~ s/^\s+//;
$data =~ s/\s+$//;
return $data;
}
1;
=head1 TAP GRAMMAR
B<NOTE:> This grammar is slightly out of date. There's still some discussion
about it and a new one will be provided when we have things better defined.
The L<TAP::Parser> does not use a formal grammar because TAP is essentially a
stream-based protocol. In fact, it's quite legal to have an infinite stream.
For the same reason that we don't apply regexes to streams, we're not using a
formal grammar here. Instead, we parse the TAP in lines.
For purposes for forward compatibility, any result which does not match the
following grammar is currently referred to as
L<TAP::Parser::Result::Unknown>. It is I<not> a parse error.
A formal grammar would look similar to the following:
(*
For the time being, I'm cheating on the EBNF by allowing
certain terms to be defined by POSIX character classes by
using the following syntax:
digit ::= [:digit:]
As far as I am aware, that's not valid EBNF. Sue me. I
didn't know how to write "char" otherwise (Unicode issues).
Suggestions welcome.
*)
tap ::= version? { comment | unknown } leading_plan lines
|
lines trailing_plan {comment}
version ::= 'TAP version ' positiveInteger {positiveInteger} "\n"
leading_plan ::= plan skip_directive? "\n"
trailing_plan ::= plan "\n"
plan ::= '1..' nonNegativeInteger
lines ::= line {line}
line ::= (comment | test | unknown | bailout ) "\n"
test ::= status positiveInteger? description? directive?
status ::= 'not '? 'ok '
description ::= (character - (digit | '#')) {character - '#'}
directive ::= todo_directive | skip_directive
todo_directive ::= hash_mark 'TODO' ' ' {character}
skip_directive ::= hash_mark 'SKIP' ' ' {character}
comment ::= hash_mark {character}
hash_mark ::= '#' {' '}
bailout ::= 'Bail out!' {character}
unknown ::= { (character - "\n") }
(* POSIX character classes and other terminals *)
digit ::= [:digit:]
character ::= ([:print:] - "\n")
positiveInteger ::= ( digit - '0' ) {digit}
nonNegativeInteger ::= digit {digit}
=head1 SUBCLASSING
Please see L<TAP::Parser/SUBCLASSING> for a subclassing overview.
If you I<really> want to subclass L<TAP::Parser>'s grammar the best thing to
do is read through the code. There's no easy way of summarizing it here.
=head1 SEE ALSO
L<TAP::Object>,
L<TAP::Parser>,
L<TAP::Parser::Iterator>,
L<TAP::Parser::Result>,
=cut
PK �[!���� � Parser/ResultFactory.pmnu �[��� package TAP::Parser::ResultFactory;
use strict;
use warnings;
use TAP::Parser::Result::Bailout ();
use TAP::Parser::Result::Comment ();
use TAP::Parser::Result::Plan ();
use TAP::Parser::Result::Pragma ();
use TAP::Parser::Result::Test ();
use TAP::Parser::Result::Unknown ();
use TAP::Parser::Result::Version ();
use TAP::Parser::Result::YAML ();
use base 'TAP::Object';
##############################################################################
=head1 NAME
TAP::Parser::ResultFactory - Factory for creating TAP::Parser output objects
=head1 SYNOPSIS
use TAP::Parser::ResultFactory;
my $token = {...};
my $factory = TAP::Parser::ResultFactory->new;
my $result = $factory->make_result( $token );
=head1 VERSION
Version 3.42
=cut
our $VERSION = '3.42';
=head2 DESCRIPTION
This is a simple factory class which returns a L<TAP::Parser::Result> subclass
representing the current bit of test data from TAP (usually a single line).
It is used primarily by L<TAP::Parser::Grammar>. Unless you're subclassing,
you probably won't need to use this module directly.
=head2 METHODS
=head2 Class Methods
=head3 C<new>
Creates a new factory class.
I<Note:> You currently don't need to instantiate a factory in order to use it.
=head3 C<make_result>
Returns an instance the appropriate class for the test token passed in.
my $result = TAP::Parser::ResultFactory->make_result($token);
Can also be called as an instance method.
=cut
sub make_result {
my ( $proto, $token ) = @_;
my $type = $token->{type};
return $proto->class_for($type)->new($token);
}
=head3 C<class_for>
Takes one argument: C<$type>. Returns the class for this $type, or C<croak>s
with an error.
=head3 C<register_type>
Takes two arguments: C<$type>, C<$class>
This lets you override an existing type with your own custom type, or register
a completely new type, eg:
# create a custom result type:
package MyResult;
use strict;
use base 'TAP::Parser::Result';
# register with the factory:
TAP::Parser::ResultFactory->register_type( 'my_type' => __PACKAGE__ );
# use it:
my $r = TAP::Parser::ResultFactory->( { type => 'my_type' } );
Your custom type should then be picked up automatically by the L<TAP::Parser>.
=cut
our %CLASS_FOR = (
plan => 'TAP::Parser::Result::Plan',
pragma => 'TAP::Parser::Result::Pragma',
test => 'TAP::Parser::Result::Test',
comment => 'TAP::Parser::Result::Comment',
bailout => 'TAP::Parser::Result::Bailout',
version => 'TAP::Parser::Result::Version',
unknown => 'TAP::Parser::Result::Unknown',
yaml => 'TAP::Parser::Result::YAML',
);
sub class_for {
my ( $class, $type ) = @_;
# return target class:
return $CLASS_FOR{$type} if exists $CLASS_FOR{$type};
# or complain:
require Carp;
Carp::croak("Could not determine class for result type '$type'");
}
sub register_type {
my ( $class, $type, $rclass ) = @_;
# register it blindly, assume they know what they're doing
$CLASS_FOR{$type} = $rclass;
return $class;
}
1;
=head1 SUBCLASSING
Please see L<TAP::Parser/SUBCLASSING> for a subclassing overview.
There are a few things to bear in mind when creating your own
C<ResultFactory>:
=over 4
=item 1
The factory itself is never instantiated (this I<may> change in the future).
This means that C<_initialize> is never called.
=item 2
C<TAP::Parser::Result-E<gt>new> is never called, $tokens are reblessed.
This I<will> change in a future version!
=item 3
L<TAP::Parser::Result> subclasses will register themselves with
L<TAP::Parser::ResultFactory> directly:
package MyFooResult;
TAP::Parser::ResultFactory->register_type( foo => __PACKAGE__ );
Of course, it's up to you to decide whether or not to ignore them.
=back
=head2 Example
package MyResultFactory;
use strict;
use MyResult;
use base 'TAP::Parser::ResultFactory';
# force all results to be 'MyResult'
sub class_for {
return 'MyResult';
}
1;
=head1 SEE ALSO
L<TAP::Parser>,
L<TAP::Parser::Result>,
L<TAP::Parser::Grammar>
=cut
PK �[i��O% O% Parser/Source.pmnu �[��� package TAP::Parser::Source;
use strict;
use warnings;
use File::Basename qw( fileparse );
use base 'TAP::Object';
use constant BLK_SIZE => 512;
=head1 NAME
TAP::Parser::Source - a TAP source & meta data about it
=head1 VERSION
Version 3.42
=cut
our $VERSION = '3.42';
=head1 SYNOPSIS
use TAP::Parser::Source;
my $source = TAP::Parser::Source->new;
$source->raw( \'reference to raw TAP source' )
->config( \%config )
->merge( $boolean )
->switches( \@switches )
->test_args( \@args )
->assemble_meta;
do { ... } if $source->meta->{is_file};
# see assemble_meta for a full list of data available
=head1 DESCRIPTION
A TAP I<source> is something that produces a stream of TAP for the parser to
consume, such as an executable file, a text file, an archive, an IO handle, a
database, etc. C<TAP::Parser::Source>s encapsulate these I<raw> sources, and
provide some useful meta data about them. They are used by
L<TAP::Parser::SourceHandler>s, which do whatever is required to produce &
capture a stream of TAP from the I<raw> source, and package it up in a
L<TAP::Parser::Iterator> for the parser to consume.
Unless you're writing a new L<TAP::Parser::SourceHandler>, a plugin or
subclassing L<TAP::Parser>, you probably won't need to use this module directly.
=head1 METHODS
=head2 Class Methods
=head3 C<new>
my $source = TAP::Parser::Source->new;
Returns a new C<TAP::Parser::Source> object.
=cut
# new() implementation supplied by TAP::Object
sub _initialize {
my ($self) = @_;
$self->meta( {} );
$self->config( {} );
return $self;
}
##############################################################################
=head2 Instance Methods
=head3 C<raw>
my $raw = $source->raw;
$source->raw( $some_value );
Chaining getter/setter for the raw TAP source. This is a reference, as it may
contain large amounts of data (eg: raw TAP).
=head3 C<meta>
my $meta = $source->meta;
$source->meta({ %some_value });
Chaining getter/setter for meta data about the source. This defaults to an
empty hashref. See L</assemble_meta> for more info.
=head3 C<has_meta>
True if the source has meta data.
=head3 C<config>
my $config = $source->config;
$source->config({ %some_value });
Chaining getter/setter for the source's configuration, if any has been provided
by the user. How it's used is up to you. This defaults to an empty hashref.
See L</config_for> for more info.
=head3 C<merge>
my $merge = $source->merge;
$source->config( $bool );
Chaining getter/setter for the flag that dictates whether STDOUT and STDERR
should be merged (where appropriate). Defaults to undef.
=head3 C<switches>
my $switches = $source->switches;
$source->config([ @switches ]);
Chaining getter/setter for the list of command-line switches that should be
passed to the source (where appropriate). Defaults to undef.
=head3 C<test_args>
my $test_args = $source->test_args;
$source->config([ @test_args ]);
Chaining getter/setter for the list of command-line arguments that should be
passed to the source (where appropriate). Defaults to undef.
=cut
sub raw {
my $self = shift;
return $self->{raw} unless @_;
$self->{raw} = shift;
return $self;
}
sub meta {
my $self = shift;
return $self->{meta} unless @_;
$self->{meta} = shift;
return $self;
}
sub has_meta {
return scalar %{ shift->meta } ? 1 : 0;
}
sub config {
my $self = shift;
return $self->{config} unless @_;
$self->{config} = shift;
return $self;
}
sub merge {
my $self = shift;
return $self->{merge} unless @_;
$self->{merge} = shift;
return $self;
}
sub switches {
my $self = shift;
return $self->{switches} unless @_;
$self->{switches} = shift;
return $self;
}
sub test_args {
my $self = shift;
return $self->{test_args} unless @_;
$self->{test_args} = shift;
return $self;
}
=head3 C<assemble_meta>
my $meta = $source->assemble_meta;
Gathers meta data about the L</raw> source, stashes it in L</meta> and returns
it as a hashref. This is done so that the L<TAP::Parser::SourceHandler>s don't
have to repeat common checks. Currently this includes:
is_scalar => $bool,
is_hash => $bool,
is_array => $bool,
# for scalars:
length => $n
has_newlines => $bool
# only done if the scalar looks like a filename
is_file => $bool,
is_dir => $bool,
is_symlink => $bool,
file => {
# only done if the scalar looks like a filename
basename => $string, # including ext
dir => $string,
ext => $string,
lc_ext => $string,
# system checks
exists => $bool,
stat => [ ... ], # perldoc -f stat
empty => $bool,
size => $n,
text => $bool,
binary => $bool,
read => $bool,
write => $bool,
execute => $bool,
setuid => $bool,
setgid => $bool,
sticky => $bool,
is_file => $bool,
is_dir => $bool,
is_symlink => $bool,
# only done if the file's a symlink
lstat => [ ... ], # perldoc -f lstat
# only done if the file's a readable text file
shebang => $first_line,
}
# for arrays:
size => $n,
=cut
sub assemble_meta {
my ($self) = @_;
return $self->meta if $self->has_meta;
my $meta = $self->meta;
my $raw = $self->raw;
# rudimentary is object test - if it's blessed it'll
# inherit from UNIVERSAL
$meta->{is_object} = UNIVERSAL::isa( $raw, 'UNIVERSAL' ) ? 1 : 0;
if ( $meta->{is_object} ) {
$meta->{class} = ref($raw);
}
else {
my $ref = lc( ref($raw) );
$meta->{"is_$ref"} = 1;
}
if ( $meta->{is_scalar} ) {
my $source = $$raw;
$meta->{length} = length($$raw);
$meta->{has_newlines} = $$raw =~ /\n/ ? 1 : 0;
# only do file checks if it looks like a filename
if ( !$meta->{has_newlines} and $meta->{length} < 1024 ) {
my $file = {};
$file->{exists} = -e $source ? 1 : 0;
if ( $file->{exists} ) {
$meta->{file} = $file;
# avoid extra system calls (see `perldoc -f -X`)
$file->{stat} = [ stat(_) ];
$file->{empty} = -z _ ? 1 : 0;
$file->{size} = -s _;
$file->{text} = -T _ ? 1 : 0;
$file->{binary} = -B _ ? 1 : 0;
$file->{read} = -r _ ? 1 : 0;
$file->{write} = -w _ ? 1 : 0;
$file->{execute} = -x _ ? 1 : 0;
$file->{setuid} = -u _ ? 1 : 0;
$file->{setgid} = -g _ ? 1 : 0;
$file->{sticky} = -k _ ? 1 : 0;
$meta->{is_file} = $file->{is_file} = -f _ ? 1 : 0;
$meta->{is_dir} = $file->{is_dir} = -d _ ? 1 : 0;
# symlink check requires another system call
$meta->{is_symlink} = $file->{is_symlink}
= -l $source ? 1 : 0;
if ( $file->{is_symlink} ) {
$file->{lstat} = [ lstat(_) ];
}
# put together some common info about the file
( $file->{basename}, $file->{dir}, $file->{ext} )
= map { defined $_ ? $_ : '' }
fileparse( $source, qr/\.[^.]*/ );
$file->{lc_ext} = lc( $file->{ext} );
$file->{basename} .= $file->{ext} if $file->{ext};
if ( !$file->{is_dir} && $file->{read} ) {
eval { $file->{shebang} = $self->shebang($$raw); };
if ( my $e = $@ ) {
warn $e;
}
}
}
}
}
elsif ( $meta->{is_array} ) {
$meta->{size} = $#$raw + 1;
}
elsif ( $meta->{is_hash} ) {
; # do nothing
}
return $meta;
}
=head3 C<shebang>
Get the shebang line for a script file.
my $shebang = TAP::Parser::Source->shebang( $some_script );
May be called as a class method
=cut
{
# Global shebang cache.
my %shebang_for;
sub _read_shebang {
my ( $class, $file ) = @_;
open my $fh, '<', $file or die "Can't read $file: $!\n";
# Might be a binary file - so read a fixed number of bytes.
my $got = read $fh, my ($buf), BLK_SIZE;
defined $got or die "I/O error: $!\n";
return $1 if $buf =~ /(.*)/;
return;
}
sub shebang {
my ( $class, $file ) = @_;
$shebang_for{$file} = $class->_read_shebang($file)
unless exists $shebang_for{$file};
return $shebang_for{$file};
}
}
=head3 C<config_for>
my $config = $source->config_for( $class );
Returns L</config> for the $class given. Class names may be fully qualified
or abbreviated, eg:
# these are equivalent
$source->config_for( 'Perl' );
$source->config_for( 'TAP::Parser::SourceHandler::Perl' );
If a fully qualified $class is given, its abbreviated version is checked first.
=cut
sub config_for {
my ( $self, $class ) = @_;
my ($abbrv_class) = ( $class =~ /(?:\:\:)?(\w+)$/ );
my $config = $self->config->{$abbrv_class} || $self->config->{$class};
return $config;
}
1;
__END__
=head1 AUTHORS
Steve Purkis.
=head1 SEE ALSO
L<TAP::Object>,
L<TAP::Parser>,
L<TAP::Parser::IteratorFactory>,
L<TAP::Parser::SourceHandler>
=cut
PK �[ c5- - Parser/Scheduler.pmnu �[��� package TAP::Parser::Scheduler;
use strict;
use warnings;
use Carp;
use TAP::Parser::Scheduler::Job;
use TAP::Parser::Scheduler::Spinner;
=head1 NAME
TAP::Parser::Scheduler - Schedule tests during parallel testing
=head1 VERSION
Version 3.42
=cut
our $VERSION = '3.42';
=head1 SYNOPSIS
use TAP::Parser::Scheduler;
=head1 DESCRIPTION
=head1 METHODS
=head2 Class Methods
=head3 C<new>
my $sched = TAP::Parser::Scheduler->new(tests => \@tests);
my $sched = TAP::Parser::Scheduler->new(
tests => [ ['t/test_name.t','Test Description'], ... ],
rules => \%rules,
);
Given 'tests' and optional 'rules' as input, returns a new
C<TAP::Parser::Scheduler> object. Each member of C<@tests> should be either a
a test file name, or a two element arrayref, where the first element is a test
file name, and the second element is a test description. By default, we'll use
the test name as the description.
The optional C<rules> attribute provides direction on which tests should be run
in parallel and which should be run sequentially. If no rule data structure is
provided, a default data structure is used which makes every test eligible to
be run in parallel:
{ par => '**' },
The rules data structure is documented more in the next section.
=head2 Rules data structure
The "C<rules>" data structure is the the heart of the scheduler. It allows you
to express simple rules like "run all tests in sequence" or "run all tests in
parallel except these five tests.". However, the rules structure also supports
glob-style pattern matching and recursive definitions, so you can also express
arbitarily complicated patterns.
The rule must only have one top level key: either 'par' for "parallel" or 'seq'
for "sequence".
Values must be either strings with possible glob-style matching, or arrayrefs
of strings or hashrefs which follow this pattern recursively.
Every element in an arrayref directly below a 'par' key is eligible to be run
in parallel, while vavalues directly below a 'seq' key must be run in sequence.
=head3 Rules examples
Here are some examples:
# All tests be run in parallel (the default rule)
{ par => '**' },
# Run all tests in sequence, except those starting with "p"
{ par => 't/p*.t' },
# Run all tests in parallel, except those starting with "p"
{
seq => [
{ seq => 't/p*.t' },
{ par => '**' },
],
}
# Run some startup tests in sequence, then some parallel tests then some
# teardown tests in sequence.
{
seq => [
{ seq => 't/startup/*.t' },
{ par => ['t/a/*.t','t/b/*.t','t/c/*.t'], }
{ seq => 't/shutdown/*.t' },
],
},
=head3 Rules resolution
=over 4
=item * By default, all tests are eligible to be run in parallel. Specifying any of your own rules removes this one.
=item * "First match wins". The first rule that matches a test will be the one that applies.
=item * Any test which does not match a rule will be run in sequence at the end of the run.
=item * The existence of a rule does not imply selecting a test. You must still specify the tests to run.
=item * Specifying a rule to allow tests to run in parallel does not make the run in parallel. You still need specify the number of parallel C<jobs> in your Harness object.
=back
=head3 Glob-style pattern matching for rules
We implement our own glob-style pattern matching. Here are the patterns it supports:
** is any number of characters, including /, within a pathname
* is zero or more characters within a filename/directory name
? is exactly one character within a filename/directory name
{foo,bar,baz} is any of foo, bar or baz.
\ is an escape character
=cut
sub new {
my $class = shift;
croak "Need a number of key, value pairs" if @_ % 2;
my %args = @_;
my $tests = delete $args{tests} || croak "Need a 'tests' argument";
my $rules = delete $args{rules} || { par => '**' };
croak "Unknown arg(s): ", join ', ', sort keys %args
if keys %args;
# Turn any simple names into a name, description pair. TODO: Maybe
# construct jobs here?
my $self = bless {}, $class;
$self->_set_rules( $rules, $tests );
return $self;
}
# Build the scheduler data structure.
#
# SCHEDULER-DATA ::= JOB
# || ARRAY OF ARRAY OF SCHEDULER-DATA
#
# The nested arrays are the key to scheduling. The outer array contains
# a list of things that may be executed in parallel. Whenever an
# eligible job is sought any element of the outer array that is ready to
# execute can be selected. The inner arrays represent sequential
# execution. They can only proceed when the first job is ready to run.
sub _set_rules {
my ( $self, $rules, $tests ) = @_;
# Convert all incoming tests to job objects.
# If no test description is provided use the file name as the description.
my @tests = map { TAP::Parser::Scheduler::Job->new(@$_) }
map { 'ARRAY' eq ref $_ ? $_ : [ $_, $_ ] } @$tests;
my $schedule = $self->_rule_clause( $rules, \@tests );
# If any tests are left add them as a sequential block at the end of
# the run.
$schedule = [ [ $schedule, @tests ] ] if @tests;
$self->{schedule} = $schedule;
}
sub _rule_clause {
my ( $self, $rule, $tests ) = @_;
croak 'Rule clause must be a hash'
unless 'HASH' eq ref $rule;
my @type = keys %$rule;
croak 'Rule clause must have exactly one key'
unless @type == 1;
my %handlers = (
par => sub {
[ map { [$_] } @_ ];
},
seq => sub { [ [@_] ] },
);
my $handler = $handlers{ $type[0] }
|| croak 'Unknown scheduler type: ', $type[0];
my $val = $rule->{ $type[0] };
return $handler->(
map {
'HASH' eq ref $_
? $self->_rule_clause( $_, $tests )
: $self->_expand( $_, $tests )
} 'ARRAY' eq ref $val ? @$val : $val
);
}
sub _glob_to_regexp {
my ( $self, $glob ) = @_;
my $nesting;
my $pattern;
while (1) {
if ( $glob =~ /\G\*\*/gc ) {
# ** is any number of characters, including /, within a pathname
$pattern .= '.*?';
}
elsif ( $glob =~ /\G\*/gc ) {
# * is zero or more characters within a filename/directory name
$pattern .= '[^/]*';
}
elsif ( $glob =~ /\G\?/gc ) {
# ? is exactly one character within a filename/directory name
$pattern .= '[^/]';
}
elsif ( $glob =~ /\G\{/gc ) {
# {foo,bar,baz} is any of foo, bar or baz.
$pattern .= '(?:';
++$nesting;
}
elsif ( $nesting and $glob =~ /\G,/gc ) {
# , is only special inside {}
$pattern .= '|';
}
elsif ( $nesting and $glob =~ /\G\}/gc ) {
# } that matches { is special. But unbalanced } are not.
$pattern .= ')';
--$nesting;
}
elsif ( $glob =~ /\G(\\.)/gc ) {
# A quoted literal
$pattern .= $1;
}
elsif ( $glob =~ /\G([\},])/gc ) {
# Sometimes meta characters
$pattern .= '\\' . $1;
}
else {
# Eat everything that is not a meta character.
$glob =~ /\G([^{?*\\\},]*)/gc;
$pattern .= quotemeta $1;
}
return $pattern if pos $glob == length $glob;
}
}
sub _expand {
my ( $self, $name, $tests ) = @_;
my $pattern = $self->_glob_to_regexp($name);
$pattern = qr/^ $pattern $/x;
my @match = ();
for ( my $ti = 0; $ti < @$tests; $ti++ ) {
if ( $tests->[$ti]->filename =~ $pattern ) {
push @match, splice @$tests, $ti, 1;
$ti--;
}
}
return @match;
}
=head2 Instance Methods
=head3 C<get_all>
Get a list of all remaining tests.
=cut
sub get_all {
my $self = shift;
my @all = $self->_gather( $self->{schedule} );
$self->{count} = @all;
@all;
}
sub _gather {
my ( $self, $rule ) = @_;
return unless defined $rule;
return $rule unless 'ARRAY' eq ref $rule;
return map { defined() ? $self->_gather($_) : () } map {@$_} @$rule;
}
=head3 C<get_job>
Return the next available job as L<TAP::Parser::Scheduler::Job> object or
C<undef> if none are available. Returns a L<TAP::Parser::Scheduler::Spinner> if
the scheduler still has pending jobs but none are available to run right now.
=cut
sub get_job {
my $self = shift;
$self->{count} ||= $self->get_all;
my @jobs = $self->_find_next_job( $self->{schedule} );
if (@jobs) {
--$self->{count};
return $jobs[0];
}
return TAP::Parser::Scheduler::Spinner->new
if $self->{count};
return;
}
sub _not_empty {
my $ar = shift;
return 1 unless 'ARRAY' eq ref $ar;
for (@$ar) {
return 1 if _not_empty($_);
}
return;
}
sub _is_empty { !_not_empty(@_) }
sub _find_next_job {
my ( $self, $rule ) = @_;
my @queue = ();
my $index = 0;
while ( $index < @$rule ) {
my $seq = $rule->[$index];
# Prune any exhausted items.
shift @$seq while @$seq && _is_empty( $seq->[0] );
if (@$seq) {
if ( defined $seq->[0] ) {
if ( 'ARRAY' eq ref $seq->[0] ) {
push @queue, $seq;
}
else {
my $job = splice @$seq, 0, 1, undef;
$job->on_finish( sub { shift @$seq } );
return $job;
}
}
++$index;
}
else {
# Remove the empty sub-array from the array
splice @$rule, $index, 1;
}
}
for my $seq (@queue) {
if ( my @jobs = $self->_find_next_job( $seq->[0] ) ) {
return @jobs;
}
}
return;
}
=head3 C<as_string>
Return a human readable representation of the scheduling tree.
For example:
my @tests = (qw{
t/startup/foo.t
t/shutdown/foo.t
t/a/foo.t t/b/foo.t t/c/foo.t t/d/foo.t
});
my $sched = TAP::Parser::Scheduler->new(
tests => \@tests,
rules => {
seq => [
{ seq => 't/startup/*.t' },
{ par => ['t/a/*.t','t/b/*.t','t/c/*.t'] },
{ seq => 't/shutdown/*.t' },
],
},
);
Produces:
par:
seq:
par:
seq:
par:
seq:
't/startup/foo.t'
par:
seq:
't/a/foo.t'
seq:
't/b/foo.t'
seq:
't/c/foo.t'
par:
seq:
't/shutdown/foo.t'
't/d/foo.t'
=cut
sub as_string {
my $self = shift;
return $self->_as_string( $self->{schedule} );
}
sub _as_string {
my ( $self, $rule, $depth ) = ( shift, shift, shift || 0 );
my $pad = ' ' x 2;
my $indent = $pad x $depth;
if ( !defined $rule ) {
return "$indent(undef)\n";
}
elsif ( 'ARRAY' eq ref $rule ) {
return unless @$rule;
my $type = ( 'par', 'seq' )[ $depth % 2 ];
return join(
'', "$indent$type:\n",
map { $self->_as_string( $_, $depth + 1 ) } @$rule
);
}
else {
return "$indent'" . $rule->filename . "'\n";
}
}
1;
PK �[r��^� � Parser/Multiplexer.pmnu �[��� package TAP::Parser::Multiplexer;
use strict;
use warnings;
use IO::Select;
use base 'TAP::Object';
use constant IS_WIN32 => $^O =~ /^(MS)?Win32$/;
use constant IS_VMS => $^O eq 'VMS';
use constant SELECT_OK => !( IS_VMS || IS_WIN32 );
=head1 NAME
TAP::Parser::Multiplexer - Multiplex multiple TAP::Parsers
=head1 VERSION
Version 3.42
=cut
our $VERSION = '3.42';
=head1 SYNOPSIS
use TAP::Parser::Multiplexer;
my $mux = TAP::Parser::Multiplexer->new;
$mux->add( $parser1, $stash1 );
$mux->add( $parser2, $stash2 );
while ( my ( $parser, $stash, $result ) = $mux->next ) {
# do stuff
}
=head1 DESCRIPTION
C<TAP::Parser::Multiplexer> gathers input from multiple TAP::Parsers.
Internally it calls select on the input file handles for those parsers
to wait for one or more of them to have input available.
See L<TAP::Harness> for an example of its use.
=head1 METHODS
=head2 Class Methods
=head3 C<new>
my $mux = TAP::Parser::Multiplexer->new;
Returns a new C<TAP::Parser::Multiplexer> object.
=cut
# new() implementation supplied by TAP::Object
sub _initialize {
my $self = shift;
$self->{select} = IO::Select->new;
$self->{avid} = []; # Parsers that can't select
$self->{count} = 0;
return $self;
}
##############################################################################
=head2 Instance Methods
=head3 C<add>
$mux->add( $parser, $stash );
Add a TAP::Parser to the multiplexer. C<$stash> is an optional opaque
reference that will be returned from C<next> along with the parser and
the next result.
=cut
sub add {
my ( $self, $parser, $stash ) = @_;
if ( SELECT_OK && ( my @handles = $parser->get_select_handles ) ) {
my $sel = $self->{select};
# We have to turn handles into file numbers here because by
# the time we want to remove them from our IO::Select they
# will already have been closed by the iterator.
my @filenos = map { fileno $_ } @handles;
for my $h (@handles) {
$sel->add( [ $h, $parser, $stash, @filenos ] );
}
$self->{count}++;
}
else {
push @{ $self->{avid} }, [ $parser, $stash ];
}
}
=head3 C<parsers>
my $count = $mux->parsers;
Returns the number of parsers. Parsers are removed from the multiplexer
when their input is exhausted.
=cut
sub parsers {
my $self = shift;
return $self->{count} + scalar @{ $self->{avid} };
}
sub _iter {
my $self = shift;
my $sel = $self->{select};
my $avid = $self->{avid};
my @ready = ();
return sub {
# Drain all the non-selectable parsers first
if (@$avid) {
my ( $parser, $stash ) = @{ $avid->[0] };
my $result = $parser->next;
shift @$avid unless defined $result;
return ( $parser, $stash, $result );
}
unless (@ready) {
return unless $sel->count;
@ready = $sel->can_read;
}
my ( $h, $parser, $stash, @handles ) = @{ shift @ready };
my $result = $parser->next;
unless ( defined $result ) {
$sel->remove(@handles);
$self->{count}--;
# Force another can_read - we may now have removed a handle
# thought to have been ready.
@ready = ();
}
return ( $parser, $stash, $result );
};
}
=head3 C<next>
Return a result from the next available parser. Returns a list
containing the parser from which the result came, the stash that
corresponds with that parser and the result.
my ( $parser, $stash, $result ) = $mux->next;
If C<$result> is undefined the corresponding parser has reached the end
of its input (and will automatically be removed from the multiplexer).
When all parsers are exhausted an empty list will be returned.
if ( my ( $parser, $stash, $result ) = $mux->next ) {
if ( ! defined $result ) {
# End of this parser
}
else {
# Process result
}
}
else {
# All parsers finished
}
=cut
sub next {
my $self = shift;
return ( $self->{_iter} ||= $self->_iter )->();
}
=head1 See Also
L<TAP::Parser>
L<TAP::Harness>
=cut
1;
PK �[I��ɳ � Parser/YAMLish/Reader.pmnu �[��� package TAP::Parser::YAMLish::Reader;
use strict;
use warnings;
use base 'TAP::Object';
our $VERSION = '3.42';
# TODO:
# Handle blessed object syntax
# Printable characters for escapes
my %UNESCAPES = (
z => "\x00", a => "\x07", t => "\x09",
n => "\x0a", v => "\x0b", f => "\x0c",
r => "\x0d", e => "\x1b", '\\' => '\\',
);
my $QQ_STRING = qr{ " (?:\\. | [^"])* " }x;
my $HASH_LINE = qr{ ^ ($QQ_STRING|\S+) \s* : \s* (?: (.+?) \s* )? $ }x;
my $IS_HASH_KEY = qr{ ^ [\w\'\"] }x;
my $IS_END_YAML = qr{ ^ \.\.\. \s* $ }x;
my $IS_QQ_STRING = qr{ ^ $QQ_STRING $ }x;
# new() implementation supplied by TAP::Object
sub read {
my $self = shift;
my $obj = shift;
die "Must have a code reference to read input from"
unless ref $obj eq 'CODE';
$self->{reader} = $obj;
$self->{capture} = [];
# Prime the reader
$self->_next;
return unless $self->{next};
my $doc = $self->_read;
# The terminator is mandatory otherwise we'd consume a line from the
# iterator that doesn't belong to us. If we want to remove this
# restriction we'll have to implement look-ahead in the iterators.
# Which might not be a bad idea.
my $dots = $self->_peek;
die "Missing '...' at end of YAMLish"
unless defined $dots
and $dots =~ $IS_END_YAML;
delete $self->{reader};
delete $self->{next};
return $doc;
}
sub get_raw { join( "\n", grep defined, @{ shift->{capture} || [] } ) . "\n" }
sub _peek {
my $self = shift;
return $self->{next} unless wantarray;
my $line = $self->{next};
$line =~ /^ (\s*) (.*) $ /x;
return ( $2, length $1 );
}
sub _next {
my $self = shift;
die "_next called with no reader"
unless $self->{reader};
my $line = $self->{reader}->();
$self->{next} = $line;
push @{ $self->{capture} }, $line;
}
sub _read {
my $self = shift;
my $line = $self->_peek;
# Do we have a document header?
if ( $line =~ /^ --- (?: \s* (.+?)? \s* )? $/x ) {
$self->_next;
return $self->_read_scalar($1) if defined $1; # Inline?
my ( $next, $indent ) = $self->_peek;
if ( $next =~ /^ - /x ) {
return $self->_read_array($indent);
}
elsif ( $next =~ $IS_HASH_KEY ) {
return $self->_read_hash( $next, $indent );
}
elsif ( $next =~ $IS_END_YAML ) {
die "Premature end of YAMLish";
}
else {
die "Unsupported YAMLish syntax: '$next'";
}
}
else {
die "YAMLish document header not found";
}
}
# Parse a double quoted string
sub _read_qq {
my $self = shift;
my $str = shift;
unless ( $str =~ s/^ " (.*?) " $/$1/x ) {
die "Internal: not a quoted string";
}
$str =~ s/\\"/"/gx;
$str =~ s/ \\ ( [tartan\\favez] | x([0-9a-fA-F]{2}) )
/ (length($1) > 1) ? pack("H2", $2) : $UNESCAPES{$1} /gex;
return $str;
}
# Parse a scalar string to the actual scalar
sub _read_scalar {
my $self = shift;
my $string = shift;
return undef if $string eq '~';
return {} if $string eq '{}';
return [] if $string eq '[]';
if ( $string eq '>' || $string eq '|' ) {
my ( $line, $indent ) = $self->_peek;
die "Multi-line scalar content missing" unless defined $line;
my @multiline = ($line);
while (1) {
$self->_next;
my ( $next, $ind ) = $self->_peek;
last if $ind < $indent;
my $pad = $string eq '|' ? ( ' ' x ( $ind - $indent ) ) : '';
push @multiline, $pad . $next;
}
return join( ( $string eq '>' ? ' ' : "\n" ), @multiline ) . "\n";
}
if ( $string =~ /^ ' (.*) ' $/x ) {
( my $rv = $1 ) =~ s/''/'/g;
return $rv;
}
if ( $string =~ $IS_QQ_STRING ) {
return $self->_read_qq($string);
}
if ( $string =~ /^['"]/ ) {
# A quote with folding... we don't support that
die __PACKAGE__ . " does not support multi-line quoted scalars";
}
# Regular unquoted string
return $string;
}
sub _read_nested {
my $self = shift;
my ( $line, $indent ) = $self->_peek;
if ( $line =~ /^ -/x ) {
return $self->_read_array($indent);
}
elsif ( $line =~ $IS_HASH_KEY ) {
return $self->_read_hash( $line, $indent );
}
else {
die "Unsupported YAMLish syntax: '$line'";
}
}
# Parse an array
sub _read_array {
my ( $self, $limit ) = @_;
my $ar = [];
while (1) {
my ( $line, $indent ) = $self->_peek;
last
if $indent < $limit
|| !defined $line
|| $line =~ $IS_END_YAML;
if ( $indent > $limit ) {
die "Array line over-indented";
}
if ( $line =~ /^ (- \s+) \S+ \s* : (?: \s+ | $ ) /x ) {
$indent += length $1;
$line =~ s/-\s+//;
push @$ar, $self->_read_hash( $line, $indent );
}
elsif ( $line =~ /^ - \s* (.+?) \s* $/x ) {
die "Unexpected start of YAMLish" if $line =~ /^---/;
$self->_next;
push @$ar, $self->_read_scalar($1);
}
elsif ( $line =~ /^ - \s* $/x ) {
$self->_next;
push @$ar, $self->_read_nested;
}
elsif ( $line =~ $IS_HASH_KEY ) {
$self->_next;
push @$ar, $self->_read_hash( $line, $indent, );
}
else {
die "Unsupported YAMLish syntax: '$line'";
}
}
return $ar;
}
sub _read_hash {
my ( $self, $line, $limit ) = @_;
my $indent;
my $hash = {};
while (1) {
die "Badly formed hash line: '$line'"
unless $line =~ $HASH_LINE;
my ( $key, $value ) = ( $self->_read_scalar($1), $2 );
$self->_next;
if ( defined $value ) {
$hash->{$key} = $self->_read_scalar($value);
}
else {
$hash->{$key} = $self->_read_nested;
}
( $line, $indent ) = $self->_peek;
last
if $indent < $limit
|| !defined $line
|| $line =~ $IS_END_YAML;
}
return $hash;
}
1;
__END__
=pod
=head1 NAME
TAP::Parser::YAMLish::Reader - Read YAMLish data from iterator
=head1 VERSION
Version 3.42
=head1 SYNOPSIS
=head1 DESCRIPTION
Note that parts of this code were derived from L<YAML::Tiny> with the
permission of Adam Kennedy.
=head1 METHODS
=head2 Class Methods
=head3 C<new>
The constructor C<new> creates and returns an empty
C<TAP::Parser::YAMLish::Reader> object.
my $reader = TAP::Parser::YAMLish::Reader->new;
=head2 Instance Methods
=head3 C<read>
my $got = $reader->read($iterator);
Read YAMLish from a L<TAP::Parser::Iterator> and return the data structure it
represents.
=head3 C<get_raw>
my $source = $reader->get_source;
Return the raw YAMLish source from the most recent C<read>.
=head1 AUTHOR
Andy Armstrong, <andy@hexten.net>
Adam Kennedy wrote L<YAML::Tiny> which provided the template and many of
the YAML matching regular expressions for this module.
=head1 SEE ALSO
L<YAML::Tiny>, L<YAML>, L<YAML::Syck>, L<Config::Tiny>, L<CSS::Tiny>,
L<http://use.perl.org/~Alias/journal/29427>
=head1 COPYRIGHT
Copyright 2007-2011 Andy Armstrong.
Portions copyright 2006-2008 Adam Kennedy.
This program is free software; you can redistribute
it and/or modify it under the same terms as Perl itself.
The full text of the license can be found in the
LICENSE file included with this module.
=cut
PK �[�J��s s Parser/YAMLish/Writer.pmnu �[��� package TAP::Parser::YAMLish::Writer;
use strict;
use warnings;
use base 'TAP::Object';
our $VERSION = '3.42';
my $ESCAPE_CHAR = qr{ [ \x00-\x1f \" ] }x;
my $ESCAPE_KEY = qr{ (?: ^\W ) | $ESCAPE_CHAR }x;
my @UNPRINTABLE = qw(
z x01 x02 x03 x04 x05 x06 a
x08 t n v f r x0e x0f
x10 x11 x12 x13 x14 x15 x16 x17
x18 x19 x1a e x1c x1d x1e x1f
);
# new() implementation supplied by TAP::Object
sub write {
my $self = shift;
die "Need something to write"
unless @_;
my $obj = shift;
my $out = shift || \*STDOUT;
die "Need a reference to something I can write to"
unless ref $out;
$self->{writer} = $self->_make_writer($out);
$self->_write_obj( '---', $obj );
$self->_put('...');
delete $self->{writer};
}
sub _make_writer {
my $self = shift;
my $out = shift;
my $ref = ref $out;
if ( 'CODE' eq $ref ) {
return $out;
}
elsif ( 'ARRAY' eq $ref ) {
return sub { push @$out, shift };
}
elsif ( 'SCALAR' eq $ref ) {
return sub { $$out .= shift() . "\n" };
}
elsif ( 'GLOB' eq $ref || 'IO::Handle' eq $ref ) {
return sub { print $out shift(), "\n" };
}
die "Can't write to $out";
}
sub _put {
my $self = shift;
$self->{writer}->( join '', @_ );
}
sub _enc_scalar {
my $self = shift;
my $val = shift;
my $rule = shift;
return '~' unless defined $val;
if ( $val =~ /$rule/ ) {
$val =~ s/\\/\\\\/g;
$val =~ s/"/\\"/g;
$val =~ s/ ( [\x00-\x1f] ) / '\\' . $UNPRINTABLE[ ord($1) ] /gex;
return qq{"$val"};
}
if ( length($val) == 0 or $val =~ /\s/ ) {
$val =~ s/'/''/;
return "'$val'";
}
return $val;
}
sub _write_obj {
my $self = shift;
my $prefix = shift;
my $obj = shift;
my $indent = shift || 0;
if ( my $ref = ref $obj ) {
my $pad = ' ' x $indent;
if ( 'HASH' eq $ref ) {
if ( keys %$obj ) {
$self->_put($prefix);
for my $key ( sort keys %$obj ) {
my $value = $obj->{$key};
$self->_write_obj(
$pad . $self->_enc_scalar( $key, $ESCAPE_KEY ) . ':',
$value, $indent + 1
);
}
}
else {
$self->_put( $prefix, ' {}' );
}
}
elsif ( 'ARRAY' eq $ref ) {
if (@$obj) {
$self->_put($prefix);
for my $value (@$obj) {
$self->_write_obj(
$pad . '-', $value,
$indent + 1
);
}
}
else {
$self->_put( $prefix, ' []' );
}
}
else {
die "Don't know how to encode $ref";
}
}
else {
$self->_put( $prefix, ' ', $self->_enc_scalar( $obj, $ESCAPE_CHAR ) );
}
}
1;
__END__
=pod
=head1 NAME
TAP::Parser::YAMLish::Writer - Write YAMLish data
=head1 VERSION
Version 3.42
=head1 SYNOPSIS
use TAP::Parser::YAMLish::Writer;
my $data = {
one => 1,
two => 2,
three => [ 1, 2, 3 ],
};
my $yw = TAP::Parser::YAMLish::Writer->new;
# Write to an array...
$yw->write( $data, \@some_array );
# ...an open file handle...
$yw->write( $data, $some_file_handle );
# ...a string ...
$yw->write( $data, \$some_string );
# ...or a closure
$yw->write( $data, sub {
my $line = shift;
print "$line\n";
} );
=head1 DESCRIPTION
Encodes a scalar, hash reference or array reference as YAMLish.
=head1 METHODS
=head2 Class Methods
=head3 C<new>
my $writer = TAP::Parser::YAMLish::Writer->new;
The constructor C<new> creates and returns an empty
C<TAP::Parser::YAMLish::Writer> object.
=head2 Instance Methods
=head3 C<write>
$writer->write($obj, $output );
Encode a scalar, hash reference or array reference as YAML.
my $writer = sub {
my $line = shift;
print SOMEFILE "$line\n";
};
my $data = {
one => 1,
two => 2,
three => [ 1, 2, 3 ],
};
my $yw = TAP::Parser::YAMLish::Writer->new;
$yw->write( $data, $writer );
The C< $output > argument may be:
=over
=item * a reference to a scalar to append YAML to
=item * the handle of an open file
=item * a reference to an array into which YAML will be pushed
=item * a code reference
=back
If you supply a code reference the subroutine will be called once for
each line of output with the line as its only argument. Passed lines
will have no trailing newline.
=head1 AUTHOR
Andy Armstrong, <andy@hexten.net>
=head1 SEE ALSO
L<YAML::Tiny>, L<YAML>, L<YAML::Syck>, L<Config::Tiny>, L<CSS::Tiny>,
L<http://use.perl.org/~Alias/journal/29427>
=head1 COPYRIGHT
Copyright 2007-2011 Andy Armstrong.
This program is free software; you can redistribute
it and/or modify it under the same terms as Perl itself.
The full text of the license can be found in the
LICENSE file included with this module.
=cut
PK �[�V<L Parser/Result.pmnu �[��� package TAP::Parser::Result;
use strict;
use warnings;
use base 'TAP::Object';
BEGIN {
# make is_* methods
my @attrs = qw( plan pragma test comment bailout version unknown yaml );
no strict 'refs';
for my $token (@attrs) {
my $method = "is_$token";
*$method = sub { return $token eq shift->type };
}
}
##############################################################################
=head1 NAME
TAP::Parser::Result - Base class for TAP::Parser output objects
=head1 VERSION
Version 3.42
=cut
our $VERSION = '3.42';
=head1 SYNOPSIS
# abstract class - not meant to be used directly
# see TAP::Parser::ResultFactory for preferred usage
# directly:
use TAP::Parser::Result;
my $token = {...};
my $result = TAP::Parser::Result->new( $token );
=head2 DESCRIPTION
This is a simple base class used by L<TAP::Parser> to store objects that
represent the current bit of test output data from TAP (usually a single
line). Unless you're subclassing, you probably won't need to use this module
directly.
=head2 METHODS
=head3 C<new>
# see TAP::Parser::ResultFactory for preferred usage
# to use directly:
my $result = TAP::Parser::Result->new($token);
Returns an instance the appropriate class for the test token passed in.
=cut
# new() implementation provided by TAP::Object
sub _initialize {
my ( $self, $token ) = @_;
if ($token) {
# assign to a hash slice to make a shallow copy of the token.
# I guess we could assign to the hash as (by default) there are not
# contents, but that seems less helpful if someone wants to subclass us
@{$self}{ keys %$token } = values %$token;
}
return $self;
}
##############################################################################
=head2 Boolean methods
The following methods all return a boolean value and are to be overridden in
the appropriate subclass.
=over 4
=item * C<is_plan>
Indicates whether or not this is the test plan line.
1..3
=item * C<is_pragma>
Indicates whether or not this is a pragma line.
pragma +strict
=item * C<is_test>
Indicates whether or not this is a test line.
ok 1 Is OK!
=item * C<is_comment>
Indicates whether or not this is a comment.
# this is a comment
=item * C<is_bailout>
Indicates whether or not this is bailout line.
Bail out! We're out of dilithium crystals.
=item * C<is_version>
Indicates whether or not this is a TAP version line.
TAP version 4
=item * C<is_unknown>
Indicates whether or not the current line could be parsed.
... this line is junk ...
=item * C<is_yaml>
Indicates whether or not this is a YAML chunk.
=back
=cut
##############################################################################
=head3 C<raw>
print $result->raw;
Returns the original line of text which was parsed.
=cut
sub raw { shift->{raw} }
##############################################################################
=head3 C<type>
my $type = $result->type;
Returns the "type" of a token, such as C<comment> or C<test>.
=cut
sub type { shift->{type} }
##############################################################################
=head3 C<as_string>
print $result->as_string;
Prints a string representation of the token. This might not be the exact
output, however. Tests will have test numbers added if not present, TODO and
SKIP directives will be capitalized and, in general, things will be cleaned
up. If you need the original text for the token, see the C<raw> method.
=cut
sub as_string { shift->{raw} }
##############################################################################
=head3 C<is_ok>
if ( $result->is_ok ) { ... }
Reports whether or not a given result has passed. Anything which is B<not> a
test result returns true. This is merely provided as a convenient shortcut.
=cut
sub is_ok {1}
##############################################################################
=head3 C<passed>
Deprecated. Please use C<is_ok> instead.
=cut
sub passed {
warn 'passed() is deprecated. Please use "is_ok()"';
shift->is_ok;
}
##############################################################################
=head3 C<has_directive>
if ( $result->has_directive ) {
...
}
Indicates whether or not the given result has a TODO or SKIP directive.
=cut
sub has_directive {
my $self = shift;
return ( $self->has_todo || $self->has_skip );
}
##############################################################################
=head3 C<has_todo>
if ( $result->has_todo ) {
...
}
Indicates whether or not the given result has a TODO directive.
=cut
sub has_todo { 'TODO' eq ( shift->{directive} || '' ) }
##############################################################################
=head3 C<has_skip>
if ( $result->has_skip ) {
...
}
Indicates whether or not the given result has a SKIP directive.
=cut
sub has_skip { 'SKIP' eq ( shift->{directive} || '' ) }
=head3 C<set_directive>
Set the directive associated with this token. Used internally to fake
TODO tests.
=cut
sub set_directive {
my ( $self, $dir ) = @_;
$self->{directive} = $dir;
}
1;
=head1 SUBCLASSING
Please see L<TAP::Parser/SUBCLASSING> for a subclassing overview.
Remember: if you want your subclass to be automatically used by the parser,
you'll have to register it with L<TAP::Parser::ResultFactory/register_type>.
If you're creating a completely new result I<type>, you'll probably need to
subclass L<TAP::Parser::Grammar> too, or else it'll never get used.
=head2 Example
package MyResult;
use strict;
use base 'TAP::Parser::Result';
# register with the factory:
TAP::Parser::ResultFactory->register_type( 'my_type' => __PACKAGE__ );
sub as_string { 'My results all look the same' }
=head1 SEE ALSO
L<TAP::Object>,
L<TAP::Parser>,
L<TAP::Parser::ResultFactory>,
L<TAP::Parser::Result::Bailout>,
L<TAP::Parser::Result::Comment>,
L<TAP::Parser::Result::Plan>,
L<TAP::Parser::Result::Pragma>,
L<TAP::Parser::Result::Test>,
L<TAP::Parser::Result::Unknown>,
L<TAP::Parser::Result::Version>,
L<TAP::Parser::Result::YAML>,
=cut
PK �[�yo^� � Parser/Iterator/Stream.pmnu �[��� package TAP::Parser::Iterator::Stream;
use strict;
use warnings;
use base 'TAP::Parser::Iterator';
=head1 NAME
TAP::Parser::Iterator::Stream - Iterator for filehandle-based TAP sources
=head1 VERSION
Version 3.42
=cut
our $VERSION = '3.42';
=head1 SYNOPSIS
use TAP::Parser::Iterator::Stream;
open( TEST, 'test.tap' );
my $it = TAP::Parser::Iterator::Stream->new(\*TEST);
my $line = $it->next;
=head1 DESCRIPTION
This is a simple iterator wrapper for reading from filehandles, used by
L<TAP::Parser>. Unless you're writing a plugin or subclassing, you probably
won't need to use this module directly.
=head1 METHODS
=head2 Class Methods
=head3 C<new>
Create an iterator. Expects one argument containing a filehandle.
=cut
# new() implementation supplied by TAP::Object
sub _initialize {
my ( $self, $thing ) = @_;
$self->{fh} = $thing;
return $self;
}
=head2 Instance Methods
=head3 C<next>
Iterate through it, of course.
=head3 C<next_raw>
Iterate raw input without applying any fixes for quirky input syntax.
=head3 C<wait>
Get the wait status for this iterator. Always returns zero.
=head3 C<exit>
Get the exit status for this iterator. Always returns zero.
=cut
sub wait { shift->exit }
sub exit { shift->{fh} ? () : 0 }
sub next_raw {
my $self = shift;
my $fh = $self->{fh};
if ( defined( my $line = <$fh> ) ) {
chomp $line;
return $line;
}
else {
$self->_finish;
return;
}
}
sub _finish {
my $self = shift;
close delete $self->{fh};
}
sub get_select_handles {
my $self = shift;
# return our handle in case it's a socket or pipe (select()-able)
return ( $self->{fh}, )
if (-S $self->{fh} || -p $self->{fh});
return;
}
1;
=head1 ATTRIBUTION
Originally ripped off from L<Test::Harness>.
=head1 SEE ALSO
L<TAP::Object>,
L<TAP::Parser>,
L<TAP::Parser::Iterator>,
=cut
PK �[�"i� � Parser/Iterator/Array.pmnu �[��� package TAP::Parser::Iterator::Array;
use strict;
use warnings;
use base 'TAP::Parser::Iterator';
=head1 NAME
TAP::Parser::Iterator::Array - Iterator for array-based TAP sources
=head1 VERSION
Version 3.42
=cut
our $VERSION = '3.42';
=head1 SYNOPSIS
use TAP::Parser::Iterator::Array;
my @data = ('foo', 'bar', baz');
my $it = TAP::Parser::Iterator::Array->new(\@data);
my $line = $it->next;
=head1 DESCRIPTION
This is a simple iterator wrapper for arrays of scalar content, used by
L<TAP::Parser>. Unless you're writing a plugin or subclassing, you probably
won't need to use this module directly.
=head1 METHODS
=head2 Class Methods
=head3 C<new>
Create an iterator. Takes one argument: an C<$array_ref>
=head2 Instance Methods
=head3 C<next>
Iterate through it, of course.
=head3 C<next_raw>
Iterate raw input without applying any fixes for quirky input syntax.
=head3 C<wait>
Get the wait status for this iterator. For an array iterator this will always
be zero.
=head3 C<exit>
Get the exit status for this iterator. For an array iterator this will always
be zero.
=cut
# new() implementation supplied by TAP::Object
sub _initialize {
my ( $self, $thing ) = @_;
chomp @$thing;
$self->{idx} = 0;
$self->{array} = $thing;
$self->{exit} = undef;
return $self;
}
sub wait { shift->exit }
sub exit {
my $self = shift;
return 0 if $self->{idx} >= @{ $self->{array} };
return;
}
sub next_raw {
my $self = shift;
return $self->{array}->[ $self->{idx}++ ];
}
1;
=head1 ATTRIBUTION
Originally ripped off from L<Test::Harness>.
=head1 SEE ALSO
L<TAP::Object>,
L<TAP::Parser>,
L<TAP::Parser::Iterator>,
=cut
PK �[ ��# �# Parser/Iterator/Process.pmnu �[��� package TAP::Parser::Iterator::Process;
use strict;
use warnings;
use Config;
use IO::Handle;
use base 'TAP::Parser::Iterator';
my $IS_WIN32 = ( $^O =~ /^(MS)?Win32$/ );
=head1 NAME
TAP::Parser::Iterator::Process - Iterator for process-based TAP sources
=head1 VERSION
Version 3.42
=cut
our $VERSION = '3.42';
=head1 SYNOPSIS
use TAP::Parser::Iterator::Process;
my %args = (
command => ['python', 'setup.py', 'test'],
merge => 1,
setup => sub { ... },
teardown => sub { ... },
);
my $it = TAP::Parser::Iterator::Process->new(\%args);
my $line = $it->next;
=head1 DESCRIPTION
This is a simple iterator wrapper for executing external processes, used by
L<TAP::Parser>. Unless you're writing a plugin or subclassing, you probably
won't need to use this module directly.
=head1 METHODS
=head2 Class Methods
=head3 C<new>
Create an iterator. Expects one argument containing a hashref of the form:
command => \@command_to_execute
merge => $attempt_merge_stderr_and_stdout?
setup => $callback_to_setup_command
teardown => $callback_to_teardown_command
Tries to uses L<IPC::Open3> & L<IO::Select> to communicate with the spawned
process if they are available. Falls back onto C<open()>.
=head2 Instance Methods
=head3 C<next>
Iterate through the process output, of course.
=head3 C<next_raw>
Iterate raw input without applying any fixes for quirky input syntax.
=head3 C<wait>
Get the wait status for this iterator's process.
=head3 C<exit>
Get the exit status for this iterator's process.
=cut
{
no warnings 'uninitialized';
# get around a catch22 in the test suite that causes failures on Win32:
local $SIG{__DIE__} = undef;
eval { require POSIX; &POSIX::WEXITSTATUS(0) };
if ($@) {
*_wait2exit = sub { $_[1] >> 8 };
}
else {
*_wait2exit = sub { POSIX::WEXITSTATUS( $_[1] ) }
}
}
sub _use_open3 {
my $self = shift;
return unless $Config{d_fork} || $IS_WIN32;
for my $module (qw( IPC::Open3 IO::Select )) {
eval "use $module";
return if $@;
}
return 1;
}
{
my $got_unicode;
sub _get_unicode {
return $got_unicode if defined $got_unicode;
eval 'use Encode qw(decode_utf8);';
$got_unicode = $@ ? 0 : 1;
}
}
# new() implementation supplied by TAP::Object
sub _initialize {
my ( $self, $args ) = @_;
my @command = @{ delete $args->{command} || [] }
or die "Must supply a command to execute";
$self->{command} = [@command];
# Private. Used to frig with chunk size during testing.
my $chunk_size = delete $args->{_chunk_size} || 65536;
my $merge = delete $args->{merge};
my ( $pid, $err, $sel );
if ( my $setup = delete $args->{setup} ) {
$setup->(@command);
}
my $out = IO::Handle->new;
if ( $self->_use_open3 ) {
# HOTPATCH {{{
my $xclose = \&IPC::Open3::xclose;
no warnings;
local *IPC::Open3::xclose = sub {
my $fh = shift;
no strict 'refs';
return if ( fileno($fh) == fileno(STDIN) );
$xclose->($fh);
};
# }}}
if ($IS_WIN32) {
$err = $merge ? '' : '>&STDERR';
eval {
$pid = open3(
'<&STDIN', $out, $merge ? '' : $err,
@command
);
};
die "Could not execute (@command): $@" if $@;
if ( $] >= 5.006 ) {
binmode($out, ":crlf");
}
}
else {
$err = $merge ? '' : IO::Handle->new;
eval { $pid = open3( '<&STDIN', $out, $err, @command ); };
die "Could not execute (@command): $@" if $@;
$sel = $merge ? undef : IO::Select->new( $out, $err );
}
}
else {
$err = '';
my $command
= join( ' ', map { $_ =~ /\s/ ? qq{"$_"} : $_ } @command );
open( $out, "$command|" )
or die "Could not execute ($command): $!";
}
$self->{out} = $out;
$self->{err} = $err;
$self->{sel} = $sel;
$self->{pid} = $pid;
$self->{exit} = undef;
$self->{chunk_size} = $chunk_size;
if ( my $teardown = delete $args->{teardown} ) {
$self->{teardown} = sub {
$teardown->(@command);
};
}
return $self;
}
=head3 C<handle_unicode>
Upgrade the input stream to handle UTF8.
=cut
sub handle_unicode {
my $self = shift;
if ( $self->{sel} ) {
if ( _get_unicode() ) {
# Make sure our iterator has been constructed and...
my $next = $self->{_next} ||= $self->_next;
# ...wrap it to do UTF8 casting
$self->{_next} = sub {
my $line = $next->();
return decode_utf8($line) if defined $line;
return;
};
}
}
else {
if ( $] >= 5.008 ) {
eval 'binmode($self->{out}, ":utf8")';
}
}
}
##############################################################################
sub wait { shift->{wait} }
sub exit { shift->{exit} }
sub _next {
my $self = shift;
if ( my $out = $self->{out} ) {
if ( my $sel = $self->{sel} ) {
my $err = $self->{err};
my @buf = ();
my $partial = ''; # Partial line
my $chunk_size = $self->{chunk_size};
return sub {
return shift @buf if @buf;
READ:
while ( my @ready = $sel->can_read ) {
for my $fh (@ready) {
my $got = sysread $fh, my ($chunk), $chunk_size;
if ( $got == 0 ) {
$sel->remove($fh);
}
elsif ( $fh == $err ) {
print STDERR $chunk; # echo STDERR
}
else {
$chunk = $partial . $chunk;
$partial = '';
# Make sure we have a complete line
unless ( substr( $chunk, -1, 1 ) eq "\n" ) {
my $nl = rindex $chunk, "\n";
if ( $nl == -1 ) {
$partial = $chunk;
redo READ;
}
else {
$partial = substr( $chunk, $nl + 1 );
$chunk = substr( $chunk, 0, $nl );
}
}
push @buf, split /\n/, $chunk;
return shift @buf if @buf;
}
}
}
# Return partial last line
if ( length $partial ) {
my $last = $partial;
$partial = '';
return $last;
}
$self->_finish;
return;
};
}
else {
return sub {
if ( defined( my $line = <$out> ) ) {
chomp $line;
return $line;
}
$self->_finish;
return;
};
}
}
else {
return sub {
$self->_finish;
return;
};
}
}
sub next_raw {
my $self = shift;
return ( $self->{_next} ||= $self->_next )->();
}
sub _finish {
my $self = shift;
my $status = $?;
# Avoid circular refs
$self->{_next} = sub {return}
if $] >= 5.006;
# If we have a subprocess we need to wait for it to terminate
if ( defined $self->{pid} ) {
if ( $self->{pid} == waitpid( $self->{pid}, 0 ) ) {
$status = $?;
}
}
( delete $self->{out} )->close if $self->{out};
# If we have an IO::Select we also have an error handle to close.
if ( $self->{sel} ) {
( delete $self->{err} )->close;
delete $self->{sel};
}
else {
$status = $?;
}
# Sometimes we get -1 on Windows. Presumably that means status not
# available.
$status = 0 if $IS_WIN32 && $status == -1;
$self->{wait} = $status;
$self->{exit} = $self->_wait2exit($status);
if ( my $teardown = $self->{teardown} ) {
$teardown->();
}
return $self;
}
=head3 C<get_select_handles>
Return a list of filehandles that may be used upstream in a select()
call to signal that this Iterator is ready. Iterators that are not
handle based should return an empty list.
=cut
sub get_select_handles {
my $self = shift;
return grep $_, ( $self->{out}, $self->{err} );
}
1;
=head1 ATTRIBUTION
Originally ripped off from L<Test::Harness>.
=head1 SEE ALSO
L<TAP::Object>,
L<TAP::Parser>,
L<TAP::Parser::Iterator>,
=cut
PK �[ � � Parser/Iterator.pmnu �[��� package TAP::Parser::Iterator;
use strict;
use warnings;
use base 'TAP::Object';
=head1 NAME
TAP::Parser::Iterator - Base class for TAP source iterators
=head1 VERSION
Version 3.42
=cut
our $VERSION = '3.42';
=head1 SYNOPSIS
# to subclass:
use TAP::Parser::Iterator ();
use base 'TAP::Parser::Iterator';
sub _initialize {
# see TAP::Object...
}
sub next_raw { ... }
sub wait { ... }
sub exit { ... }
=head1 DESCRIPTION
This is a simple iterator base class that defines L<TAP::Parser>'s iterator
API. Iterators are typically created from L<TAP::Parser::SourceHandler>s.
=head1 METHODS
=head2 Class Methods
=head3 C<new>
Create an iterator. Provided by L<TAP::Object>.
=head2 Instance Methods
=head3 C<next>
while ( my $item = $iter->next ) { ... }
Iterate through it, of course.
=head3 C<next_raw>
B<Note:> this method is abstract and should be overridden.
while ( my $item = $iter->next_raw ) { ... }
Iterate raw input without applying any fixes for quirky input syntax.
=cut
sub next {
my $self = shift;
my $line = $self->next_raw;
# vms nit: When encountering 'not ok', vms often has the 'not' on a line
# by itself:
# not
# ok 1 - 'I hate VMS'
if ( defined($line) and $line =~ /^\s*not\s*$/ ) {
$line .= ( $self->next_raw || '' );
}
return $line;
}
sub next_raw {
require Carp;
my $msg = Carp::longmess('abstract method called directly!');
$_[0]->_croak($msg);
}
=head3 C<handle_unicode>
If necessary switch the input stream to handle unicode. This only has
any effect for I/O handle based streams.
The default implementation does nothing.
=cut
sub handle_unicode { }
=head3 C<get_select_handles>
Return a list of filehandles that may be used upstream in a select()
call to signal that this Iterator is ready. Iterators that are not
handle-based should return an empty list.
The default implementation does nothing.
=cut
sub get_select_handles {
return;
}
=head3 C<wait>
B<Note:> this method is abstract and should be overridden.
my $wait_status = $iter->wait;
Return the C<wait> status for this iterator.
=head3 C<exit>
B<Note:> this method is abstract and should be overridden.
my $wait_status = $iter->exit;
Return the C<exit> status for this iterator.
=cut
sub wait {
require Carp;
my $msg = Carp::longmess('abstract method called directly!');
$_[0]->_croak($msg);
}
sub exit {
require Carp;
my $msg = Carp::longmess('abstract method called directly!');
$_[0]->_croak($msg);
}
1;
=head1 SUBCLASSING
Please see L<TAP::Parser/SUBCLASSING> for a subclassing overview.
You must override the abstract methods as noted above.
=head2 Example
L<TAP::Parser::Iterator::Array> is probably the easiest example to follow.
There's not much point repeating it here.
=head1 SEE ALSO
L<TAP::Object>,
L<TAP::Parser>,
L<TAP::Parser::Iterator::Array>,
L<TAP::Parser::Iterator::Stream>,
L<TAP::Parser::Iterator::Process>,
=cut
PK �['�� � Parser/SourceHandler/File.pmnu �[��� package TAP::Parser::SourceHandler::File;
use strict;
use warnings;
use TAP::Parser::IteratorFactory ();
use TAP::Parser::Iterator::Stream ();
use base 'TAP::Parser::SourceHandler';
TAP::Parser::IteratorFactory->register_handler(__PACKAGE__);
=head1 NAME
TAP::Parser::SourceHandler::File - Stream TAP from a text file.
=head1 VERSION
Version 3.42
=cut
our $VERSION = '3.42';
=head1 SYNOPSIS
use TAP::Parser::Source;
use TAP::Parser::SourceHandler::File;
my $source = TAP::Parser::Source->new->raw( \'file.tap' );
$source->assemble_meta;
my $class = 'TAP::Parser::SourceHandler::File';
my $vote = $class->can_handle( $source );
my $iter = $class->make_iterator( $source );
=head1 DESCRIPTION
This is a I<raw TAP stored in a file> L<TAP::Parser::SourceHandler> - it has 2 jobs:
1. Figure out if the I<raw> source it's given is a file containing raw TAP
output. See L<TAP::Parser::IteratorFactory> for more details.
2. Takes raw TAP from the text file given, and converts into an iterator.
Unless you're writing a plugin or subclassing L<TAP::Parser>, you probably
won't need to use this module directly.
=head1 METHODS
=head2 Class Methods
=head3 C<can_handle>
my $vote = $class->can_handle( $source );
Only votes if $source looks like a regular file. Casts the following votes:
0.9 if it's a .tap file
0.9 if it has an extension matching any given in user config.
=cut
sub can_handle {
my ( $class, $src ) = @_;
my $meta = $src->meta;
my $config = $src->config_for($class);
return 0 unless $meta->{is_file};
my $file = $meta->{file};
return 0.9 if $file->{lc_ext} eq '.tap';
if ( my $exts = $config->{extensions} ) {
my @exts = ref $exts eq 'ARRAY' ? @$exts : $exts;
return 0.9 if grep { lc($_) eq $file->{lc_ext} } @exts;
}
return 0;
}
=head3 C<make_iterator>
my $iterator = $class->make_iterator( $source );
Returns a new L<TAP::Parser::Iterator::Stream> for the source. C<croak>s
on error.
=cut
sub make_iterator {
my ( $class, $source ) = @_;
$class->_croak('$source->raw must be a scalar ref')
unless $source->meta->{is_scalar};
my $file = ${ $source->raw };
my $fh;
open( $fh, '<', $file )
or $class->_croak("error opening TAP source file '$file': $!");
return $class->iterator_class->new($fh);
}
=head3 C<iterator_class>
The class of iterator to use, override if you're sub-classing. Defaults
to L<TAP::Parser::Iterator::Stream>.
=cut
use constant iterator_class => 'TAP::Parser::Iterator::Stream';
1;
__END__
=head1 CONFIGURATION
{
extensions => [ @case_insensitive_exts_to_match ]
}
=head1 SUBCLASSING
Please see L<TAP::Parser/SUBCLASSING> for a subclassing overview.
=head1 SEE ALSO
L<TAP::Object>,
L<TAP::Parser>,
L<TAP::Parser::SourceHandler>,
L<TAP::Parser::SourceHandler::Executable>,
L<TAP::Parser::SourceHandler::Perl>,
L<TAP::Parser::SourceHandler::Handle>,
L<TAP::Parser::SourceHandler::RawTAP>
=cut
PK �[KlJ[
[
Parser/SourceHandler/Handle.pmnu �[��� package TAP::Parser::SourceHandler::Handle;
use strict;
use warnings;
use TAP::Parser::IteratorFactory ();
use TAP::Parser::Iterator::Stream ();
use base 'TAP::Parser::SourceHandler';
TAP::Parser::IteratorFactory->register_handler(__PACKAGE__);
=head1 NAME
TAP::Parser::SourceHandler::Handle - Stream TAP from an IO::Handle or a GLOB.
=head1 VERSION
Version 3.42
=cut
our $VERSION = '3.42';
=head1 SYNOPSIS
use TAP::Parser::Source;
use TAP::Parser::SourceHandler::Executable;
my $source = TAP::Parser::Source->new->raw( \*TAP_FILE );
$source->assemble_meta;
my $class = 'TAP::Parser::SourceHandler::Handle';
my $vote = $class->can_handle( $source );
my $iter = $class->make_iterator( $source );
=head1 DESCRIPTION
This is a I<raw TAP stored in an IO Handle> L<TAP::Parser::SourceHandler> class. It
has 2 jobs:
1. Figure out if the L<TAP::Parser::Source> it's given is an L<IO::Handle> or
GLOB containing raw TAP output (L</can_handle>).
2. Creates an iterator for IO::Handle's & globs (L</make_iterator>).
Unless you're writing a plugin or subclassing L<TAP::Parser>, you probably
won't need to use this module directly.
=head1 METHODS
=head2 Class Methods
=head3 C<can_handle>
my $vote = $class->can_handle( $source );
Casts the following votes:
0.9 if $source is an IO::Handle
0.8 if $source is a glob
=cut
sub can_handle {
my ( $class, $src ) = @_;
my $meta = $src->meta;
return 0.9
if $meta->{is_object}
&& UNIVERSAL::isa( $src->raw, 'IO::Handle' );
return 0.8 if $meta->{is_glob};
return 0;
}
=head3 C<make_iterator>
my $iterator = $class->make_iterator( $source );
Returns a new L<TAP::Parser::Iterator::Stream> for the source.
=cut
sub make_iterator {
my ( $class, $source ) = @_;
$class->_croak('$source->raw must be a glob ref or an IO::Handle')
unless $source->meta->{is_glob}
|| UNIVERSAL::isa( $source->raw, 'IO::Handle' );
return $class->iterator_class->new( $source->raw );
}
=head3 C<iterator_class>
The class of iterator to use, override if you're sub-classing. Defaults
to L<TAP::Parser::Iterator::Stream>.
=cut
use constant iterator_class => 'TAP::Parser::Iterator::Stream';
1;
=head1 SUBCLASSING
Please see L<TAP::Parser/SUBCLASSING> for a subclassing overview.
=head1 SEE ALSO
L<TAP::Object>,
L<TAP::Parser>,
L<TAP::Parser::Iterator>,
L<TAP::Parser::Iterator::Stream>,
L<TAP::Parser::IteratorFactory>,
L<TAP::Parser::SourceHandler>,
L<TAP::Parser::SourceHandler::Executable>,
L<TAP::Parser::SourceHandler::Perl>,
L<TAP::Parser::SourceHandler::File>,
L<TAP::Parser::SourceHandler::RawTAP>
=cut
PK �[�Y[ Parser/SourceHandler/RawTAP.pmnu �[��� package TAP::Parser::SourceHandler::RawTAP;
use strict;
use warnings;
use TAP::Parser::IteratorFactory ();
use TAP::Parser::Iterator::Array ();
use base 'TAP::Parser::SourceHandler';
TAP::Parser::IteratorFactory->register_handler(__PACKAGE__);
=head1 NAME
TAP::Parser::SourceHandler::RawTAP - Stream output from raw TAP in a scalar/array ref.
=head1 VERSION
Version 3.42
=cut
our $VERSION = '3.42';
=head1 SYNOPSIS
use TAP::Parser::Source;
use TAP::Parser::SourceHandler::RawTAP;
my $source = TAP::Parser::Source->new->raw( \"1..1\nok 1\n" );
$source->assemble_meta;
my $class = 'TAP::Parser::SourceHandler::RawTAP';
my $vote = $class->can_handle( $source );
my $iter = $class->make_iterator( $source );
=head1 DESCRIPTION
This is a I<raw TAP output> L<TAP::Parser::SourceHandler> - it has 2 jobs:
1. Figure out if the L<TAP::Parser::Source> it's given is raw TAP output
(L</can_handle>).
2. Creates an iterator for raw TAP output (L</make_iterator>).
Unless you're writing a plugin or subclassing L<TAP::Parser>, you probably
won't need to use this module directly.
=head1 METHODS
=head2 Class Methods
=head3 C<can_handle>
my $vote = $class->can_handle( $source );
Only votes if $source is an array, or a scalar with newlines. Casts the
following votes:
0.9 if it's a scalar with '..' in it
0.7 if it's a scalar with 'ok' in it
0.3 if it's just a scalar with newlines
0.5 if it's an array
=cut
sub can_handle {
my ( $class, $src ) = @_;
my $meta = $src->meta;
return 0 if $meta->{file};
if ( $meta->{is_scalar} ) {
return 0 unless $meta->{has_newlines};
return 0.9 if ${ $src->raw } =~ /\d\.\.\d/;
return 0.7 if ${ $src->raw } =~ /ok/;
return 0.3;
}
elsif ( $meta->{is_array} ) {
return 0.5;
}
return 0;
}
=head3 C<make_iterator>
my $iterator = $class->make_iterator( $source );
Returns a new L<TAP::Parser::Iterator::Array> for the source.
C<$source-E<gt>raw> must be an array ref, or a scalar ref.
C<croak>s on error.
=cut
sub make_iterator {
my ( $class, $src ) = @_;
my $meta = $src->meta;
my $tap_array;
if ( $meta->{is_scalar} ) {
$tap_array = [ split "\n" => ${ $src->raw } ];
}
elsif ( $meta->{is_array} ) {
$tap_array = $src->raw;
}
$class->_croak('No raw TAP found in $source->raw')
unless scalar $tap_array;
return TAP::Parser::Iterator::Array->new($tap_array);
}
1;
=head1 SUBCLASSING
Please see L<TAP::Parser/SUBCLASSING> for a subclassing overview.
=head1 SEE ALSO
L<TAP::Object>,
L<TAP::Parser>,
L<TAP::Parser::IteratorFactory>,
L<TAP::Parser::SourceHandler>,
L<TAP::Parser::SourceHandler::Executable>,
L<TAP::Parser::SourceHandler::Perl>,
L<TAP::Parser::SourceHandler::File>,
L<TAP::Parser::SourceHandler::Handle>
=cut
PK �[�M
v�$ �$ Parser/SourceHandler/Perl.pmnu �[��� package TAP::Parser::SourceHandler::Perl;
use strict;
use warnings;
use Config;
use constant IS_WIN32 => ( $^O =~ /^(MS)?Win32$/ );
use constant IS_VMS => ( $^O eq 'VMS' );
use TAP::Parser::IteratorFactory ();
use TAP::Parser::Iterator::Process ();
use Text::ParseWords qw(shellwords);
use base 'TAP::Parser::SourceHandler::Executable';
TAP::Parser::IteratorFactory->register_handler(__PACKAGE__);
=head1 NAME
TAP::Parser::SourceHandler::Perl - Stream TAP from a Perl executable
=head1 VERSION
Version 3.42
=cut
our $VERSION = '3.42';
=head1 SYNOPSIS
use TAP::Parser::Source;
use TAP::Parser::SourceHandler::Perl;
my $source = TAP::Parser::Source->new->raw( \'script.pl' );
$source->assemble_meta;
my $class = 'TAP::Parser::SourceHandler::Perl';
my $vote = $class->can_handle( $source );
my $iter = $class->make_iterator( $source );
=head1 DESCRIPTION
This is a I<Perl> L<TAP::Parser::SourceHandler> - it has 2 jobs:
1. Figure out if the L<TAP::Parser::Source> it's given is actually a Perl
script (L</can_handle>).
2. Creates an iterator for Perl sources (L</make_iterator>).
Unless you're writing a plugin or subclassing L<TAP::Parser>, you probably
won't need to use this module directly.
=head1 METHODS
=head2 Class Methods
=head3 C<can_handle>
my $vote = $class->can_handle( $source );
Only votes if $source looks like a file. Casts the following votes:
0.9 if it has a shebang ala "#!...perl"
0.75 if it has any shebang
0.8 if it's a .t file
0.9 if it's a .pl file
0.75 if it's in a 't' directory
0.25 by default (backwards compat)
=cut
sub can_handle {
my ( $class, $source ) = @_;
my $meta = $source->meta;
return 0 unless $meta->{is_file};
my $file = $meta->{file};
if ( my $shebang = $file->{shebang} ) {
return 0.9 if $shebang =~ /^#!.*\bperl/;
# We favour Perl as the interpreter for any shebang to preserve
# previous semantics: we used to execute everything via Perl and
# relied on it to pass the shebang off to the appropriate
# interpreter.
return 0.3;
}
return 0.8 if $file->{lc_ext} eq '.t'; # vote higher than Executable
return 0.9 if $file->{lc_ext} eq '.pl';
return 0.75 if $file->{dir} =~ /^t\b/; # vote higher than Executable
# backwards compat, always vote:
return 0.25;
}
=head3 C<make_iterator>
my $iterator = $class->make_iterator( $source );
Constructs & returns a new L<TAP::Parser::Iterator::Process> for the source.
Assumes C<$source-E<gt>raw> contains a reference to the perl script. C<croak>s
if the file could not be found.
The command to run is built as follows:
$perl @switches $perl_script @test_args
The perl command to use is determined by L</get_perl>. The command generated
is guaranteed to preserve:
PERL5LIB
PERL5OPT
Taint Mode, if set in the script's shebang
I<Note:> the command generated will I<not> respect any shebang line defined in
your Perl script. This is only a problem if you have compiled a custom version
of Perl or if you want to use a specific version of Perl for one test and a
different version for another, for example:
#!/path/to/a/custom_perl --some --args
#!/usr/local/perl-5.6/bin/perl -w
Currently you need to write a plugin to get around this.
=cut
sub _autoflush_stdhandles {
my ($class) = @_;
$class->_autoflush( \*STDOUT );
$class->_autoflush( \*STDERR );
}
sub make_iterator {
my ( $class, $source ) = @_;
my $meta = $source->meta;
my $perl_script = ${ $source->raw };
$class->_croak("Cannot find ($perl_script)") unless $meta->{is_file};
# TODO: does this really need to be done here?
$class->_autoflush_stdhandles;
my ( $libs, $switches )
= $class->_mangle_switches(
$class->_filter_libs( $class->_switches($source) ) );
$class->_run( $source, $libs, $switches );
}
sub _has_taint_switch {
my( $class, $switches ) = @_;
my $has_taint = grep { $_ eq "-T" || $_ eq "-t" } @{$switches};
return $has_taint ? 1 : 0;
}
sub _mangle_switches {
my ( $class, $libs, $switches ) = @_;
# Taint mode ignores environment variables so we must retranslate
# PERL5LIB as -I switches and place PERL5OPT on the command line
# in order that it be seen.
if ( $class->_has_taint_switch($switches) ) {
my @perl5lib = defined $ENV{PERL5LIB} ? split /$Config{path_sep}/, $ENV{PERL5LIB} : ();
return (
$libs,
[ @{$switches},
$class->_libs2switches([@$libs, @perl5lib]),
defined $ENV{PERL5OPT} ? shellwords( $ENV{PERL5OPT} ) : ()
],
);
}
return ( $libs, $switches );
}
sub _filter_libs {
my ( $class, @switches ) = @_;
my $path_sep = $Config{path_sep};
my $path_re = qr{$path_sep};
# Filter out any -I switches to be handled as libs later.
#
# Nasty kludge. It might be nicer if we got the libs separately
# although at least this way we find any -I switches that were
# supplied other then as explicit libs.
#
# We filter out any names containing colons because they will break
# PERL5LIB
my @libs;
my @filtered_switches;
for (@switches) {
if ( !/$path_re/ && m/ ^ ['"]? -I ['"]? (.*?) ['"]? $ /x ) {
push @libs, $1;
}
else {
push @filtered_switches, $_;
}
}
return \@libs, \@filtered_switches;
}
sub _iterator_hooks {
my ( $class, $source, $libs, $switches ) = @_;
my $setup = sub {
if ( @{$libs} and !$class->_has_taint_switch($switches) ) {
$ENV{PERL5LIB} = join(
$Config{path_sep}, grep {defined} @{$libs},
$ENV{PERL5LIB}
);
}
};
# VMS environment variables aren't guaranteed to reset at the end of
# the process, so we need to put PERL5LIB back.
my $previous = $ENV{PERL5LIB};
my $teardown = sub {
if ( defined $previous ) {
$ENV{PERL5LIB} = $previous;
}
else {
delete $ENV{PERL5LIB};
}
};
return ( $setup, $teardown );
}
sub _run {
my ( $class, $source, $libs, $switches ) = @_;
my @command = $class->_get_command_for_switches( $source, $switches )
or $class->_croak("No command found!");
my ( $setup, $teardown ) = $class->_iterator_hooks( $source, $libs, $switches );
return $class->_create_iterator( $source, \@command, $setup, $teardown );
}
sub _create_iterator {
my ( $class, $source, $command, $setup, $teardown ) = @_;
return TAP::Parser::Iterator::Process->new(
{ command => $command,
merge => $source->merge,
setup => $setup,
teardown => $teardown,
}
);
}
sub _get_command_for_switches {
my ( $class, $source, $switches ) = @_;
my $file = ${ $source->raw };
my @args = @{ $source->test_args || [] };
my $command = $class->get_perl;
# XXX don't need to quote if we treat the parts as atoms (except maybe vms)
#$file = qq["$file"] if ( $file =~ /\s/ ) && ( $file !~ /^".*"$/ );
my @command = ( $command, @{$switches}, $file, @args );
return @command;
}
sub _libs2switches {
my $class = shift;
return map {"-I$_"} grep {$_} @{ $_[0] };
}
=head3 C<get_taint>
Decode any taint switches from a Perl shebang line.
# $taint will be 't'
my $taint = TAP::Parser::SourceHandler::Perl->get_taint( '#!/usr/bin/perl -t' );
# $untaint will be undefined
my $untaint = TAP::Parser::SourceHandler::Perl->get_taint( '#!/usr/bin/perl' );
=cut
sub get_taint {
my ( $class, $shebang ) = @_;
return
unless defined $shebang
&& $shebang =~ /^#!.*\bperl.*\s-\w*([Tt]+)/;
return $1;
}
sub _switches {
my ( $class, $source ) = @_;
my $file = ${ $source->raw };
my @switches = @{ $source->switches || [] };
my $shebang = $source->meta->{file}->{shebang};
return unless defined $shebang;
my $taint = $class->get_taint($shebang);
push @switches, "-$taint" if defined $taint;
# Quote the argument if we're VMS, since VMS will downcase anything
# not quoted.
if (IS_VMS) {
for (@switches) {
$_ = qq["$_"];
}
}
return @switches;
}
=head3 C<get_perl>
Gets the version of Perl currently running the test suite.
=cut
sub get_perl {
my $class = shift;
return $ENV{HARNESS_PERL} if defined $ENV{HARNESS_PERL};
return qq["$^X"] if IS_WIN32 && ( $^X =~ /[^\w\.\/\\]/ );
return $^X;
}
1;
__END__
=head1 SUBCLASSING
Please see L<TAP::Parser/SUBCLASSING> for a subclassing overview.
=head2 Example
package MyPerlSourceHandler;
use strict;
use TAP::Parser::SourceHandler::Perl;
use base 'TAP::Parser::SourceHandler::Perl';
# use the version of perl from the shebang line in the test file
sub get_perl {
my $self = shift;
if (my $shebang = $self->shebang( $self->{file} )) {
$shebang =~ /^#!(.*\bperl.*?)(?:(?:\s)|(?:$))/;
return $1 if $1;
}
return $self->SUPER::get_perl(@_);
}
=head1 SEE ALSO
L<TAP::Object>,
L<TAP::Parser>,
L<TAP::Parser::IteratorFactory>,
L<TAP::Parser::SourceHandler>,
L<TAP::Parser::SourceHandler::Executable>,
L<TAP::Parser::SourceHandler::File>,
L<TAP::Parser::SourceHandler::Handle>,
L<TAP::Parser::SourceHandler::RawTAP>
=cut
PK �[�Z�� � "