diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 19:43:11 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 19:43:11 +0000 |
commit | fc22b3d6507c6745911b9dfcc68f1e665ae13dbc (patch) | |
tree | ce1e3bce06471410239a6f41282e328770aa404a /upstream/mageia-cauldron/man3pm/TAP::Parser.3pm | |
parent | Initial commit. (diff) | |
download | manpages-l10n-fc22b3d6507c6745911b9dfcc68f1e665ae13dbc.tar.xz manpages-l10n-fc22b3d6507c6745911b9dfcc68f1e665ae13dbc.zip |
Adding upstream version 4.22.0.upstream/4.22.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'upstream/mageia-cauldron/man3pm/TAP::Parser.3pm')
-rw-r--r-- | upstream/mageia-cauldron/man3pm/TAP::Parser.3pm | 1412 |
1 files changed, 1412 insertions, 0 deletions
diff --git a/upstream/mageia-cauldron/man3pm/TAP::Parser.3pm b/upstream/mageia-cauldron/man3pm/TAP::Parser.3pm new file mode 100644 index 00000000..91fa32b8 --- /dev/null +++ b/upstream/mageia-cauldron/man3pm/TAP::Parser.3pm @@ -0,0 +1,1412 @@ +.\" -*- mode: troff; coding: utf-8 -*- +.\" Automatically generated by Pod::Man 5.01 (Pod::Simple 3.43) +.\" +.\" Standard preamble: +.\" ======================================================================== +.de Sp \" Vertical space (when we can't use .PP) +.if t .sp .5v +.if n .sp +.. +.de Vb \" Begin verbatim text +.ft CW +.nf +.ne \\$1 +.. +.de Ve \" End verbatim text +.ft R +.fi +.. +.\" \*(C` and \*(C' are quotes in nroff, nothing in troff, for use with C<>. +.ie n \{\ +. ds C` "" +. ds C' "" +'br\} +.el\{\ +. ds C` +. ds C' +'br\} +.\" +.\" Escape single quotes in literal strings from groff's Unicode transform. +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.\" +.\" If the F register is >0, we'll generate index entries on stderr for +.\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index +.\" entries marked with X<> in POD. Of course, you'll have to process the +.\" output yourself in some meaningful fashion. +.\" +.\" Avoid warning from groff about undefined register 'F'. +.de IX +.. +.nr rF 0 +.if \n(.g .if rF .nr rF 1 +.if (\n(rF:(\n(.g==0)) \{\ +. if \nF \{\ +. de IX +. tm Index:\\$1\t\\n%\t"\\$2" +.. +. if !\nF==2 \{\ +. nr % 0 +. nr F 2 +. \} +. \} +.\} +.rr rF +.\" ======================================================================== +.\" +.IX Title "TAP::Parser 3pm" +.TH TAP::Parser 3pm 2023-11-28 "perl v5.38.2" "Perl Programmers Reference Guide" +.\" For nroff, turn off justification. Always turn off hyphenation; it makes +.\" way too many mistakes in technical documents. +.if n .ad l +.nh +.SH NAME +TAP::Parser \- Parse TAP output +.SH VERSION +.IX Header "VERSION" +Version 3.44 +.SH SYNOPSIS +.IX Header "SYNOPSIS" +.Vb 1 +\& use TAP::Parser; +\& +\& my $parser = TAP::Parser\->new( { source => $source } ); +\& +\& while ( my $result = $parser\->next ) { +\& print $result\->as_string; +\& } +.Ve +.SH DESCRIPTION +.IX Header "DESCRIPTION" +\&\f(CW\*(C`TAP::Parser\*(C'\fR 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 \f(CW\*(C`examples/\*(C'\fR. +.PP +There's a wiki dedicated to the Test Anything Protocol: +.PP +<http://testanything.org> +.PP +It includes the TAP::Parser Cookbook: +.PP +<http://testanything.org/testing\-with\-tap/perl/tap::parser\-cookbook.html> +.SH METHODS +.IX Header "METHODS" +.SS "Class Methods" +.IX Subsection "Class Methods" +\fR\f(CI\*(C`new\*(C'\fR\fI\fR +.IX Subsection "new" +.PP +.Vb 1 +\& my $parser = TAP::Parser\->new(\e%args); +.Ve +.PP +Returns a new \f(CW\*(C`TAP::Parser\*(C'\fR object. +.PP +The arguments should be a hashref with \fIone\fR of the following keys: +.IP \(bu 4 +\&\f(CW\*(C`source\*(C'\fR +.Sp +\&\fICHANGED in 3.18\fR +.Sp +This is the preferred method of passing input to the constructor. +.Sp +The \f(CW\*(C`source\*(C'\fR is used to create a TAP::Parser::Source that is passed to the +"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. +.Sp +To configure the \fIIteratorFactory\fR use the \f(CW\*(C`sources\*(C'\fR parameter below. +.Sp +Note that \f(CW\*(C`source\*(C'\fR, \f(CW\*(C`tap\*(C'\fR and \f(CW\*(C`exec\*(C'\fR are \fImutually exclusive\fR. +.IP \(bu 4 +\&\f(CW\*(C`tap\*(C'\fR +.Sp +\&\fICHANGED in 3.18\fR +.Sp +The value should be the complete TAP output. +.Sp +The \fItap\fR is used to create a TAP::Parser::Source that is passed to the +"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. +.Sp +To configure the \fIIteratorFactory\fR use the \f(CW\*(C`sources\*(C'\fR parameter below. +.Sp +Note that \f(CW\*(C`source\*(C'\fR, \f(CW\*(C`tap\*(C'\fR and \f(CW\*(C`exec\*(C'\fR are \fImutually exclusive\fR. +.IP \(bu 4 +\&\f(CW\*(C`exec\*(C'\fR +.Sp +Must be passed an array reference. +.Sp +The \fIexec\fR array ref is used to create a TAP::Parser::Source that is passed +to the "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. +.Sp +By default the TAP::Parser::SourceHandler::Executable class will create a +TAP::Parser::Iterator::Process object to handle the source. This passes the +array reference strings as command arguments to IPC::Open3::open3: +.Sp +.Vb 1 +\& exec => [ \*(Aq/usr/bin/ruby\*(Aq, \*(Aqt/my_test.rb\*(Aq ] +.Ve +.Sp +If any \f(CW\*(C`test_args\*(C'\fR are given they will be appended to the end of the command +argument list. +.Sp +To configure the \fIIteratorFactory\fR use the \f(CW\*(C`sources\*(C'\fR parameter below. +.Sp +Note that \f(CW\*(C`source\*(C'\fR, \f(CW\*(C`tap\*(C'\fR and \f(CW\*(C`exec\*(C'\fR are \fImutually exclusive\fR. +.PP +The following keys are optional. +.IP \(bu 4 +\&\f(CW\*(C`sources\*(C'\fR +.Sp +\&\fINEW to 3.18\fR. +.Sp +If set, \f(CW\*(C`sources\*(C'\fR must be a hashref containing the names of the +TAP::Parser::SourceHandlers to load and/or configure. The values are a +hash of configuration that will be accessible to the source handlers via +"config_for" in TAP::Parser::Source. +.Sp +For example: +.Sp +.Vb 5 +\& sources => { +\& Perl => { exec => \*(Aq/path/to/custom/perl\*(Aq }, +\& File => { extensions => [ \*(Aq.tap\*(Aq, \*(Aq.txt\*(Aq ] }, +\& MyCustom => { some => \*(Aqconfig\*(Aq }, +\& } +.Ve +.Sp +This will cause \f(CW\*(C`TAP::Parser\*(C'\fR to pass custom configuration to two of the built\- +in source handlers \- TAP::Parser::SourceHandler::Perl, +TAP::Parser::SourceHandler::File \- and attempt to load the \f(CW\*(C`MyCustom\*(C'\fR +class. See "load_handlers" in TAP::Parser::IteratorFactory for more detail. +.Sp +The \f(CW\*(C`sources\*(C'\fR parameter affects how \f(CW\*(C`source\*(C'\fR, \f(CW\*(C`tap\*(C'\fR and \f(CW\*(C`exec\*(C'\fR parameters +are handled. +.Sp +See TAP::Parser::IteratorFactory, TAP::Parser::SourceHandler and subclasses for +more details. +.IP \(bu 4 +\&\f(CW\*(C`callback\*(C'\fR +.Sp +If present, each callback corresponding to a given result type will be called +with the result as the argument if the \f(CW\*(C`run\*(C'\fR method is used: +.Sp +.Vb 7 +\& my %callbacks = ( +\& test => \e&test_callback, +\& plan => \e&plan_callback, +\& comment => \e&comment_callback, +\& bailout => \e&bailout_callback, +\& unknown => \e&unknown_callback, +\& ); +\& +\& my $aggregator = TAP::Parser::Aggregator\->new; +\& for my $file ( @test_files ) { +\& my $parser = TAP::Parser\->new( +\& { +\& source => $file, +\& callbacks => \e%callbacks, +\& } +\& ); +\& $parser\->run; +\& $aggregator\->add( $file, $parser ); +\& } +.Ve +.IP \(bu 4 +\&\f(CW\*(C`switches\*(C'\fR +.Sp +If using a Perl file as a source, optional switches may be passed which will +be used when invoking the perl executable. +.Sp +.Vb 4 +\& my $parser = TAP::Parser\->new( { +\& source => $test_file, +\& switches => [ \*(Aq\-Ilib\*(Aq ], +\& } ); +.Ve +.IP \(bu 4 +\&\f(CW\*(C`test_args\*(C'\fR +.Sp +Used in conjunction with the \f(CW\*(C`source\*(C'\fR and \f(CW\*(C`exec\*(C'\fR option to supply a reference +to an \f(CW@ARGV\fR style array of arguments to pass to the test program. +.IP \(bu 4 +\&\f(CW\*(C`spool\*(C'\fR +.Sp +If passed a filehandle will write a copy of all parsed TAP to that handle. +.IP \(bu 4 +\&\f(CW\*(C`merge\*(C'\fR +.Sp +If false, STDERR is not captured (though it is 'relayed' to keep it +somewhat synchronized with STDOUT.) +.Sp +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. +.Sp +Subtleties of this behavior may be platform-dependent and may change in +the future. +.IP \(bu 4 +\&\f(CW\*(C`grammar_class\*(C'\fR +.Sp +This option was introduced to let you easily customize which \fIgrammar\fR class +the parser should use. It defaults to TAP::Parser::Grammar. +.Sp +See also "make_grammar". +.IP \(bu 4 +\&\f(CW\*(C`result_factory_class\*(C'\fR +.Sp +This option was introduced to let you easily customize which \fIresult\fR +factory class the parser should use. It defaults to +TAP::Parser::ResultFactory. +.Sp +See also "make_result". +.IP \(bu 4 +\&\f(CW\*(C`iterator_factory_class\*(C'\fR +.Sp +\&\fICHANGED in 3.18\fR +.Sp +This option was introduced to let you easily customize which \fIiterator\fR +factory class the parser should use. It defaults to +TAP::Parser::IteratorFactory. +.SS "Instance Methods" +.IX Subsection "Instance Methods" +\fR\f(CI\*(C`next\*(C'\fR\fI\fR +.IX Subsection "next" +.PP +.Vb 4 +\& my $parser = TAP::Parser\->new( { source => $file } ); +\& while ( my $result = $parser\->next ) { +\& print $result\->as_string, "\en"; +\& } +.Ve +.PP +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. +.PP +If callbacks are used, they will be issued before this call returns. +.PP +Each result returned is a subclass of TAP::Parser::Result. See that +module and related classes for more information on how to use them. +.PP +\fR\f(CI\*(C`run\*(C'\fR\fI\fR +.IX Subsection "run" +.PP +.Vb 1 +\& $parser\->run; +.Ve +.PP +This method merely runs the parser and parses all of the TAP. +.PP +\fR\f(CI\*(C`make_grammar\*(C'\fR\fI\fR +.IX Subsection "make_grammar" +.PP +Make a new TAP::Parser::Grammar object and return it. Passes through any +arguments given. +.PP +The \f(CW\*(C`grammar_class\*(C'\fR can be customized, as described in "new". +.PP +\fR\f(CI\*(C`make_result\*(C'\fR\fI\fR +.IX Subsection "make_result" +.PP +Make a new TAP::Parser::Result object using the parser's +TAP::Parser::ResultFactory, and return it. Passes through any arguments +given. +.PP +The \f(CW\*(C`result_factory_class\*(C'\fR can be customized, as described in "new". +.PP +\fR\f(CI\*(C`make_iterator_factory\*(C'\fR\fI\fR +.IX Subsection "make_iterator_factory" +.PP +\&\fINEW to 3.18\fR. +.PP +Make a new TAP::Parser::IteratorFactory object and return it. Passes through +any arguments given. +.PP +\&\f(CW\*(C`iterator_factory_class\*(C'\fR can be customized, as described in "new". +.SH "INDIVIDUAL RESULTS" +.IX Header "INDIVIDUAL RESULTS" +If you've read this far in the docs, you've seen this: +.PP +.Vb 3 +\& while ( my $result = $parser\->next ) { +\& print $result\->as_string; +\& } +.Ve +.PP +Each result returned is a TAP::Parser::Result subclass, referred to as +\&\fIresult types\fR. +.SS "Result types" +.IX Subsection "Result types" +Basically, you fetch individual results from the TAP. The six types, with +examples of each, are as follows: +.IP \(bu 4 +Version +.Sp +.Vb 1 +\& TAP version 12 +.Ve +.IP \(bu 4 +Plan +.Sp +.Vb 1 +\& 1..42 +.Ve +.IP \(bu 4 +Pragma +.Sp +.Vb 1 +\& pragma +strict +.Ve +.IP \(bu 4 +Test +.Sp +.Vb 1 +\& ok 3 \- We should start with some foobar! +.Ve +.IP \(bu 4 +Comment +.Sp +.Vb 1 +\& # Hope we don\*(Aqt use up the foobar. +.Ve +.IP \(bu 4 +Bailout +.Sp +.Vb 1 +\& Bail out! We ran out of foobar! +.Ve +.IP \(bu 4 +Unknown +.Sp +.Vb 1 +\& ... yo, this ain\*(Aqt TAP! ... +.Ve +.PP +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. +.SS "Common type methods" +.IX Subsection "Common type methods" +\fR\f(CI\*(C`type\*(C'\fR\fI\fR +.IX Subsection "type" +.PP +Returns the type of result, such as \f(CW\*(C`comment\*(C'\fR or \f(CW\*(C`test\*(C'\fR. +.PP +\fR\f(CI\*(C`as_string\*(C'\fR\fI\fR +.IX Subsection "as_string" +.PP +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 \f(CW\*(C`raw\*(C'\fR method. +.PP +\fR\f(CI\*(C`raw\*(C'\fR\fI\fR +.IX Subsection "raw" +.PP +Returns the original line of text which was parsed. +.PP +\fR\f(CI\*(C`is_plan\*(C'\fR\fI\fR +.IX Subsection "is_plan" +.PP +Indicates whether or not this is the test plan line. +.PP +\fR\f(CI\*(C`is_test\*(C'\fR\fI\fR +.IX Subsection "is_test" +.PP +Indicates whether or not this is a test line. +.PP +\fR\f(CI\*(C`is_comment\*(C'\fR\fI\fR +.IX Subsection "is_comment" +.PP +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 +\&\f(CW\*(C`merge\*(C'\fR option. +.PP +\fR\f(CI\*(C`is_bailout\*(C'\fR\fI\fR +.IX Subsection "is_bailout" +.PP +Indicates whether or not this is bailout line. +.PP +\fR\f(CI\*(C`is_yaml\*(C'\fR\fI\fR +.IX Subsection "is_yaml" +.PP +Indicates whether or not the current item is a YAML block. +.PP +\fR\f(CI\*(C`is_unknown\*(C'\fR\fI\fR +.IX Subsection "is_unknown" +.PP +Indicates whether or not the current line could be parsed. +.PP +\fR\f(CI\*(C`is_ok\*(C'\fR\fI\fR +.IX Subsection "is_ok" +.PP +.Vb 1 +\& if ( $result\->is_ok ) { ... } +.Ve +.PP +Reports whether or not a given result has passed. Anything which is \fBnot\fR a +test result returns true. This is merely provided as a convenient shortcut +which allows you to do this: +.PP +.Vb 5 +\& my $parser = TAP::Parser\->new( { source => $source } ); +\& while ( my $result = $parser\->next ) { +\& # only print failing results +\& print $result\->as_string unless $result\->is_ok; +\& } +.Ve +.ie n .SS """plan"" methods" +.el .SS "\f(CWplan\fP methods" +.IX Subsection "plan methods" +.Vb 1 +\& if ( $result\->is_plan ) { ... } +.Ve +.PP +If the above evaluates as true, the following methods will be available on the +\&\f(CW$result\fR object. +.PP +\fR\f(CI\*(C`plan\*(C'\fR\fI\fR +.IX Subsection "plan" +.PP +.Vb 3 +\& if ( $result\->is_plan ) { +\& print $result\->plan; +\& } +.Ve +.PP +This is merely a synonym for \f(CW\*(C`as_string\*(C'\fR. +.PP +\fR\f(CI\*(C`directive\*(C'\fR\fI\fR +.IX Subsection "directive" +.PP +.Vb 1 +\& my $directive = $result\->directive; +.Ve +.PP +If a SKIP directive is included with the plan, this method will return it. +.PP +.Vb 1 +\& 1..0 # SKIP: why bother? +.Ve +.PP +\fR\f(CI\*(C`explanation\*(C'\fR\fI\fR +.IX Subsection "explanation" +.PP +.Vb 1 +\& my $explanation = $result\->explanation; +.Ve +.PP +If a SKIP directive was included with the plan, this method will return the +explanation, if any. +.ie n .SS """pragma"" methods" +.el .SS "\f(CWpragma\fP methods" +.IX Subsection "pragma methods" +.Vb 1 +\& if ( $result\->is_pragma ) { ... } +.Ve +.PP +If the above evaluates as true, the following methods will be available on the +\&\f(CW$result\fR object. +.PP +\fR\f(CI\*(C`pragmas\*(C'\fR\fI\fR +.IX Subsection "pragmas" +.PP +Returns a list of pragmas each of which is a + or \- followed by the +pragma name. +.ie n .SS """comment"" methods" +.el .SS "\f(CWcomment\fP methods" +.IX Subsection "comment methods" +.Vb 1 +\& if ( $result\->is_comment ) { ... } +.Ve +.PP +If the above evaluates as true, the following methods will be available on the +\&\f(CW$result\fR object. +.PP +\fR\f(CI\*(C`comment\*(C'\fR\fI\fR +.IX Subsection "comment" +.PP +.Vb 4 +\& if ( $result\->is_comment ) { +\& my $comment = $result\->comment; +\& print "I have something to say: $comment"; +\& } +.Ve +.ie n .SS """bailout"" methods" +.el .SS "\f(CWbailout\fP methods" +.IX Subsection "bailout methods" +.Vb 1 +\& if ( $result\->is_bailout ) { ... } +.Ve +.PP +If the above evaluates as true, the following methods will be available on the +\&\f(CW$result\fR object. +.PP +\fR\f(CI\*(C`explanation\*(C'\fR\fI\fR +.IX Subsection "explanation" +.PP +.Vb 4 +\& if ( $result\->is_bailout ) { +\& my $explanation = $result\->explanation; +\& print "We bailed out because ($explanation)"; +\& } +.Ve +.PP +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. +.ie n .SS """unknown"" methods" +.el .SS "\f(CWunknown\fP methods" +.IX Subsection "unknown methods" +.Vb 1 +\& if ( $result\->is_unknown ) { ... } +.Ve +.PP +There are no unique methods for unknown results. +.ie n .SS """test"" methods" +.el .SS "\f(CWtest\fP methods" +.IX Subsection "test methods" +.Vb 1 +\& if ( $result\->is_test ) { ... } +.Ve +.PP +If the above evaluates as true, the following methods will be available on the +\&\f(CW$result\fR object. +.PP +\fR\f(CI\*(C`ok\*(C'\fR\fI\fR +.IX Subsection "ok" +.PP +.Vb 1 +\& my $ok = $result\->ok; +.Ve +.PP +Returns the literal text of the \f(CW\*(C`ok\*(C'\fR or \f(CW\*(C`not ok\*(C'\fR status. +.PP +\fR\f(CI\*(C`number\*(C'\fR\fI\fR +.IX Subsection "number" +.PP +.Vb 1 +\& my $test_number = $result\->number; +.Ve +.PP +Returns the number of the test, even if the original TAP output did not supply +that number. +.PP +\fR\f(CI\*(C`description\*(C'\fR\fI\fR +.IX Subsection "description" +.PP +.Vb 1 +\& my $description = $result\->description; +.Ve +.PP +Returns the description of the test, if any. This is the portion after the +test number but before the directive. +.PP +\fR\f(CI\*(C`directive\*(C'\fR\fI\fR +.IX Subsection "directive" +.PP +.Vb 1 +\& my $directive = $result\->directive; +.Ve +.PP +Returns either \f(CW\*(C`TODO\*(C'\fR or \f(CW\*(C`SKIP\*(C'\fR if either directive was present for a test +line. +.PP +\fR\f(CI\*(C`explanation\*(C'\fR\fI\fR +.IX Subsection "explanation" +.PP +.Vb 1 +\& my $explanation = $result\->explanation; +.Ve +.PP +If a test had either a \f(CW\*(C`TODO\*(C'\fR or \f(CW\*(C`SKIP\*(C'\fR directive, this method will return +the accompanying explanation, if present. +.PP +.Vb 1 +\& not ok 17 \- \*(AqPigs can fly\*(Aq # TODO not enough acid +.Ve +.PP +For the above line, the explanation is \fInot enough acid\fR. +.PP +\fR\f(CI\*(C`is_ok\*(C'\fR\fI\fR +.IX Subsection "is_ok" +.PP +.Vb 1 +\& if ( $result\->is_ok ) { ... } +.Ve +.PP +Returns a boolean value indicating whether or not the test passed. Remember +that for TODO tests, the test always passes. +.PP +\&\fBNote:\fR this was formerly \f(CW\*(C`passed\*(C'\fR. The latter method is deprecated and +will issue a warning. +.PP +\fR\f(CI\*(C`is_actual_ok\*(C'\fR\fI\fR +.IX Subsection "is_actual_ok" +.PP +.Vb 1 +\& if ( $result\->is_actual_ok ) { ... } +.Ve +.PP +Returns a boolean value indicating whether or not the test passed, regardless +of its TODO status. +.PP +\&\fBNote:\fR this was formerly \f(CW\*(C`actual_passed\*(C'\fR. The latter method is deprecated +and will issue a warning. +.PP +\fR\f(CI\*(C`is_unplanned\*(C'\fR\fI\fR +.IX Subsection "is_unplanned" +.PP +.Vb 1 +\& if ( $test\->is_unplanned ) { ... } +.Ve +.PP +If a test number is greater than the number of planned tests, this method will +return true. Unplanned tests will \fIalways\fR return false for \f(CW\*(C`is_ok\*(C'\fR, +regardless of whether or not the test \f(CW\*(C`has_todo\*(C'\fR (see +TAP::Parser::Result::Test for more information about this). +.PP +\fR\f(CI\*(C`has_skip\*(C'\fR\fI\fR +.IX Subsection "has_skip" +.PP +.Vb 1 +\& if ( $result\->has_skip ) { ... } +.Ve +.PP +Returns a boolean value indicating whether or not this test had a SKIP +directive. +.PP +\fR\f(CI\*(C`has_todo\*(C'\fR\fI\fR +.IX Subsection "has_todo" +.PP +.Vb 1 +\& if ( $result\->has_todo ) { ... } +.Ve +.PP +Returns a boolean value indicating whether or not this test had a TODO +directive. +.PP +Note that TODO tests \fIalways\fR pass. If you need to know whether or not +they really passed, check the \f(CW\*(C`is_actual_ok\*(C'\fR method. +.PP +\fR\f(CI\*(C`in_todo\*(C'\fR\fI\fR +.IX Subsection "in_todo" +.PP +.Vb 1 +\& if ( $parser\->in_todo ) { ... } +.Ve +.PP +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. +.SH "TOTAL RESULTS" +.IX Header "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. +.SS "Individual Results" +.IX Subsection "Individual Results" +These results refer to individual tests which are run. +.PP +\fR\f(CI\*(C`passed\*(C'\fR\fI\fR +.IX Subsection "passed" +.PP +.Vb 2 +\& my @passed = $parser\->passed; # the test numbers which passed +\& my $passed = $parser\->passed; # the number of tests which passed +.Ve +.PP +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. +.PP +\fR\f(CI\*(C`failed\*(C'\fR\fI\fR +.IX Subsection "failed" +.PP +.Vb 2 +\& my @failed = $parser\->failed; # the test numbers which failed +\& my $failed = $parser\->failed; # the number of tests which failed +.Ve +.PP +This method lets you know which (or how many) tests failed. If a test passed +but had a TODO directive, it will \fBNOT\fR be counted as a failed test. +.PP +\fR\f(CI\*(C`actual_passed\*(C'\fR\fI\fR +.IX Subsection "actual_passed" +.PP +.Vb 2 +\& # 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; +.Ve +.PP +This method lets you know which (or how many) tests actually passed, +regardless of whether or not a TODO directive was found. +.PP +\fR\f(CI\*(C`actual_ok\*(C'\fR\fI\fR +.IX Subsection "actual_ok" +.PP +This method is a synonym for \f(CW\*(C`actual_passed\*(C'\fR. +.PP +\fR\f(CI\*(C`actual_failed\*(C'\fR\fI\fR +.IX Subsection "actual_failed" +.PP +.Vb 2 +\& # 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; +.Ve +.PP +This method lets you know which (or how many) tests actually failed, +regardless of whether or not a TODO directive was found. +.PP +\fR\f(CI\*(C`todo\*(C'\fR\fI\fR +.IX Subsection "todo" +.PP +.Vb 2 +\& my @todo = $parser\->todo; # the test numbers with todo directives +\& my $todo = $parser\->todo; # the number of tests with todo directives +.Ve +.PP +This method lets you know which (or how many) tests had TODO directives. +.PP +\fR\f(CI\*(C`todo_passed\*(C'\fR\fI\fR +.IX Subsection "todo_passed" +.PP +.Vb 2 +\& # 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; +.Ve +.PP +This method lets you know which (or how many) tests actually passed but were +declared as "TODO" tests. +.PP +\fR\f(CI\*(C`todo_failed\*(C'\fR\fI\fR +.IX Subsection "todo_failed" +.PP +.Vb 1 +\& # deprecated in favor of \*(Aqtodo_passed\*(Aq. This method was horribly misnamed. +.Ve +.PP +This was a badly misnamed method. It indicates which TODO tests unexpectedly +succeeded. Will now issue a warning and call \f(CW\*(C`todo_passed\*(C'\fR. +.PP +\fR\f(CI\*(C`skipped\*(C'\fR\fI\fR +.IX Subsection "skipped" +.PP +.Vb 2 +\& my @skipped = $parser\->skipped; # the test numbers with SKIP directives +\& my $skipped = $parser\->skipped; # the number of tests with SKIP directives +.Ve +.PP +This method lets you know which (or how many) tests had SKIP directives. +.SS Pragmas +.IX Subsection "Pragmas" +\fR\f(CI\*(C`pragma\*(C'\fR\fI\fR +.IX Subsection "pragma" +.PP +Get or set a pragma. To get the state of a pragma: +.PP +.Vb 3 +\& if ( $p\->pragma(\*(Aqstrict\*(Aq) ) { +\& # be strict +\& } +.Ve +.PP +To set the state of a pragma: +.PP +.Vb 1 +\& $p\->pragma(\*(Aqstrict\*(Aq, 1); # enable strict mode +.Ve +.PP +\fR\f(CI\*(C`pragmas\*(C'\fR\fI\fR +.IX Subsection "pragmas" +.PP +Get a list of all the currently enabled pragmas: +.PP +.Vb 1 +\& my @pragmas_enabled = $p\->pragmas; +.Ve +.SS "Summary Results" +.IX Subsection "Summary Results" +These results are "meta" information about the total results of an individual +test program. +.PP +\fR\f(CI\*(C`plan\*(C'\fR\fI\fR +.IX Subsection "plan" +.PP +.Vb 1 +\& my $plan = $parser\->plan; +.Ve +.PP +Returns the test plan, if found. +.PP +\fR\f(CI\*(C`good_plan\*(C'\fR\fI\fR +.IX Subsection "good_plan" +.PP +Deprecated. Use \f(CW\*(C`is_good_plan\*(C'\fR instead. +.PP +\fR\f(CI\*(C`is_good_plan\*(C'\fR\fI\fR +.IX Subsection "is_good_plan" +.PP +.Vb 1 +\& if ( $parser\->is_good_plan ) { ... } +.Ve +.PP +Returns a boolean value indicating whether or not the number of tests planned +matches the number of tests run. +.PP +\&\fBNote:\fR this was formerly \f(CW\*(C`good_plan\*(C'\fR. The latter method is deprecated and +will issue a warning. +.PP +And since we're on that subject ... +.PP +\fR\f(CI\*(C`tests_planned\*(C'\fR\fI\fR +.IX Subsection "tests_planned" +.PP +.Vb 1 +\& print $parser\->tests_planned; +.Ve +.PP +Returns the number of tests planned, according to the plan. For example, a +plan of '1..17' will mean that 17 tests were planned. +.PP +\fR\f(CI\*(C`tests_run\*(C'\fR\fI\fR +.IX Subsection "tests_run" +.PP +.Vb 1 +\& print $parser\->tests_run; +.Ve +.PP +Returns the number of tests which actually were run. Hopefully this will +match the number of \f(CW\*(C`$parser\->tests_planned\*(C'\fR. +.PP +\fR\f(CI\*(C`skip_all\*(C'\fR\fI\fR +.IX Subsection "skip_all" +.PP +Returns a true value (actually the reason for skipping) if all tests +were skipped. +.PP +\fR\f(CI\*(C`start_time\*(C'\fR\fI\fR +.IX Subsection "start_time" +.PP +Returns the wall-clock time when the Parser was created. +.PP +\fR\f(CI\*(C`end_time\*(C'\fR\fI\fR +.IX Subsection "end_time" +.PP +Returns the wall-clock time when the end of TAP input was seen. +.PP +\fR\f(CI\*(C`start_times\*(C'\fR\fI\fR +.IX Subsection "start_times" +.PP +Returns the CPU times (like "times" in perlfunc when the Parser was created. +.PP +\fR\f(CI\*(C`end_times\*(C'\fR\fI\fR +.IX Subsection "end_times" +.PP +Returns the CPU times (like "times" in perlfunc when the end of TAP +input was seen. +.PP +\fR\f(CI\*(C`has_problems\*(C'\fR\fI\fR +.IX Subsection "has_problems" +.PP +.Vb 3 +\& if ( $parser\->has_problems ) { +\& ... +\& } +.Ve +.PP +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. +.PP +\fR\f(CI\*(C`version\*(C'\fR\fI\fR +.IX Subsection "version" +.PP +.Vb 1 +\& $parser\->version; +.Ve +.PP +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. +.PP +\fR\f(CI\*(C`exit\*(C'\fR\fI\fR +.IX Subsection "exit" +.PP +.Vb 1 +\& $parser\->exit; +.Ve +.PP +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. +.PP +\fR\f(CI\*(C`wait\*(C'\fR\fI\fR +.IX Subsection "wait" +.PP +.Vb 1 +\& $parser\->wait; +.Ve +.PP +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 \f(CW\*(C`exit\*(C'\fR status. +.ie n .SS """ignore_exit""" +.el .SS \f(CWignore_exit\fP +.IX Subsection "ignore_exit" +.Vb 1 +\& $parser\->ignore_exit(1); +.Ve +.PP +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. +.PP +\fR\f(CI\*(C`parse_errors\*(C'\fR\fI\fR +.IX Subsection "parse_errors" +.PP +.Vb 2 +\& my @errors = $parser\->parse_errors; # the parser errors +\& my $errors = $parser\->parse_errors; # the number of parser_errors +.Ve +.PP +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 \f(CW\*(C`not\*(C'\fR an error. This allows this parser to handle future +versions of TAP. The following are all TAP errors reported by the parser: +.IP \(bu 4 +Misplaced plan +.Sp +The plan (for example, '1..5'), must only come at the beginning or end of the +TAP output. +.IP \(bu 4 +No plan +.Sp +Gotta have a plan! +.IP \(bu 4 +More than one plan +.Sp +.Vb 5 +\& 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 +.Ve +.Sp +Right. Very funny. Don't do that. +.IP \(bu 4 +Test numbers out of sequence +.Sp +.Vb 4 +\& 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 +.Ve +.Sp +That last test line above should have the number '3' instead of '2'. +.Sp +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: +.Sp +.Vb 4 +\& 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 +.Ve +.Sp +But this is not: +.Sp +.Vb 4 +\& 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 +.Ve +.PP +\fR\f(CI\*(C`get_select_handles\*(C'\fR\fI\fR +.IX Subsection "get_select_handles" +.PP +Get an a list of file handles which can be passed to \f(CW\*(C`select\*(C'\fR to +determine the readiness of this parser. +.PP +\fR\f(CI\*(C`delete_spool\*(C'\fR\fI\fR +.IX Subsection "delete_spool" +.PP +Delete and return the spool. +.PP +.Vb 1 +\& my $fh = $parser\->delete_spool; +.Ve +.SH CALLBACKS +.IX Header "CALLBACKS" +As mentioned earlier, a "callback" key may be added to the +\&\f(CW\*(C`TAP::Parser\*(C'\fR constructor. If present, each callback corresponding to a +given result type will be called with the result as the argument if the +\&\f(CW\*(C`run\*(C'\fR 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. +.PP +.Vb 7 +\& my %callbacks = ( +\& test => \e&test_callback, +\& plan => \e&plan_callback, +\& comment => \e&comment_callback, +\& bailout => \e&bailout_callback, +\& unknown => \e&unknown_callback, +\& ); +\& +\& my $aggregator = TAP::Parser::Aggregator\->new; +\& for my $file ( @test_files ) { +\& my $parser = TAP::Parser\->new( +\& { +\& source => $file, +\& callbacks => \e%callbacks, +\& } +\& ); +\& $parser\->run; +\& $aggregator\->add( $file, $parser ); +\& } +.Ve +.PP +Callbacks may also be added like this: +.PP +.Vb 2 +\& $parser\->callback( test => \e&test_callback ); +\& $parser\->callback( plan => \e&plan_callback ); +.Ve +.PP +The following keys allowed for callbacks. These keys are case-sensitive. +.IP \(bu 4 +\&\f(CW\*(C`test\*(C'\fR +.Sp +Invoked if \f(CW\*(C`$result\->is_test\*(C'\fR returns true. +.IP \(bu 4 +\&\f(CW\*(C`version\*(C'\fR +.Sp +Invoked if \f(CW\*(C`$result\->is_version\*(C'\fR returns true. +.IP \(bu 4 +\&\f(CW\*(C`plan\*(C'\fR +.Sp +Invoked if \f(CW\*(C`$result\->is_plan\*(C'\fR returns true. +.IP \(bu 4 +\&\f(CW\*(C`comment\*(C'\fR +.Sp +Invoked if \f(CW\*(C`$result\->is_comment\*(C'\fR returns true. +.IP \(bu 4 +\&\f(CW\*(C`bailout\*(C'\fR +.Sp +Invoked if \f(CW\*(C`$result\->is_unknown\*(C'\fR returns true. +.IP \(bu 4 +\&\f(CW\*(C`yaml\*(C'\fR +.Sp +Invoked if \f(CW\*(C`$result\->is_yaml\*(C'\fR returns true. +.IP \(bu 4 +\&\f(CW\*(C`unknown\*(C'\fR +.Sp +Invoked if \f(CW\*(C`$result\->is_unknown\*(C'\fR returns true. +.IP \(bu 4 +\&\f(CW\*(C`ELSE\*(C'\fR +.Sp +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 \fInever\fR be invoked. +.IP \(bu 4 +\&\f(CW\*(C`ALL\*(C'\fR +.Sp +This callback will always be invoked and this will happen for each +result after one of the above callbacks is invoked. For example, if +Term::ANSIColor is loaded, you could use the following to color your +test output: +.Sp +.Vb 12 +\& my %callbacks = ( +\& test => sub { +\& my $test = shift; +\& if ( $test\->is_ok && not $test\->directive ) { +\& # normal passing test +\& print color \*(Aqgreen\*(Aq; +\& } +\& elsif ( !$test\->is_ok ) { # even if it\*(Aqs TODO +\& print color \*(Aqwhite on_red\*(Aq; +\& } +\& elsif ( $test\->has_skip ) { +\& print color \*(Aqwhite on_blue\*(Aq; +\& +\& } +\& elsif ( $test\->has_todo ) { +\& print color \*(Aqwhite\*(Aq; +\& } +\& }, +\& ELSE => sub { +\& # plan, comment, and so on (anything which isn\*(Aqt a test line) +\& print color \*(Aqblack on_white\*(Aq; +\& }, +\& ALL => sub { +\& # now print them +\& print shift\->as_string; +\& print color \*(Aqreset\*(Aq; +\& print "\en"; +\& }, +\& ); +.Ve +.IP \(bu 4 +\&\f(CW\*(C`EOF\*(C'\fR +.Sp +Invoked when there are no more lines to be parsed. Since there is no +accompanying TAP::Parser::Result object the \f(CW\*(C`TAP::Parser\*(C'\fR object is +passed instead. +.SH "TAP GRAMMAR" +.IX Header "TAP GRAMMAR" +If you're looking for an EBNF grammar, see TAP::Parser::Grammar. +.SH "BACKWARDS COMPATIBILITY" +.IX Header "BACKWARDS COMPATIBILITY" +The Perl-QA list attempted to ensure backwards compatibility with +Test::Harness. However, there are some minor differences. +.SS Differences +.IX Subsection "Differences" +.IP \(bu 4 +TODO plans +.Sp +A little-known feature of Test::Harness is that it supported TODO +lists in the plan: +.Sp +.Vb 3 +\& 1..2 todo 2 +\& ok 1 \- We have liftoff +\& not ok 2 \- Anti\-gravity device activated +.Ve +.Sp +Under Test::Harness, test number 2 would \fIpass\fR 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: +.Sp +.Vb 3 +\& 1..2 +\& ok 1 \- We have liftoff +\& not ok 2 \- Anti\-gravity device activated # TODO +.Ve +.IP \(bu 4 +\&'Missing' tests +.Sp +It rarely happens, but sometimes a harness might encounter +\&'missing tests: +.Sp +.Vb 5 +\& ok 1 +\& ok 2 +\& ok 15 +\& ok 16 +\& ok 17 +.Ve +.Sp +Test::Harness would report tests 3\-14 as having failed. For the +\&\f(CW\*(C`TAP::Parser\*(C'\fR, these tests are not considered failed because they've +never run. They're reported as parse failures (tests out of sequence). +.SH SUBCLASSING +.IX Header "SUBCLASSING" +If you find you need to provide custom functionality (as you would have using +Test::Harness::Straps), you're in luck: \f(CW\*(C`TAP::Parser\*(C'\fR and friends are +designed to be easily plugged-into and/or subclassed. +.PP +Before you start, it's important to know a few things: +.IP 1. 2 +All \f(CW\*(C`TAP::*\*(C'\fR objects inherit from TAP::Object. +.IP 2. 2 +Many \f(CW\*(C`TAP::*\*(C'\fR classes have a \fISUBCLASSING\fR section to guide you. +.IP 3. 2 +Note that \f(CW\*(C`TAP::Parser\*(C'\fR is designed to be the central "maker" \- ie: it is +responsible for creating most new objects in the \f(CW\*(C`TAP::Parser::*\*(C'\fR namespace. +.Sp +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. +.Sp +The exception to this rule are \fISourceHandlers\fR & \fIIterators\fR, but those are +both created with customizable \fIIteratorFactory\fR. +.IP 4. 2 +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 \fIdocumented\fR method needs changing, it will be +deprecated first, and changed in a later release. +.SS "Parser Components" +.IX Subsection "Parser Components" +\fISources\fR +.IX Subsection "Sources" +.PP +A TAP parser consumes input from a single \fIraw source\fR 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 TAP::Parser::Source object which gathers some +meta data about it. The parser then uses a TAP::Parser::IteratorFactory to +determine which TAP::Parser::SourceHandler to use to turn the raw source +into a stream of TAP by way of "Iterators". +.PP +If you simply want \f(CW\*(C`TAP::Parser\*(C'\fR to handle a new source of TAP you probably +don't need to subclass \f(CW\*(C`TAP::Parser\*(C'\fR itself. Rather, you'll need to create a +new TAP::Parser::SourceHandler class, and just plug it into the parser using +the \fIsources\fR param to "new". Before you start writing one, read through +TAP::Parser::IteratorFactory to get a feel for how the system works first. +.PP +If you find you really need to use your own iterator factory you can still do +so without sub-classing \f(CW\*(C`TAP::Parser\*(C'\fR by setting "iterator_factory_class". +.PP +If you just need to customize the objects on creation, subclass TAP::Parser +and override "make_iterator_factory". +.PP +Note that \f(CW\*(C`make_source\*(C'\fR & \f(CW\*(C`make_perl_source\*(C'\fR have been \fIDEPRECATED\fR and +are now removed. +.PP +\fIIterators\fR +.IX Subsection "Iterators" +.PP +A TAP parser uses \fIiterators\fR to loop through the \fIstream\fR of TAP read in +from the \fIsource\fR it was given. There are a few types of Iterators available +by default, all sub-classes of TAP::Parser::Iterator. Choosing which +iterator to use is the responsibility of the \fIiterator factory\fR, though it +simply delegates to the \fISource Handler\fR it uses. +.PP +If you're writing your own TAP::Parser::SourceHandler, you may need to +create your own iterators too. If so you'll need to subclass +TAP::Parser::Iterator. +.PP +Note that "make_iterator" has been \fIDEPRECATED\fR and is now removed. +.PP +\fIResults\fR +.IX Subsection "Results" +.PP +A TAP parser creates TAP::Parser::Results as it iterates through the +input \fIstream\fR. There are quite a few result types available; choosing +which class to use is the responsibility of the \fIresult factory\fR. +.PP +To create your own result types you have two options: +.IP "option 1" 2 +.IX Item "option 1" +Subclass TAP::Parser::Result and register your new result type/class with +the default TAP::Parser::ResultFactory. +.IP "option 2" 2 +.IX Item "option 2" +Subclass TAP::Parser::ResultFactory itself and implement your own +TAP::Parser::Result creation logic. Then you'll need to customize the +class used by your parser by setting the \f(CW\*(C`result_factory_class\*(C'\fR parameter. +See "new" for more details. +.PP +If you need to customize the objects on creation, subclass TAP::Parser and +override "make_result". +.PP +\fIGrammar\fR +.IX Subsection "Grammar" +.PP +TAP::Parser::Grammar is the heart of the parser. It tokenizes the TAP +input \fIstream\fR and produces results. If you need to customize its behaviour +you should probably familiarize yourself with the source first. Enough +lecturing. +.PP +Subclass TAP::Parser::Grammar and customize your parser by setting the +\&\f(CW\*(C`grammar_class\*(C'\fR parameter. See "new" for more details. +.PP +If you need to customize the objects on creation, subclass TAP::Parser and +override "make_grammar" +.SH ACKNOWLEDGMENTS +.IX Header "ACKNOWLEDGMENTS" +All of the following have helped. Bug reports, patches, (im)moral +support, or just words of encouragement have all been forthcoming. +.IP \(bu 4 +Michael Schwern +.IP \(bu 4 +Andy Lester +.IP \(bu 4 +chromatic +.IP \(bu 4 +GEOFFR +.IP \(bu 4 +Shlomi Fish +.IP \(bu 4 +Torsten Schoenfeld +.IP \(bu 4 +Jerry Gay +.IP \(bu 4 +Aristotle +.IP \(bu 4 +Adam Kennedy +.IP \(bu 4 +Yves Orton +.IP \(bu 4 +Adrian Howard +.IP \(bu 4 +Sean & Lil +.IP \(bu 4 +Andreas J. Koenig +.IP \(bu 4 +Florian Ragwitz +.IP \(bu 4 +Corion +.IP \(bu 4 +Mark Stosberg +.IP \(bu 4 +Matt Kraai +.IP \(bu 4 +David Wheeler +.IP \(bu 4 +Alex Vandiver +.IP \(bu 4 +Cosimo Streppone +.IP \(bu 4 +Ville Skyttä +.SH AUTHORS +.IX Header "AUTHORS" +Curtis "Ovid" Poe <ovid@cpan.org> +.PP +Andy Armstong <andy@hexten.net> +.PP +Eric Wilhelm @ <ewilhelm at cpan dot org> +.PP +Michael Peters <mpeters at plusthree dot com> +.PP +Leif Eriksen <leif dot eriksen at bigpond dot com> +.PP +Steve Purkis <spurkis@cpan.org> +.PP +Nicholas Clark <nick@ccl4.org> +.PP +Lee Johnson <notfadeaway at btinternet dot com> +.PP +Philippe Bruhat <book@cpan.org> +.SH BUGS +.IX Header "BUGS" +Please report any bugs or feature requests to +\&\f(CW\*(C`bug\-test\-harness@rt.cpan.org\*(C'\fR, or through the web interface at +<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. +.PP +Obviously, bugs which include patches are best. If you prefer, you can +patch against bleed by via anonymous checkout of the latest version: +.PP +.Vb 1 +\& git clone git://github.com/Perl\-Toolchain\-Gang/Test\-Harness.git +.Ve +.SH "COPYRIGHT & LICENSE" +.IX Header "COPYRIGHT & LICENSE" +Copyright 2006\-2008 Curtis "Ovid" Poe, all rights reserved. +.PP +This program is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. |