File Coverage

support/Test/Harness/Straps.pm
Criterion Covered Total %
statement 168 208 80.7
branch 53 100 53.0
condition 14 39 35.9
subroutine 26 28 92.8
pod 3 4 75.0
total 264 379 69.6


line stmt bran cond sub pod time code
1             # -*- Mode: cperl; cperl-indent-level: 4 -*-
2             package Test::Harness::Straps;
3              
4 1     1   23 use strict;
  1         2  
  1         38  
5 1     1   6 use vars qw($VERSION);
  1         2  
  1         81  
6             $VERSION = '0.26';
7              
8 1     1   5 use Config;
  1         2  
  1         56  
9 1     1   399 use Test::Harness::Assert;
  1         2  
  1         80  
10 1     1   296 use Test::Harness::Iterator;
  1         2  
  1         27  
11 1     1   288 use Test::Harness::Point;
  1         105  
  1         2695  
12              
13             # Flags used as return values from our methods. Just for internal
14             # clarification.
15             my $YES = (1==1);
16             my $NO = !$YES;
17              
18             =head1 NAME
19              
20             Test::Harness::Straps - detailed analysis of test results
21              
22             =head1 SYNOPSIS
23              
24             use Test::Harness::Straps;
25              
26             my $strap = Test::Harness::Straps->new;
27              
28             # Various ways to interpret a test
29             my %results = $strap->analyze($name, \@test_output);
30             my %results = $strap->analyze_fh($name, $test_filehandle);
31             my %results = $strap->analyze_file($test_file);
32              
33             # UNIMPLEMENTED
34             my %total = $strap->total_results;
35              
36             # Altering the behavior of the strap UNIMPLEMENTED
37             my $verbose_output = $strap->dump_verbose();
38             $strap->dump_verbose_fh($output_filehandle);
39              
40              
41             =head1 DESCRIPTION
42              
43             B in that the interface is subject to change
44             in incompatible ways. It is otherwise stable.
45              
46             Test::Harness is limited to printing out its results. This makes
47             analysis of the test results difficult for anything but a human. To
48             make it easier for programs to work with test results, we provide
49             Test::Harness::Straps. Instead of printing the results, straps
50             provide them as raw data. You can also configure how the tests are to
51             be run.
52              
53             The interface is currently incomplete. I contact the author
54             if you'd like a feature added or something change or just have
55             comments.
56              
57             =head1 CONSTRUCTION
58              
59             =head2 new()
60              
61             my $strap = Test::Harness::Straps->new;
62              
63             Initialize a new strap.
64              
65             =cut
66              
67             sub new {
68 1     1 1 3 my $class = shift;
69 1         4 my $self = bless {}, $class;
70              
71 1         4 $self->_init;
72              
73 1         3 return $self;
74             }
75              
76             =for private $strap->_init
77              
78             $strap->_init;
79              
80             Initialize the internal state of a strap to make it ready for parsing.
81              
82             =cut
83              
84             sub _init {
85 1     1   3 my($self) = shift;
86              
87 1         9 $self->{_is_vms} = ( $^O eq 'VMS' );
88 1         4 $self->{_is_win32} = ( $^O =~ /^(MS)?Win32$/ );
89 1         3 $self->{_is_macos} = ( $^O eq 'MacOS' );
90             }
91              
92             =head1 ANALYSIS
93              
94             =head2 $strap->analyze( $name, \@output_lines )
95              
96             my %results = $strap->analyze($name, \@test_output);
97              
98             Analyzes the output of a single test, assigning it the given C<$name>
99             for use in the total report. Returns the C<%results> of the test.
100             See L.
101              
102             C<@test_output> should be the raw output from the test, including
103             newlines.
104              
105             =cut
106              
107             sub analyze {
108 0     0 1 0 my($self, $name, $test_output) = @_;
109              
110 0         0 my $it = Test::Harness::Iterator->new($test_output);
111 0         0 return $self->_analyze_iterator($name, $it);
112             }
113              
114              
115             sub _analyze_iterator {
116 58     58   254 my($self, $name, $it) = @_;
117              
118 58         637 $self->_reset_file_state;
119 58         207 $self->{file} = $name;
120 58         1622 my %totals = (
121             max => 0,
122             seen => 0,
123              
124             ok => 0,
125             todo => 0,
126             skip => 0,
127             bonus => 0,
128              
129             details => []
130             );
131              
132             # Set them up here so callbacks can have them.
133 58         592 $self->{totals}{$name} = \%totals;
134 58         461 while( defined(my $line = $it->next) ) {
135 64829         269529 $self->_analyze_line($line, \%totals);
136 64829 50       194415 last if $self->{saw_bailout};
137             }
138              
139 58 100       830 $totals{skip_all} = $self->{skip_all} if defined $self->{skip_all};
140              
141             my $passed = ($totals{max} == 0 && defined $totals{skip_all}) ||
142             ($totals{max} && $totals{seen} &&
143             $totals{max} == $totals{seen} &&
144 58   66     2298 $totals{max} == $totals{ok});
145 58 50       654 $totals{passing} = $passed ? 1 : 0;
146              
147 58         1964 return %totals;
148             }
149              
150              
151             sub _analyze_line {
152 64829     64829   76650 my $self = shift;
153 64829         68550 my $line = shift;
154 64829         68449 my $totals = shift;
155              
156 64829         94235 $self->{line}++;
157              
158 64829         63739 my $linetype;
159 64829         140535 my $point = Test::Harness::Point->from_test_line( $line );
160 64829 100       120780 if ( $point ) {
    50          
    100          
    50          
    50          
161 48543         60612 $linetype = 'test';
162              
163 48543         64605 $totals->{seen}++;
164 48543 50       81877 $point->set_number( $self->{'next'} ) unless $point->number;
165              
166             # sometimes the 'not ' and the 'ok' are on different lines,
167             # happens often on VMS if you do:
168             # print "not " unless $test;
169             # print "ok $num\n";
170 48543 50 33     116728 if ( $self->{lone_not_line} && ($self->{lone_not_line} == $self->{line} - 1) ) {
171 0         0 $point->set_ok( 0 );
172             }
173              
174 48543 50       92623 if ( $self->{todo}{$point->number} ) {
175 0         0 $point->set_directive_type( 'todo' );
176             }
177              
178 48543 50       102202 if ( $point->is_todo ) {
    100          
179 0         0 $totals->{todo}++;
180 0 0       0 $totals->{bonus}++ if $point->ok;
181             }
182             elsif ( $point->is_skip ) {
183 560         781 $totals->{skip}++;
184             }
185              
186 48543 50       83103 $totals->{ok}++ if $point->pass;
187              
188 48543 50 0     78330 if ( ($point->number > 100_000) && ($point->number > ($self->{max}||100_000)) ) {
      33        
189 0 0       0 if ( !$self->{too_many_tests}++ ) {
190 0         0 warn "Enormous test number seen [test ", $point->number, "]\n";
191 0         0 warn "Can't detailize, too big.\n";
192             }
193             }
194             else {
195 48543         84411 my $details = {
196             ok => $point->pass,
197             actual_ok => $point->ok,
198             name => _def_or_blank( $point->description ),
199             type => _def_or_blank( $point->directive_type ),
200             reason => _def_or_blank( $point->directive_reason ),
201             };
202              
203 48543   33     258592 assert( defined( $details->{ok} ) && defined( $details->{actual_ok} ) );
204 48543         97498 $totals->{details}[$point->number - 1] = $details;
205             }
206             } # test point
207             elsif ( $line =~ /^not\s+$/ ) {
208 0         0 $linetype = 'other';
209             # Sometimes the "not " and "ok" will be on separate lines on VMS.
210             # We catch this and remember we saw it.
211 0         0 $self->{lone_not_line} = $self->{line};
212             }
213             elsif ( $self->_is_header($line) ) {
214 58         142 $linetype = 'header';
215              
216 58         111 $self->{saw_header}++;
217              
218 58         258 $totals->{max} += $self->{max};
219             }
220             elsif ( $self->_is_bail_out($line, \$self->{bailout_reason}) ) {
221 0         0 $linetype = 'bailout';
222 0         0 $self->{saw_bailout} = 1;
223             }
224             elsif (my $diagnostics = $self->_is_diagnostic_line( $line )) {
225 16228         16041 $linetype = 'other';
226 16228         18619 my $test = $totals->{details}[-1];
227 16228   100     36778 $test->{diagnostics} ||= '';
228 16228         26027 $test->{diagnostics} .= $diagnostics;
229             }
230             else {
231 0         0 $linetype = 'other';
232             }
233              
234 64829 50       227304 $self->{callback}->($self, $line, $linetype, $totals) if $self->{callback};
235              
236 64829 100       138388 $self->{'next'} = $point->number + 1 if $point;
237             } # _analyze_line
238              
239              
240             sub _is_diagnostic_line {
241 16228     16228   18381 my ($self, $line) = @_;
242 16228 50       26141 return if index( $line, '# Looks like you failed' ) == 0;
243 16228         47192 $line =~ s/^#\s//;
244 16228         31601 return $line;
245             }
246              
247             =for private $strap->analyze_fh( $name, $test_filehandle )
248              
249             my %results = $strap->analyze_fh($name, $test_filehandle);
250              
251             Like C, but it reads from the given filehandle.
252              
253             =cut
254              
255             sub analyze_fh {
256 58     58 0 898 my($self, $name, $fh) = @_;
257              
258 58         2382 my $it = Test::Harness::Iterator->new($fh);
259 58         722 return $self->_analyze_iterator($name, $it);
260             }
261              
262             =head2 $strap->analyze_file( $test_file )
263              
264             my %results = $strap->analyze_file($test_file);
265              
266             Like C, but it runs the given C<$test_file> and parses its
267             results. It will also use that name for the total report.
268              
269             =cut
270              
271             sub analyze_file {
272 58     58 1 189 my($self, $file) = @_;
273              
274 58 50       1690 unless( -e $file ) {
275 0         0 $self->{error} = "$file does not exist";
276 0         0 return;
277             }
278              
279 58 50       796 unless( -r $file ) {
280 0         0 $self->{error} = "$file is not readable";
281 0         0 return;
282             }
283              
284 58         668 local $ENV{PERL5LIB} = $self->_INC2PERL5LIB;
285 58 50       333 if ( $Test::Harness::Debug ) {
286 0         0 local $^W=0; # ignore undef warnings
287 0         0 print "# PERL5LIB=$ENV{PERL5LIB}\n";
288             }
289              
290             # *sigh* this breaks under taint, but open -| is unportable.
291 58         494 my $line = $self->_command_line($file);
292              
293 58 50       219656 unless ( open(FILE, "$line|" )) {
294 0         0 print "can't run $file. $!\n";
295 0         0 return;
296             }
297              
298 58         2234 my %results = $self->analyze_fh($file, \*FILE);
299 58         12184 my $exit = close FILE;
300 58         881 $results{'wait'} = $?;
301 58 50 33     420 if( $? && $self->{_is_vms} ) {
302 0         0 eval q{use vmsish "status"; $results{'exit'} = $?};
303             }
304             else {
305 58         747 $results{'exit'} = _wait2exit($?);
306             }
307 58 50       353 $results{passing} = 0 unless $? == 0;
308              
309 58         343 $self->_restore_PERL5LIB();
310              
311 58         3117 return %results;
312             }
313              
314              
315             eval { require POSIX; &POSIX::WEXITSTATUS(0) };
316             if( $@ ) {
317             *_wait2exit = sub { $_[0] >> 8 };
318             }
319             else {
320 58     58   1246 *_wait2exit = sub { POSIX::WEXITSTATUS($_[0]) }
321             }
322              
323             =for private $strap->_command_line( $file )
324              
325             Returns the full command line that will be run to test I<$file>.
326              
327             =cut
328              
329             sub _command_line {
330 58     58   142 my $self = shift;
331 58         91 my $file = shift;
332              
333 58         199 my $command = $self->_command();
334 58         227 my $switches = $self->_switches($file);
335              
336 58 50 33     287 $file = qq["$file"] if ($file =~ /\s/) && ($file !~ /^".*"$/);
337 58         173 my $line = "$command $switches $file";
338              
339 58         139 return $line;
340             }
341              
342              
343             =for private $strap->_command()
344              
345             Returns the command that runs the test. Combine this with C<_switches()>
346             to build a command line.
347              
348             Typically this is C<$^X>, but you can set C<$ENV{HARNESS_PERL}>
349             to use a different Perl than what you're running the harness under.
350             This might be to run a threaded Perl, for example.
351              
352             You can also overload this method if you've built your own strap subclass,
353             such as a PHP interpreter for a PHP-based strap.
354              
355             =cut
356              
357             sub _command {
358 116     116   257 my $self = shift;
359              
360 116 50       350 return $ENV{HARNESS_PERL} if defined $ENV{HARNESS_PERL};
361 116 50 33     329 return qq["$^X"] if $self->{_is_win32} && ($^X =~ /[^\w\.\/\\]/);
362 116         248 return $^X;
363             }
364              
365              
366             =for private $strap->_switches( $file )
367              
368             Formats and returns the switches necessary to run the test.
369              
370             =cut
371              
372             sub _switches {
373 58     58   134 my($self, $file) = @_;
374              
375 58         332 my @existing_switches = $self->_cleaned_switches( $Test::Harness::Switches, $ENV{HARNESS_PERL_SWITCHES} );
376 58         147 my @derived_switches;
377              
378 58         224 local *TEST;
379 58 50       2812 open(TEST, $file) or print "can't open $file. $!\n";
380 58         1746 my $shebang = ;
381 58 50       995 close(TEST) or print "can't close $file. $!\n";
382              
383 58         277 my $taint = ( $shebang =~ /^#!.*\bperl.*\s-\w*([Tt]+)/ );
384 58 50       146 push( @derived_switches, "-$1" ) if $taint;
385              
386             # When taint mode is on, PERL5LIB is ignored. So we need to put
387             # all that on the command line as -Is.
388             # MacPerl's putenv is broken, so it will not see PERL5LIB, tainted or not.
389 58 50 33     781 if ( $taint || $self->{_is_macos} ) {
390 0         0 my @inc = $self->_filtered_INC;
391 0         0 push @derived_switches, map { "-I$_" } @inc;
  0         0  
392             }
393              
394             # Quote the argument if there's any whitespace in it, or if
395             # we're VMS, since VMS requires all parms quoted. Also, don't quote
396             # it if it's already quoted.
397 58         203 for ( @derived_switches ) {
398 0 0 0     0 $_ = qq["$_"] if ((/\s/ || $self->{_is_vms}) && !/^".*"$/ );
      0        
399             }
400 58         386 return join( " ", @existing_switches, @derived_switches );
401             }
402              
403             =for private $strap->_cleaned_switches( @switches_from_user )
404              
405             Returns only defined, non-blank, trimmed switches from the parms passed.
406              
407             =cut
408              
409             sub _cleaned_switches {
410 58     58   139 my $self = shift;
411              
412 58         71 local $_;
413              
414 58         127 my @switches;
415 58         145 for ( @_ ) {
416 116         487 my $switch = $_;
417 116 100       367 next unless defined $switch;
418 58         486 $switch =~ s/^\s+//;
419 58         342 $switch =~ s/\s+$//;
420 58 50       314 push( @switches, $switch ) if $switch ne "";
421             }
422              
423 58         210 return @switches;
424             }
425              
426             =for private $strap->_INC2PERL5LIB
427              
428             local $ENV{PERL5LIB} = $self->_INC2PERL5LIB;
429              
430             Takes the current value of C<@INC> and turns it into something suitable
431             for putting onto C.
432              
433             =cut
434              
435             sub _INC2PERL5LIB {
436 58     58   353 my($self) = shift;
437              
438 58         221 $self->{_old5lib} = $ENV{PERL5LIB};
439              
440 58         3087 return join $Config{path_sep}, $self->_filtered_INC;
441             }
442              
443             =for private $strap->_filtered_INC()
444              
445             my @filtered_inc = $self->_filtered_INC;
446              
447             Shortens C<@INC> by removing redundant and unnecessary entries.
448             Necessary for OSes with limited command line lengths, like VMS.
449              
450             =cut
451              
452             sub _filtered_INC {
453 58     58   241 my($self, @inc) = @_;
454 58 50       2421 @inc = @INC unless @inc;
455              
456 58 50       383 if( $self->{_is_vms} ) {
    50          
457             # VMS has a 255-byte limit on the length of %ENV entries, so
458             # toss the ones that involve perl_root, the install location
459 0         0 @inc = grep !/perl_root/i, @inc;
460              
461             }
462             elsif ( $self->{_is_win32} ) {
463             # Lose any trailing backslashes in the Win32 paths
464 0         0 s/[\\\/+]$// foreach @inc;
465             }
466              
467 58         137 my %seen;
468 58         207 $seen{$_}++ foreach $self->_default_inc();
469 58         3252 @inc = grep !$seen{$_}++, @inc;
470              
471 58         1444 return @inc;
472             }
473              
474              
475             { # Without caching, _default_inc() takes a huge amount of time
476             my %cache;
477             sub _default_inc {
478 58     58   114 my $self = shift;
479 58         482 my $perl = $self->_command;
480 58   100     199 $cache{$perl} ||= [do {
481 1         3 local $ENV{PERL5LIB};
482 1         483006 my @inc =`$perl -le "print join qq[\\n], \@INC"`;
483 1         66 chomp @inc;
484             }];
485 58         134 return @{$cache{$perl}};
  58         828  
486             }
487             }
488              
489              
490             =for private $strap->_restore_PERL5LIB()
491              
492             $self->_restore_PERL5LIB;
493              
494             This restores the original value of the C environment variable.
495             Necessary on VMS, otherwise a no-op.
496              
497             =cut
498              
499             sub _restore_PERL5LIB {
500 59     59   171 my($self) = shift;
501              
502 59 50       286 return unless $self->{_is_vms};
503              
504 0 0       0 if (defined $self->{_old5lib}) {
505 0         0 $ENV{PERL5LIB} = $self->{_old5lib};
506             }
507             }
508              
509             =head1 Parsing
510              
511             Methods for identifying what sort of line you're looking at.
512              
513             =for private _is_diagnostic
514              
515             my $is_diagnostic = $strap->_is_diagnostic($line, \$comment);
516              
517             Checks if the given line is a comment. If so, it will place it into
518             C<$comment> (sans #).
519              
520             =cut
521              
522             sub _is_diagnostic {
523 0     0   0 my($self, $line, $comment) = @_;
524              
525 0 0       0 if( $line =~ /^\s*\#(.*)/ ) {
526 0         0 $$comment = $1;
527 0         0 return $YES;
528             }
529             else {
530 0         0 return $NO;
531             }
532             }
533              
534             =for private _is_header
535              
536             my $is_header = $strap->_is_header($line);
537              
538             Checks if the given line is a header (1..M) line. If so, it places how
539             many tests there will be in C<< $strap->{max} >>, a list of which tests
540             are todo in C<< $strap->{todo} >> and if the whole test was skipped
541             C<< $strap->{skip_all} >> contains the reason.
542              
543             =cut
544              
545             # Regex for parsing a header. Will be run with /x
546             my $Extra_Header_Re = <<'REGEX';
547             ^
548             (?: \s+ todo \s+ ([\d \t]+) )? # optional todo set
549             (?: \s* \# \s* ([\w:]+\s?) (.*) )? # optional skip with optional reason
550             REGEX
551              
552             sub _is_header {
553 16286     16286   19768 my($self, $line) = @_;
554              
555 16286 100       25282 if( my($max, $extra) = $line =~ /^1\.\.(\d+)(.*)/ ) {
556 58         394 $self->{max} = $max;
557 58         964 assert( $self->{max} >= 0, 'Max # of tests looks right' );
558              
559 58 50       181 if( defined $extra ) {
560 58         1029 my($todo, $skip, $reason) = $extra =~ /$Extra_Header_Re/xo;
561              
562 58 50       340 $self->{todo} = { map { $_ => 1 } split /\s+/, $todo } if $todo;
  0         0  
563              
564 58 100       5789 if( $self->{max} == 0 ) {
565 1 50 33     55 $reason = '' unless defined $skip and $skip =~ /^Skip/i;
566             }
567              
568 58         202 $self->{skip_all} = $reason;
569             }
570              
571 58         306 return $YES;
572             }
573             else {
574 16228         41588 return $NO;
575             }
576             }
577              
578             =for private _is_bail_out
579              
580             my $is_bail_out = $strap->_is_bail_out($line, \$reason);
581              
582             Checks if the line is a "Bail out!". Places the reason for bailing
583             (if any) in $reason.
584              
585             =cut
586              
587             sub _is_bail_out {
588 16228     16228   20807 my($self, $line, $reason) = @_;
589              
590 16228 50       30348 if( $line =~ /^Bail out!\s*(.*)/i ) {
591 0 0       0 $$reason = $1 if $1;
592 0         0 return $YES;
593             }
594             else {
595 16228         33321 return $NO;
596             }
597             }
598              
599             =for private _reset_file_state
600              
601             $strap->_reset_file_state;
602              
603             Resets things like C<< $strap->{max} >> , C<< $strap->{skip_all} >>,
604             etc. so it's ready to parse the next file.
605              
606             =cut
607              
608             sub _reset_file_state {
609 58     58   162 my($self) = shift;
610              
611 58         111 delete @{$self}{qw(max skip_all todo too_many_tests)};
  58         1698  
612 58         196 $self->{line} = 0;
613 58         110 $self->{saw_header} = 0;
614 58         153 $self->{saw_bailout}= 0;
615 58         98 $self->{lone_not_line} = 0;
616 58         309 $self->{bailout_reason} = '';
617 58         157 $self->{'next'} = 1;
618             }
619              
620             =head1 Results
621              
622             The C<%results> returned from C contain the following
623             information:
624              
625             passing true if the whole test is considered a pass
626             (or skipped), false if its a failure
627              
628             exit the exit code of the test run, if from a file
629             wait the wait code of the test run, if from a file
630              
631             max total tests which should have been run
632             seen total tests actually seen
633             skip_all if the whole test was skipped, this will
634             contain the reason.
635              
636             ok number of tests which passed
637             (including todo and skips)
638              
639             todo number of todo tests seen
640             bonus number of todo tests which
641             unexpectedly passed
642              
643             skip number of tests skipped
644              
645             So a successful test should have max == seen == ok.
646              
647              
648             There is one final item, the details.
649              
650             details an array ref reporting the result of
651             each test looks like this:
652              
653             $results{details}[$test_num - 1] =
654             { ok => is the test considered ok?
655             actual_ok => did it literally say 'ok'?
656             name => name of the test (if any)
657             diagnostics => test diagnostics (if any)
658             type => 'skip' or 'todo' (if any)
659             reason => reason for the above (if any)
660             };
661              
662             Element 0 of the details is test #1. I tried it with element 1 being
663             #1 and 0 being empty, this is less awkward.
664              
665             =head1 EXAMPLES
666              
667             See F for an example of use.
668              
669             =head1 AUTHOR
670              
671             Michael G Schwern C<< >>, currently maintained by
672             Andy Lester C<< >>.
673              
674             =head1 SEE ALSO
675              
676             L
677              
678             =cut
679              
680             sub _def_or_blank {
681 145629 100   145629   241127 return $_[0] if defined $_[0];
682 123719         401277 return "";
683             }
684              
685             1;