File Coverage

lib/TAP/Harness/ReportByDescription.pm
Criterion Covered Total %
statement 69 80 86.2
branch 18 38 47.3
condition 2 5 40.0
subroutine 7 7 100.0
pod 2 2 100.0
total 98 132 74.2


line stmt bran cond sub pod time code
1             package TAP::Harness::ReportByDescription;
2              
3 3     3   25975 use strict;
  3         6  
  3         148  
4 3     3   18 use base 'TAP::Harness';
  3         5  
  3         3539  
5             our $VERSION = '0.06';
6              
7             =head1 NAME
8              
9             TAP::Harness::ReportByDescription - Report TAP output by test file description rather than test file name
10              
11             =head1 VERSION
12              
13             0.01
14              
15             =head1 SYNOPSIS
16              
17             use TAP::Harness::ReportByDescription;
18             my $harness = TAP::Harness::ReportByDescription->new();
19             $harness->aggregate_tests($aggregator, @tests);
20              
21             =head1 DESCRIPTION
22              
23             This package subclasses TAP::Harness for the purpose of enabling a user to
24             report the TAP output for a test file by a user-provided description rather
25             than by the name of the test file itself.
26              
27             Why would you want to do this? Three reasons come to mind.
28              
29             =over 4
30              
31             =item * One Master Summary Rather Than Summaries for Individual Subharnesses
32              
33             Suppose that you had a F testing target that is in essence nothing more
34             than a sequential run of several smaller testing targets, each of which is a
35             separate invocation of a test harness.
36              
37             make fulltest : test_prep \
38             compilers \
39             src \
40             oo \
41             codingstd \
42             examples
43              
44             Other things being equal, you would get a B at the end of each of the
45             five targets or subharnesses. Under some circumstances, you might prefer to
46             get a single master Summary at the end of the overall program.
47              
48             =item * Multiple Runs of Same Tests in Different Environments
49              
50             Suppose that you had a set of tests that you wanted to run several times, each
51             time in a slightly different environment. You could write a program which
52             executes multiple runs, writing a summary after each run and then modifying
53             the environment for the next run.
54              
55             perl t/harness --gc-debug --runcore=bounds
56             perl t/harness --gc-debug --runcore=fast
57             perl t/harness --gc-debug --run-pbc
58              
59             As the TAP output flowed by, you would see three instances of each test:
60              
61             t/pmc/arrayiterator.t ............................ ok
62             # ...
63             t/pmc/arrayiterator.t ............................ ok
64             # ...
65             t/pmc/arrayiterator.t ............................ ok
66              
67             ... but you would not be able to tell from the test file's report itself which
68             harness it was a part of. Under certain circumstances it would be nice to be
69             able to differentiate among the different runs:
70              
71             bounds__t/pmc/arrayiterator.t .................... ok
72             # ...
73             fast__t/pmc/arrayiterator.t ...................... ok
74             # ...
75             pbc__t/pmc/arrayiterator.t ....................... ok
76              
77             Here you're providing a B of each run of each test which provides
78             an observer with more information.
79              
80             =item * Preparation of a Test Harness Archive
81              
82             The ability to provide a specific description for a different run of the same
83             test becomes crucial when preparing a test harness archive. Currently, CPAN
84             distribution Test::Harness::Archive stores the TAP for a particular test file
85             in a file with the name of the test file itself. If you do multiple runs of
86             the same file in different environments, a later run of a test will overwrite
87             the TAP file from an earlier run. You would therefore only be able to include
88             the TAP from the last subharness in an archive. That would impede you from
89             sharing the full results of testing via a smoke-test aggregator such as
90             Smolder.
91              
92             =back
93              
94             In short, we need (a) a way to run multiple harnesses as if they were one, (b)
95             run the same tests through multiple harnesses and be able to quickly identify
96             which harness we were running it through, and (c) store multiple versions of a
97             file's TAP output in a test harness archive.
98              
99             Need (a) can actually be fulfilled with existing TAP::Parser::Aggregator
100             functionality. Let's build on that to meet needs (b) and (c). To do that we
101             need one package to subclass TAP::Harness and one to subclass
102             TAP::Harness::Archive. TAP::Harness::ReportByDescription and
103             TAP::Harness::Archive::MultiplesHarnesses are these packages.
104              
105             =head1 METHODS
106              
107             =head2 C
108              
109             Inherited from TAP::Harness.
110              
111             =head2 C
112              
113             Replicated, along with methods called internally from this method, from
114             TAP::Harness. The only change occurs in an internal method
115             C<_get_parser_args()>, which now assigns the individual test's B to
116             one variable and a user-provided B to a second variable.
117              
118             my $test_prog = $job->filename;
119             my $spool_prog = $job->description;
120              
121             It is the latter variable which will appear on the console and in a test
122             archive. Since this occurs within an internal method, the user need make no
123             change in how C is called.
124              
125             =cut
126              
127             sub aggregate_tests {
128 16     16 1 241061 my ( $self, $aggregate, @tests ) = @_;
129              
130 16         650 my $scheduler = $self->make_scheduler(@tests);
131              
132             # #12458
133 16 50       18483 local $ENV{HARNESS_IS_VERBOSE} = 1
134             if $self->{formatter}->{verbosity} > 0;
135              
136             # Formatter gets only names.
137 16         90 $self->{formatter}->prepare( map { $_->description } $scheduler->get_all );
  32         1057  
138              
139 16 100       1328 if ( $self->{formatter}->{jobs} > 1 ) {
140 1         12 $self->_aggregate_parallel( $aggregate, $scheduler );
141             }
142             else {
143 15         67 $self->_aggregate_single( $aggregate, $scheduler );
144             }
145              
146 16         1535 return;
147             }
148              
149             sub _aggregate_parallel {
150 1     1   3 my ( $self, $aggregate, $scheduler ) = @_;
151              
152 1         8 my $jobs = $self->jobs;
153 1         13 my $mux = $self->_construct( $self->multiplexer_class );
154              
155             RESULT: {
156              
157             # Keep multiplexer topped up
158 1         2420 FILL:
159 11         46 while ( $mux->parsers < $jobs ) {
160 13         443 my $job = $scheduler->get_job;
161              
162             # If we hit a spinner stop filling and start running.
163 13 100 66     999 last FILL if !defined $job || $job->is_spinner;
164              
165 2         53 my ( $parser, $session ) = $self->make_parser($job);
166 2         27 $mux->add( $parser, [ $session, $job ] );
167             }
168              
169 11 100       48 if ( my ( $parser, $stash, $result ) = $mux->next ) {
170 10         45197 my ( $session, $job ) = @$stash;
171 10 100       37 if ( defined $result ) {
172 8         59 $session->result($result);
173 8 50       982 $self->_bailout($result) if $result->is_bailout;
174             }
175             else {
176              
177             # End of parser. Automatically removed from the mux.
178 2         30 $self->finish_parser( $parser, $session );
179 2         798 $self->_after_test( $aggregate, $job, $parser );
180 2         527 $job->finish;
181             }
182 10         217 redo RESULT;
183             }
184             }
185              
186 1         29 return;
187             }
188              
189             sub _aggregate_single {
190 15     15   31 my ( $self, $aggregate, $scheduler ) = @_;
191              
192             JOB:
193 15         71 while ( my $job = $scheduler->get_job ) {
194 30 50       4015 next JOB if $job->is_spinner;
195              
196 30         309 my ( $parser, $session ) = $self->make_parser($job);
197              
198 30         411 while ( defined( my $result = $parser->next ) ) {
199 120         1774336 $session->result($result);
200 120 50       35022 if ( $result->is_bailout ) {
201              
202             # Keep reading until input is exhausted in the hope
203             # of allowing any pending diagnostics to show up.
204 0         0 1 while $parser->next;
205 0         0 $self->_bailout($result);
206             }
207             }
208              
209 30         70096 $self->finish_parser( $parser, $session );
210 30         11939 $self->_after_test( $aggregate, $job, $parser );
211 30         10113 $job->finish;
212             }
213              
214 15         3616 return;
215             }
216              
217             sub make_parser {
218 32     32 1 70 my ( $self, $job ) = @_;
219              
220 32         161 my $args = $self->_get_parser_args($job);
221 32         161 $self->_make_callback( 'parser_args', $args, $job->as_array_ref );
222 32         1506 my $parser = $self->_construct( $self->parser_class, $args );
223              
224 32         592962 $self->_make_callback( 'made_parser', $parser, $job->as_array_ref );
225 32         3180 my $session = $self->{formatter}->open_test( $job->description, $parser );
226              
227 32         11966 return ( $parser, $session );
228             }
229              
230             sub _get_parser_args {
231 32     32   57 my ( $self, $job ) = @_;
232 32         98 my $test_prog = $job->filename;
233 32         229 my $spool_prog = $job->description;
234 32         157 my %args = ();
235              
236 32 50       560 $args{sources} = $self->sources if $self->sources;
237              
238 32         220 my @switches;
239 32 50       289 @switches = $self->lib if $self->lib;
240 32 50       1069 push @switches => $self->switches if $self->switches;
241 32         576 $args{switches} = \@switches;
242 32         199 $args{spool} = $self->_open_spool($spool_prog);
243 32         16085 $args{merge} = $self->merge;
244 32         465 $args{ignore_exit} = $self->ignore_exit;
245 32 50       406 $args{version} = $self->version if $self->version;
246              
247 32 50       304 if ( my $exec = $self->exec ) {
248 0 0       0 $args{exec}
249             = ref $exec eq 'CODE'
250             ? $exec->( $self, $test_prog )
251             : [ @$exec, $test_prog ];
252 0 0 0     0 if ( not defined $args{exec} ) {
    0          
253 0         0 $args{source} = $test_prog;
254             }
255             elsif ( ( ref( $args{exec} ) || "" ) ne "ARRAY" ) {
256 0         0 $args{source} = delete $args{exec};
257             }
258             }
259             else {
260 32         262 $args{source} = $test_prog;
261             }
262              
263 32 50       161 if ( defined( my $test_args = $self->test_args ) ) {
264              
265 0 0       0 if ( ref($test_args) eq 'HASH' ) {
266              
267             # different args for each test
268 0 0       0 if ( exists( $test_args->{ $job->description } ) ) {
269 0         0 $test_args = $test_args->{ $job->description };
270             }
271             else {
272 0         0 $self->_croak( "TAP::Harness Can't find test_args for "
273             . $job->description );
274             }
275             }
276              
277 0         0 $args{test_args} = $test_args;
278             }
279 32         436 return \%args;
280             }
281              
282             1;
283              
284             =head1 EXAMPLE
285              
286             See C.
287              
288             =head1 AUTHOR
289              
290             99% of the code in this module comes from TAP::Harness, written by Andy
291             Armstrong and generations of Perl QA hackers. Documentation and the one small
292             code tweak needed were written by James E Keenan.
293              
294             =head1 LICENSE
295              
296             This is free software and is released under the same terms as Perl itself.
297              
298             =cut
299              
300             # vim:ts=4:sw=4:et:sta