The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

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::Iterators 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::Iterators 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.