File Coverage

blib/lib/TAP/Stream.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package TAP::Stream;
2             $TAP::Stream::VERSION = '0.44';
3             # ABSTRACT: Combine multiple TAP streams with subtests
4              
5 3     3   151305 use Moose;
  0            
  0            
6             use TAP::Stream::Text;
7             use namespace::autoclean;
8             with qw(TAP::Stream::Role::ToString);
9              
10             has '_stream' => (
11             traits => ['Array'],
12             is => 'ro',
13             isa => 'ArrayRef[TAP::Stream::Role::ToString]',
14             default => sub { [] },
15             handles => {
16             add_to_stream => 'push',
17             is_empty => 'is_empty',
18             },
19             );
20              
21             sub to_string {
22             my $self = shift;
23             return '' if $self->is_empty;
24              
25             my $to_string = '';
26              
27             my $test_number = 0;
28              
29             foreach my $next ( @{ $self->_stream } ) {
30             $test_number++;
31             chomp( my $tap = $next->to_string );
32             my $name = $next->name;
33             $to_string .= $self->_build_tap( $tap, $name, $test_number );
34             }
35             $to_string .= "1..$test_number";
36             return $to_string;
37             }
38              
39             sub _build_tap {
40             my ( $self, $tap, $name, $test_number ) = @_;
41              
42             # I don't want to hardcode this, but it's hardcoded in Test::Builder.
43             # Given that I am the one who originally wrote the subtest() code in
44             # Test::Builder, this ugliness is my fault - Ovid
45             my $indent = ' ';
46              
47             my $failed = $self->_tap_failed($tap);
48             $tap =~ s/(?<=^)/$indent/gm;
49             if ($failed) {
50             $tap .= "\nnot ok $test_number - $name\n# $failed\n";
51             }
52             else {
53             $tap .= "\nok $test_number - $name\n";
54             }
55             return $tap;
56             }
57              
58             sub _tap_failed {
59             my ( $self, $tap ) = @_;
60             my $plan_re = qr/1\.\.(\d+)/;
61             my $test_re = qr/(?:not )?ok/;
62             my $failed;
63             my $core_tap = '';
64             foreach ( split "\n" => $tap ) {
65             if (/^not ok/) { # TODO tests are not failures
66             $failed++
67             unless m/^ ( [^\\\#]* (?: \\. [^\\\#]* )* )
68             \# \s* TODO \b \s* (.*) $/ix
69             }
70             $core_tap .= "$_\n" if /^(?:$plan_re|$test_re)/;
71             }
72             my $plan;
73             if ( $core_tap =~ /^$plan_re/ or $core_tap =~ /$plan_re$/ ) {
74             $plan = $1;
75             }
76             return 'No plan found' unless defined $plan;
77             return "Failed $failed out of $plan tests" if $failed;
78              
79             my $plans_found = 0;
80             $plans_found++ while $core_tap =~ /^$plan_re/gm;
81             return "$plans_found plans found" if $plans_found > 1;
82              
83             my $tests = 0;
84             $tests++ while $core_tap =~ /^$test_re/gm;
85             return "Planned $plan tests and found $tests tests" if $tests != $plan;
86              
87             return;
88             }
89              
90             __PACKAGE__->meta->make_immutable;
91              
92             1;
93              
94             __END__
95              
96             =pod
97              
98             =encoding UTF-8
99              
100             =head1 NAME
101              
102             TAP::Stream - Combine multiple TAP streams with subtests
103              
104             =head1 VERSION
105              
106             version 0.44
107              
108             =head1 SYNOPSIS
109              
110             use TAP::Stream;
111             use TAP::Stream::Text;
112              
113             my $tap1 = <<'END';
114             ok 1 - foo 1
115             ok 2 - foo 2
116             1..2
117             END
118              
119             # note that we have a failing test
120             my $tap2 = <<'END';
121             ok 1 - bar 1
122             ok 2 - bar 2
123             1..3
124             ok 1 - bar subtest 1
125             ok 2 - bar subtest 2
126             not ok 3 - bar subtest 3 #TODO ignore
127             ok 3 - bar subtest
128             not ok 4 - bar 4
129             1..4
130             END
131              
132             my $stream = TAP::Stream->new;
133              
134             $stream->add_to_stream(
135             TAP::Stream::Text->new( name => 'foo tests', text => $tap1 ),
136             TAP::Stream::Text->new( name => 'bar tests', text => $tap2 )
137             );
138              
139             print $stream->to_string;
140              
141             Output:
142              
143             ok 1 - foo 1
144             ok 2 - foo 2
145             1..2
146             ok 1 - foo tests
147             ok 1 - bar 1
148             ok 2 - bar 2
149             1..3
150             ok 1 - bar subtest 1
151             ok 2 - bar subtest 2
152             not ok 3 - bar subtest 3 #TODO ignore
153             ok 3 - bar subtest
154             not ok 4 - bar 4
155             1..4
156             not ok 2 - bar tests
157             # Failed 1 out of 4 tests
158             1..2
159              
160             =head1 DESCRIPTION
161              
162             Sometimes you find yourself needing to merge multiple streams of TAP.
163             Several use cases:
164              
165             =over 4
166              
167             =item * Merging results from parallel tests
168              
169             =item * Running tests across multiple boxes and fetching their TAP
170              
171             =item * Saving TAP and reassembling it later
172              
173             =back
174              
175             L<TAP::Stream> allows you to do this. You can both merge multiple chunks of
176             TAP text, or even multiple C<TAP::Stream> objects.
177              
178             =head1 DESCRIPTION
179              
180             B<Experimental> module to combine multiple TAP streams.
181              
182             =head1 METHODS
183              
184             =head2 C<new>
185              
186             my $stream = TAP::Stream->new( name => 'Parent stream' );
187              
188             Creates a TAP::Stream object. The name is optional, but highly recommend to be
189             unique. The top-level stream's name is not used, but if you use
190             C<add_to_stream> to add another stream object, that stream object should be
191             named or else the summary C<(not) ok> line will be named C<Unnamed TAP stream>
192             and this may make it harder to figure out which stream contained a failure.
193              
194             Names should be descriptive of the use case of the stream.
195              
196             =head2 C<name>
197              
198             my $name = $stream->name;
199              
200             A read/write string accessor.
201              
202             Returns the name of the stream. Default to C<Unnamed TAP stream>. If you add
203             this stream to another stream, consider naming this stream for a more useful
204             TAP output. This is used to create the subtest summary line:
205              
206             1..2
207             ok 1 - some test
208             ok 2 - another test
209             ok 1 - this is $stream->name
210              
211             =head2 C<add_to_stream>
212              
213             $stream->add_to_stream(TAP::Stream::Text->new(%args));
214             # or
215             $stream->add_to_stream($another_stream);
216              
217             Add a L<TAP::Stream::Text> object or another L<TAP::Stream> object. You may
218             call this method multiple times. The following two chunks of code are the
219             same:
220              
221             $stream->add_to_stream(
222             TAP::Stream::Text->new( name => 'foo tests', text => $tap1 ),
223             TAP::Stream::Text->new( name => 'bar tests', text => $tap2 )
224             );
225              
226             Versus:
227              
228             $stream->add_to_stream(
229             TAP::Stream::Text->new( name => 'foo tests', text => $tap1 ),
230             );
231             $stream->add_to_stream(
232             TAP::Stream::Text->new( name => 'bar tests', text => $tap2 )
233             );
234              
235             Stream objects can be added to other stream objects:
236              
237             my $parent = TAP::Stream->new; # the name is unused for the parent
238              
239             my $stream = TAP::Stream->new( name => 'child stream' );
240              
241             $stream->add_to_stream(
242             TAP::Stream::Text->new( name => 'foo tests', text => $tap1 ),
243             TAP::Stream::Text->new( name => 'bar tests', text => $tap2 )
244             );
245             $parent->add_to_stream($stream);
246              
247             # later:
248             $parent->add_to_stream($another_stream);
249             $parent->add_to_stream(TAP::Stream::Text->new%args);
250             $parent->add_to_stream($yet_another_stream);
251              
252             say $parent->to_string;
253              
254             =head2 C<to_string>
255              
256             say $stream->to_string;
257              
258             Prints the stream as TAP. We do not overload stringification.
259              
260             =head1 HOW IT WORKS
261              
262             Each chunk of TAP (or stream) that is added is added as a subtest. This avoids
263             issues of trying to recalculate the numbers. This means that if you
264             concatenate three TAP streams, each with 25 tests, you will still see 3 tests
265             reported (because you have three subtests).
266              
267             There is a mini-TAP parser within C<TAP::Stream>. As you add a chunk of TAP or
268             a stream, the parser analyzes the TAP and if there is a failure, the subtest
269             itself will be reported as a failure. Causes of failure:
270              
271             =over 4
272              
273             =item * Any failing tests (TODO tests, of course, are not failures)
274              
275             =item * No plan
276              
277             =item * Number of tests do not match the plan
278              
279             =item * More than one plan
280              
281             =back
282              
283             =head1 CAVEATS
284              
285             =over 4
286              
287             =item * Out-of-sequence tests not handled
288              
289             Currently we do not check for tests out of sequence because, in theory, test
290             numbers are strictly optional in TAP. Make sure your TAP emitters Do The Right
291             Thing. Patches welcome.
292              
293             =item * Partial streams not handled
294              
295             Each chunk of TAP added must be a complete chunk of TAP, complete with a plan.
296             You can't add tests 1 through 3, and then 4 through 7.
297              
298             =back
299              
300             =head1 AUTHOR
301              
302             Curtis "Ovid" Poe <ovid@cpan.org>
303              
304             =head1 COPYRIGHT AND LICENSE
305              
306             This software is copyright (c) 2013 by Curtis "Ovid" Poe.
307              
308             This is free software; you can redistribute it and/or modify it under
309             the same terms as the Perl 5 programming language system itself.
310              
311             =cut