Spaces:
Running
Running
| 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.43 | |
| =cut | |
| our $VERSION = '3.43'; | |
| 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 ) unless $self->{start_time}; | |
| $self->start_times( $self->get_times ) unless $self->{start_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 <[email protected]> | |
| Andy Armstong <[email protected]> | |
| 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 <[email protected]> | |
| Nicholas Clark <[email protected]> | |
| Lee Johnson <notfadeaway at btinternet dot com> | |
| Philippe Bruhat <[email protected]> | |
| =head1 BUGS | |
| Please report any bugs or feature requests to | |
| C<[email protected]>, 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; | |