File Coverage

blib/lib/TAP/Filter/Iterator.pm
Criterion Covered Total %
statement 122 122 100.0
branch 36 38 94.7
condition 15 18 83.3
subroutine 23 23 100.0
pod 3 3 100.0
total 199 204 97.5


line stmt bran cond sub pod time code
1             package TAP::Filter::Iterator;
2              
3 5     5   83001 use warnings;
  5         9  
  5         166  
4 5     5   26 use strict;
  5         10  
  5         138  
5 5     5   25 use Carp;
  5         10  
  5         404  
6 5     5   28 use List::Util qw( max );
  5         10  
  5         705  
7 5     5   3842 use TAP::Parser::Result;
  5         4602  
  5         1460  
8              
9             sub _thing_needs_coderef {
10 15     15   20 my $thing_name = shift;
11             return sub {
12 9     9   19 my $thing = shift;
13 9 100       248 croak "$thing_name must be a coderef"
14             unless 'CODE' eq ref $thing;
15 15         101 };
16             }
17              
18             BEGIN {
19             # Methods to alias from TAP::Filter
20 5     5   12 my @ALIASES = qw( ok );
21              
22             # Named callback hooks
23 5         16 my @HOOKS = qw( inspect init done );
24              
25             my %VALIDATOR = (
26             next_iterator => sub {
27 18         34 my $iter = shift;
28 18 100 100     1757 croak "Iterator must have a 'tokenize' method"
      100        
29             unless defined $iter
30             && UNIVERSAL::can( $iter, 'can' )
31             && $iter->can( 'tokenize' );
32             },
33             parser => sub {
34 32         59 my $parser = shift;
35 32 100 100     484 croak "parser must be a TAP::Parser"
      66        
36             unless !defined $parser
37             || ( UNIVERSAL::can( $parser, 'isa' )
38             && $parser->isa( 'TAP::Parser' ) );
39             },
40             # *_hook methods
41             (
42 5         26 map { ( "${_}_hook" => _thing_needs_coderef( $_ ) ) } @HOOKS
  15         53  
43             ),
44             );
45              
46 5         14 for my $alias ( @ALIASES ) {
47 5     5   28 no strict 'refs';
  5         11  
  5         319  
48 5         13 *{$alias} = *{"TAP::Filter::$alias"};
  5         25  
  5         28  
49             }
50              
51 5         9 for my $hook ( @HOOKS ) {
52 5     5   24 no strict 'refs';
  5         9  
  5         580  
53 15         29 my $hook_accessor = "${hook}_hook";
54 15         82 *{$hook} = sub {
55 76     76   114 my $self = shift;
56 76 100       252 if ( my $hook_func = $self->$hook_accessor() ) {
57 41         865 return $hook_func->( @_ );
58             }
59 35         96 return @_;
60 15         50 };
61             }
62              
63 5         31 while ( my ( $acc, $valid ) = each %VALIDATOR ) {
64 5     5   28 no strict 'refs';
  5         9  
  5         404  
65 25         4912 *{$acc} = sub {
66 166     166   3895 my $self = shift;
67 166 100       537 if ( @_ ) {
68 59         170 $valid->( my $val = shift );
69 53         226 $self->{$acc} = $val;
70             }
71 160         642 return $self->{$acc};
72 25         103 };
73             }
74             }
75              
76             =head1 NAME
77              
78             TAP::Filter::Iterator - A TAP filter
79              
80             =head1 VERSION
81              
82             This document describes TAP::Filter::Iterator version 0.04
83              
84             =cut
85              
86             our $VERSION = '0.04';
87              
88             =head1 SYNOPSIS
89              
90             use TAP::Parser;
91             use TAP::Filter::Iterator;
92              
93             my $parser = TAP::Parser->new({ source => 'test.t' });
94             my $filter = TAP::Filter::Iterator->new;
95             $filter->add_to_parser( $parser );
96              
97             =head1 DESCRIPTION
98              
99             C allows arbitrary filters to be placed in the TAP
100             processing pipeline of L. Installed filters see the parsed
101             TAP stream a line at a time and can modify the stream by
102              
103             =over
104              
105             =item * replacing a result
106              
107             =item * injecting extra results
108              
109             =item * removing results
110              
111             =back
112              
113             An individual filter in the processing pipeline is a
114             C or a subclass of it. Here is a simple filter:
115              
116             package MyFilter;
117              
118             use strict;
119             use warnings;
120             use base qw( TAP::Filter::Iterator );
121              
122             sub inspect {
123             my ( $self, $result ) = @_;
124             # Perform some manipulation here...
125             return $result;
126             }
127              
128             1;
129              
130             The C method is called for each line of TAP. The C<$result>
131             argument is an instance of L, the class that
132             represents TAP tokens within L. The return value of
133             C is a list of results that will replace the result being
134             processed.
135              
136             Here's a simple C implementation that flags an error for any
137             test that has no description:
138              
139             sub inspect {
140             my ( $self, $result ) = @_;
141             if ( $result->is_test ) {
142             my $description = $result->description;
143             unless ( defined $description && $description =~ /\S/ ) {
144             return (
145             $result,
146             TAP::Filter->ok(
147             ok => 0,
148             description =>
149             'Preceding test has no description'
150             )
151             );
152             }
153             }
154             return $result;
155             }
156              
157             Note that C sees all TAP tokens; not just those that represent
158             test results. In this case I'm only interested in test results so I call
159             C to check the type of the result.
160              
161             If I have a test I then call C to get its descriptive text.
162             If the description is undefined or contains no non-blank characters I
163             return the original C<$result> followed by a new, failed test result
164             that I synthesize by calling C<< TAP::Filter->ok >>.
165              
166             By returning a pair of values I'm adding an extra result to the TAP
167             stream. The filter automatically adjust's C's notion of how
168             many tests have been planned and renumbers subsequent test results to
169             account for the additional result.
170              
171             Any number of additional tests may be injected into the TAP stream in
172             this way. It is not necessary to return the original C<$result> as
173             part of the list; the returned list can consist solely of new,
174             synthetic tokens. If C<$result> is present it need not be the first item
175             in the list; that is, it is legal to inject additional results before or
176             after the original C<$result>.
177              
178             Note that the result tokens you return may be modified by
179             C; for example tests may be renumbered. For this
180             reason you should not retain a reference to the returned results and
181             expect them to remain unaltered and should not use the same result
182             instance more than once.
183              
184             To remove a token from the TAP stream return an empty list from
185             C.
186              
187             =head2 Filter lifecycle
188              
189             When a filter is loaded by L the same filter instance may
190             be used to process the output of multiple test files. If a filter has
191             state that it would like to reset before each file it should override
192             the C method:
193              
194             sub init {
195             my $self = shift;
196             $self->{_test_count} = 0; # for example
197             }
198              
199             Similarly a filter that needs to clean up at the end of each file may
200             override C:
201              
202             sub done {
203             my $self = shift;
204             close $self->{_log_file}; # for example
205             }
206              
207             =head2 An alternative to subclassing
208              
209             Instead of subclassing C you may use it directly
210             as a filter by supplying one, two or three closures that correspond to
211             the C, C and C methods:
212              
213             my $filter = TAP::Filter::Iterator->new(
214             sub { # inspect
215             my $result = shift;
216             return $result;
217             },
218             sub { # init
219             $count = 0;
220             },
221             sub { # done
222             close $log_file;
223             }
224             );
225              
226             Note that unlike the corresponding methods the anonymous subroutines are
227             not passed a C<$self> reference. In all other ways their interface is
228             the same.
229              
230             =head1 INTERFACE
231              
232             =head2 C<< new >>
233              
234             Create a new C. You may optionally supply one,
235             two or three subroutine references that provide handlers for C,
236             C and C.
237              
238             Subclasses that wish to provide their own constructor should look
239             like this:
240              
241             package MyFilter;
242             use base qw( TAP::Filter::Iterator );
243              
244             sub new {
245             my $class = shift;
246             my $self = $class->SUPER::new;
247             # Perform our own initialisation
248             # Return instance
249             return $self;
250             }
251              
252             =cut
253              
254             sub new {
255 12     12 1 110986 my $class = shift;
256 12         42 my $self = bless {}, $class;
257              
258 12 100       61 $self->inspect_hook( shift ) if @_;
259 12 100       44 $self->init_hook( shift ) if @_;
260 12 100       39 $self->done_hook( shift ) if @_;
261              
262 12         48 return $self;
263             }
264              
265             =head2 C<< add_to_parser >>
266              
267             Add this filter to the specified C. Filters must be added
268             after the parser is created but before the first TAP is read through it.
269              
270             $filter->add_to_parser( $parser );
271              
272             When filters are loaded by L C is called
273             automatically at the appropriate time.
274              
275             =cut
276              
277             sub add_to_parser {
278 15     15 1 808 my ( $self, $parser ) = @_;
279 15 100       88 $self = $self->new unless ref $self;
280 15         75 $self->parser( $parser );
281 15         78 $self->next_iterator( $parser->_grammar );
282 15         66 $parser->_grammar( $self );
283 15         122 $self->_recycle;
284              
285 15         403 return;
286             }
287              
288             sub _recycle {
289 15     15   22 my $self = shift;
290 15         317 delete $self->{_iter};
291 15         235 $self->{_plan_adjust} = 0;
292 15         78 $self->init;
293             }
294              
295             sub _set_test_number {
296 63     63   137 my ( $test, $number ) = @_;
297 63         165 $test->_number( $number );
298              
299             # Nasty encapsulation violation!
300 63 100       351 if ( exists $test->{raw} ) {
301 62         649 $test->{raw} =~ s/^((?:not\s+)?ok\s+)(?:\d+|\*)/$1$number/;
302             }
303             }
304              
305             sub _set_plan_count {
306 15     15   82 my ( $plan, $count ) = @_;
307              
308             # Nasty encapsulation violation!
309 15         65 $plan->{tests_planned} = $count;
310             }
311              
312             sub _iter {
313 15     15   29 my $self = shift;
314 15         39 my $iter = $self->next_iterator;
315              
316 15         31 my @queue = ();
317 15         28 my $in_number = 0;
318 15         24 my $out_number = 0;
319 15         17 my $last_adjust = 0;
320              
321             my $renumber = sub {
322 77     77   102 my $result = shift;
323 77 100       765 if ( $result->is_test ) {
    50          
324 62         399 $out_number++;
325 62         194 my $number = $result->number;
326 62 50 66     373 _set_test_number( $result,
327             $number == $in_number || $number == 0
328             ? $out_number
329             : max( 1, $number + $out_number - $in_number ) );
330             }
331             elsif ( $result->is_plan ) {
332 15         231 my $adjust = $out_number - $in_number;
333 15         77 _set_plan_count( $result,
334             $result->tests_planned + $adjust );
335             }
336 15         260 };
337              
338             return sub {
339 92     92   94 my $result;
340              
341             RESULT: {
342 92 100       100 if ( @queue ) {
  163         328  
343 77         128 $result = shift @queue;
344             }
345             else {
346 86         240 $result = $iter->tokenize;
347 86 100       109972 if ( defined $result ) {
348 71 100       280 $in_number++ if $result->is_test;
349 71         637 my @batch = grep defined, $self->inspect( $result );
350              
351 71         4602 for my $test ( @batch ) {
352 77         159 $renumber->( $test );
353             }
354              
355 71         133 push @queue, @batch;
356              
357             # Patch up the parser's test count. We need to do
358             # this continuously because the parser checks test
359             # numbers against the plan as it goes.
360 71         119 my $adjust = $out_number - $in_number;
361 71         223 $self->_adjust_test_count( $adjust - $last_adjust );
362 71         84 $last_adjust = $adjust;
363 71         143 redo RESULT;
364             }
365             }
366             }
367              
368 92 100       234 unless ( defined $result ) {
369             # Drop parser reference at end of stream to remove circular
370             # references.
371 15         57 $self->done;
372 15         112 $self->parser( undef );
373             }
374              
375 92         251 return $result;
376 15         152 };
377             }
378              
379             =head2 C<< tokenize >>
380              
381             Cs implement C so that they can stand
382             in for a L. C calls C to
383             read the next token from the TAP stream. If you wish to use a filter
384             directly you may call C repeatedly to read tokens. At the end
385             of the TAP token stream C returns C.
386              
387             =cut
388              
389             sub tokenize {
390 92     92 1 10622 my $self = shift;
391 92   66     37542 return ( $self->{_iter} ||= $self->_iter )->();
392             }
393              
394             sub _adjust_test_count {
395 71     71   100 my ( $self, $count ) = @_;
396 71 100       176 return unless $count;
397 16         39 my $parser = $self->parser;
398 16 100       52 if ( defined( my $tests_planned = $parser->tests_planned ) ) {
399 13         120 $parser->tests_planned(
400             $tests_planned + $count + $self->{_plan_adjust} );
401 13         79 $self->{_plan_adjust} = 0;
402             }
403             else {
404             # No plan yet - so remember the offset
405 3         24 $self->{_plan_adjust} += $count;
406             }
407             }
408              
409             =head2 C<< inspect >>
410              
411             Override C in a subclass to filter the TAP stream. Called for
412             each token in the TAP stream. Returns a list of tokens to replace the
413             input token. See the example implementation of C above.
414              
415             It is not necessary for subclasses to call the superclass C.
416              
417             =head2 C<< init >>
418              
419             Called before the first TAP token in each test's output is passed to
420             C. Override in a subclass to perform custom initialisation.
421              
422             =head2 C<< done >>
423              
424             Called after the last token in a TAP stream has been read. Override to
425             perform custom cleanup.
426              
427             =head1 Utility methods
428              
429             =head2 C<< ok >>
430              
431             A convenience method for creating new test results to inject into the
432             TAP stream. This method is an alias for C provided here
433             for convenient use in subclasses. See L for full documentation.
434              
435             =head1 Accessors
436              
437             A C has a number of attributes which may be
438             retrieved or set using the following accessors. To read a value call the
439             accessor with no arguments:
440              
441             my $parser = $filter->parser;
442              
443             To set the value pass it as an argument:
444              
445             $filter->parser( $new_parser );
446              
447             In many cases it will not be necessary to use these accessors.
448              
449             =head2 C<< inspect_hook >>
450              
451             Get or set the closure that the default implementation of C
452             delegates to. This is only relevant if you are using the default
453             implementation of C. Normally closures are passed to C;
454             see the documentation for C above for more details.
455              
456             =head2 C<< init_hook >>
457              
458             Get or set the C closure.
459              
460             =head2 C<< done_hook >>
461              
462             Get or set the C closure.
463              
464             =head2 C<< next_iterator >>
465              
466             Multiple Cs may be chained together. The
467             parser's original C tokeniser is at the end of
468             the iterator chain. An iterator's C attribute contains a
469             reference to the next iterator in the chain.
470              
471             =head2 C<< parser >>
472              
473             A C has a reference, stored in the C
474             attribute, to the parser to which it is attached so that it can update
475             the parser's test count dynamically.
476              
477             =cut
478              
479             1;
480             __END__