Why not adopt me?
NAME
TAP::Filter::Iterator - A TAP filter
VERSION
This document describes TAP::Filter::Iterator version 0.04
SYNOPSIS
use TAP::Parser;
use TAP::Filter::Iterator;
my $parser = TAP::Parser->new({ source => 'test.t' });
my $filter = TAP::Filter::Iterator->new;
$filter->add_to_parser( $parser );
DESCRIPTION
TAP::Filter
allows arbitrary filters to be placed in the TAP processing pipeline of TAP::Harness. Installed filters see the parsed TAP stream a line at a time and can modify the stream by
replacing a result
injecting extra results
removing results
An individual filter in the processing pipeline is a TAP::Filter::Iterator
or a subclass of it. Here is a simple filter:
package MyFilter;
use strict;
use warnings;
use base qw( TAP::Filter::Iterator );
sub inspect {
my ( $self, $result ) = @_;
# Perform some manipulation here...
return $result;
}
1;
The inspect
method is called for each line of TAP. The $result
argument is an instance of TAP::Parser::Result, the class that represents TAP tokens within TAP::Parser. The return value of inspect
is a list of results that will replace the result being processed.
Here's a simple inspect
implementation that flags an error for any test that has no description:
sub inspect {
my ( $self, $result ) = @_;
if ( $result->is_test ) {
my $description = $result->description;
unless ( defined $description && $description =~ /\S/ ) {
return (
$result,
TAP::Filter->ok(
ok => 0,
description =>
'Preceding test has no description'
)
);
}
}
return $result;
}
Note that inspect
sees all TAP tokens; not just those that represent test results. In this case I'm only interested in test results so I call is_test
to check the type of the result.
If I have a test I then call description
to get its descriptive text. If the description is undefined or contains no non-blank characters I return the original $result
followed by a new, failed test result that I synthesize by calling TAP::Filter->ok
.
By returning a pair of values I'm adding an extra result to the TAP stream. The filter automatically adjust's TAP::Parser
's notion of how many tests have been planned and renumbers subsequent test results to account for the additional result.
Any number of additional tests may be injected into the TAP stream in this way. It is not necessary to return the original $result
as part of the list; the returned list can consist solely of new, synthetic tokens. If $result
is present it need not be the first item in the list; that is, it is legal to inject additional results before or after the original $result
.
Note that the result tokens you return may be modified by TAP::Filter::Iterator
; for example tests may be renumbered. For this reason you should not retain a reference to the returned results and expect them to remain unaltered and should not use the same result instance more than once.
To remove a token from the TAP stream return an empty list from inspect
.
Filter lifecycle
When a filter is loaded by TAP::Filter the same filter instance may be used to process the output of multiple test files. If a filter has state that it would like to reset before each file it should override the init
method:
sub init {
my $self = shift;
$self->{_test_count} = 0; # for example
}
Similarly a filter that needs to clean up at the end of each file may override done
:
sub done {
my $self = shift;
close $self->{_log_file}; # for example
}
An alternative to subclassing
Instead of subclassing TAP::Filter::Iterator
you may use it directly as a filter by supplying one, two or three closures that correspond to the inspect
, init
and done
methods:
my $filter = TAP::Filter::Iterator->new(
sub { # inspect
my $result = shift;
return $result;
},
sub { # init
$count = 0;
},
sub { # done
close $log_file;
}
);
Note that unlike the corresponding methods the anonymous subroutines are not passed a $self
reference. In all other ways their interface is the same.
INTERFACE
new
Create a new TAP::Filter::Iterator
. You may optionally supply one, two or three subroutine references that provide handlers for inspect
, init
and done
.
Subclasses that wish to provide their own constructor should look like this:
package MyFilter;
use base qw( TAP::Filter::Iterator );
sub new {
my $class = shift;
my $self = $class->SUPER::new;
# Perform our own initialisation
# Return instance
return $self;
}
add_to_parser
Add this filter to the specified TAP::Parser
. Filters must be added after the parser is created but before the first TAP is read through it.
$filter->add_to_parser( $parser );
When filters are loaded by TAP::Filter add_to_parser
is called automatically at the appropriate time.
tokenize
TAP::Filter::Iterator
s implement tokenize
so that they can stand in for a TAP::Parser::Grammar. TAP::Parser
calls tokenize
to read the next token from the TAP stream. If you wish to use a filter directly you may call tokenize
repeatedly to read tokens. At the end of the TAP token stream tokenize
returns undef
.
inspect
Override inspect
in a subclass to filter the TAP stream. Called for each token in the TAP stream. Returns a list of tokens to replace the input token. See the example implementation of inspect
above.
It is not necessary for subclasses to call the superclass inspect
.
init
Called before the first TAP token in each test's output is passed to inspect
. Override in a subclass to perform custom initialisation.
done
Called after the last token in a TAP stream has been read. Override to perform custom cleanup.
Utility methods
ok
A convenience method for creating new test results to inject into the TAP stream. This method is an alias for TAP::Filter::ok
provided here for convenient use in subclasses. See TAP::Filter for full documentation.
Accessors
A TAP::Filter::Iterator
has a number of attributes which may be retrieved or set using the following accessors. To read a value call the accessor with no arguments:
my $parser = $filter->parser;
To set the value pass it as an argument:
$filter->parser( $new_parser );
In many cases it will not be necessary to use these accessors.
inspect_hook
Get or set the closure that the default implementation of inspect
delegates to. This is only relevant if you are using the default implementation of inspect
. Normally closures are passed to new
; see the documentation for new
above for more details.
init_hook
Get or set the init
closure.
done_hook
Get or set the done
closure.
next_iterator
Multiple TAP::Filter::Iterator
s may be chained together. The parser's original TAP::Parser::Grammar
tokeniser is at the end of the iterator chain. An iterator's next_iterator
attribute contains a reference to the next iterator in the chain.
parser
A TAP::Filter::Iterator
has a reference, stored in the parser
attribute, to the parser to which it is attached so that it can update the parser's test count dynamically.
Implementation details and caveats
A filter may vary the number of tests that appear in a TAP stream. To avoid a plan error it must dynamically adjust the TAP::Parser
's test count. This is normally effective but may interract badly with other TAP::Parser
features in certain cases.
In particular if you are spooling TAP to a file (by passing the spool
option to TAP::Parser
) the plan line that is output to the file will be incorrect if the filter adjusts the number of tests. Without buffering the entire TAP stream this is hard to avoid; the plan token will already have been spooled to disk when the test count adjustments are applied.
CONFIGURATION AND ENVIRONMENT
TAP::Filter::Iterator requires no configuration files or environment variables.
DEPENDENCIES
TAP::Filter::Iterator
requires Test::Harness version 3.11 or later.
INCOMPATIBILITIES
None reported.
BUGS AND LIMITATIONS
No bugs have been reported.
Please report any bugs or feature requests to bug-tap-filter@rt.cpan.org
, or through the web interface at http://rt.cpan.org.
AUTHOR
Andy Armstrong <andy.armstrong@messagesystems.com>
LICENCE AND COPYRIGHT
This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See perlartistic.
Copyright (c) 2008, Message Systems, Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name Message Systems, Inc. nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.