1 # -*- Mode: cperl; cperl-indent-level: 4 -*-
2 # $Id: Straps.pm,v 1.35 2003/12/31 02:34:22 andy Exp $
4 package Test::Harness::Straps;
11 use Test::Harness::Assert;
12 use Test::Harness::Iterator;
14 # Flags used as return values from our methods. Just for internal
24 Test::Harness::Straps - detailed analysis of test results
28 use Test::Harness::Straps;
30 my $strap = Test::Harness::Straps->new;
32 # Various ways to interpret a test
33 my %results = $strap->analyze($name, \@test_output);
34 my %results = $strap->analyze_fh($name, $test_filehandle);
35 my %results = $strap->analyze_file($test_file);
38 my %total = $strap->total_results;
40 # Altering the behavior of the strap UNIMPLEMENTED
41 my $verbose_output = $strap->dump_verbose();
42 $strap->dump_verbose_fh($output_filehandle);
47 B<THIS IS ALPHA SOFTWARE> in that the interface is subject to change
48 in incompatible ways. It is otherwise stable.
50 Test::Harness is limited to printing out its results. This makes
51 analysis of the test results difficult for anything but a human. To
52 make it easier for programs to work with test results, we provide
53 Test::Harness::Straps. Instead of printing the results, straps
54 provide them as raw data. You can also configure how the tests are to
57 The interface is currently incomplete. I<Please> contact the author
58 if you'd like a feature added or something change or just have
65 my $strap = Test::Harness::Straps->new;
67 Initialize a new strap.
73 my($class) = ref $proto || $proto;
75 my $self = bless {}, $class;
85 Initialize the internal state of a strap to make it ready for parsing.
92 $self->{_is_vms} = ( $^O eq 'VMS' );
93 $self->{_is_win32} = ( $^O =~ /^(MS)?Win32$/ );
94 $self->{_is_macos} = ( $^O eq 'MacOS' );
101 my %results = $strap->analyze($name, \@test_output);
103 Analyzes the output of a single test, assigning it the given C<$name>
104 for use in the total report. Returns the C<%results> of the test.
107 C<@test_output> should be the raw output from the test, including
113 my($self, $name, $test_output) = @_;
115 my $it = Test::Harness::Iterator->new($test_output);
116 return $self->_analyze_iterator($name, $it);
120 sub _analyze_iterator {
121 my($self, $name, $it) = @_;
123 $self->_reset_file_state;
124 $self->{file} = $name;
137 # Set them up here so callbacks can have them.
138 $self->{totals}{$name} = \%totals;
139 while( defined(my $line = $it->next) ) {
140 $self->_analyze_line($line, \%totals);
141 last if $self->{saw_bailout};
144 $totals{skip_all} = $self->{skip_all} if defined $self->{skip_all};
146 my $passed = ($totals{max} == 0 && defined $totals{skip_all}) ||
147 ($totals{max} && $totals{seen} &&
148 $totals{max} == $totals{seen} &&
149 $totals{max} == $totals{ok});
150 $totals{passing} = $passed ? 1 : 0;
157 my($self, $line, $totals) = @_;
164 if( $self->_is_header($line) ) {
167 $self->{saw_header}++;
169 $totals->{max} += $self->{max};
171 elsif( $self->_is_test($line, \%result) ) {
175 $result{number} = $self->{'next'} unless $result{number};
177 # sometimes the 'not ' and the 'ok' are on different lines,
178 # happens often on VMS if you do:
179 # print "not " unless $test;
181 if( $self->{saw_lone_not} &&
182 ($self->{lone_not_line} == $self->{line} - 1) )
187 my $pass = $result{ok};
188 $result{type} = 'todo' if $self->{todo}{$result{number}};
190 if( $result{type} eq 'todo' ) {
193 $totals->{bonus}++ if $result{ok}
195 elsif( $result{type} eq 'skip' ) {
200 $totals->{ok}++ if $pass;
202 if( $result{number} > 100000 && $result{number} > $self->{max} ) {
203 warn "Enormous test number seen [test $result{number}]\n";
204 warn "Can't detailize, too big.\n";
207 $totals->{details}[$result{number} - 1] =
208 {$self->_detailize($pass, \%result)};
211 # XXX handle counter mismatch
213 elsif ( $self->_is_bail_out($line, \$self->{bailout_reason}) ) {
215 $self->{saw_bailout} = 1;
221 $self->{callback}->($self, $line, $type, $totals) if $self->{callback};
223 $self->{'next'} = $result{number} + 1 if $type eq 'test';
228 my %results = $strap->analyze_fh($name, $test_filehandle);
230 Like C<analyze>, but it reads from the given filehandle.
235 my($self, $name, $fh) = @_;
237 my $it = Test::Harness::Iterator->new($fh);
238 $self->_analyze_iterator($name, $it);
241 =head2 C<analyze_file>
243 my %results = $strap->analyze_file($test_file);
245 Like C<analyze>, but it runs the given C<$test_file> and parses its
246 results. It will also use that name for the total report.
251 my($self, $file) = @_;
254 $self->{error} = "$file does not exist";
259 $self->{error} = "$file is not readable";
263 local $ENV{PERL5LIB} = $self->_INC2PERL5LIB;
265 # *sigh* this breaks under taint, but open -| is unportable.
266 my $line = $self->_command_line($file);
267 unless( open(FILE, "$line|") ) {
268 print "can't run $file. $!\n";
272 my %results = $self->analyze_fh($file, \*FILE);
273 my $exit = close FILE;
274 $results{'wait'} = $?;
275 if( $? && $self->{_is_vms} ) {
276 eval q{use vmsish "status"; $results{'exit'} = $?};
279 $results{'exit'} = _wait2exit($?);
281 $results{passing} = 0 unless $? == 0;
283 $self->_restore_PERL5LIB();
289 eval { require POSIX; &POSIX::WEXITSTATUS(0) };
291 *_wait2exit = sub { $_[0] >> 8 };
294 *_wait2exit = sub { POSIX::WEXITSTATUS($_[0]) }
297 =head2 C<_command_line( $file )>
299 my $command_line = $self->_command_line();
301 Returns the full command line that will be run to test I<$file>.
309 my $command = $self->_command();
310 my $switches = $self->_switches($file);
312 $file = qq["$file"] if ($file =~ /\s/) && ($file !~ /^".*"$/);
313 my $line = "$command $switches $file";
321 my $command = $self->_command();
323 Returns the command that runs the test. Combine this with _switches()
324 to build a command line.
326 Typically this is C<$^X>, but you can set C<$ENV{HARNESS_COMMAND}>
327 to use a different Perl than what you're running the harness under.
328 This might be to run a threaded Perl, for example.
330 You can also overload this method if you've built your own strap subclass,
331 such as a PHP interpreter for a PHP-based strap.
338 return $ENV{HARNESS_PERL} if defined $ENV{HARNESS_PERL};
339 return "MCR $^X" if $self->{_is_vms};
340 return Win32::GetShortPathName($^X) if $self->{_is_win32};
347 my $switches = $self->_switches($file);
349 Formats and returns the switches necessary to run the test.
354 my($self, $file) = @_;
356 my @existing_switches = $self->_cleaned_switches( $Test::Harness::Switches, $ENV{HARNESS_PERL_SWITCHES} );
357 my @derived_switches;
360 open(TEST, $file) or print "can't open $file. $!\n";
361 my $shebang = <TEST>;
362 close(TEST) or print "can't close $file. $!\n";
364 my $taint = ( $shebang =~ /^#!.*\bperl.*\s-\w*([Tt]+)/ );
365 push( @derived_switches, "-$1" ) if $taint;
367 # When taint mode is on, PERL5LIB is ignored. So we need to put
368 # all that on the command line as -Is.
369 # MacPerl's putenv is broken, so it will not see PERL5LIB, tainted or not.
370 if ( $taint || $self->{_is_macos} ) {
371 my @inc = $self->_filtered_INC;
372 push @derived_switches, map { "-I$_" } @inc;
375 # Quote the argument if there's any whitespace in it, or if
376 # we're VMS, since VMS requires all parms quoted. Also, don't quote
377 # it if it's already quoted.
378 for ( @derived_switches ) {
379 $_ = qq["$_"] if ((/\s/ || $self->{_is_vms}) && !/^".*"$/ );
381 return join( " ", @existing_switches, @derived_switches );
384 =head2 C<_cleaned_switches>
386 my @switches = $self->_cleaned_switches( @switches_from_user );
388 Returns only defined, non-blank, trimmed switches from the parms passed.
392 sub _cleaned_switches {
400 next unless defined $switch;
403 push( @switches, $switch ) if $switch ne "";
409 =head2 C<_INC2PERL5LIB>
411 local $ENV{PERL5LIB} = $self->_INC2PERL5LIB;
413 Takes the current value of C<@INC> and turns it into something suitable
414 for putting onto C<PERL5LIB>.
421 $self->{_old5lib} = $ENV{PERL5LIB};
423 return join $Config{path_sep}, $self->_filtered_INC;
426 =head2 C<_filtered_INC>
428 my @filtered_inc = $self->_filtered_INC;
430 Shortens C<@INC> by removing redundant and unnecessary entries.
431 Necessary for OSes with limited command line lengths, like VMS.
436 my($self, @inc) = @_;
437 @inc = @INC unless @inc;
439 if( $self->{_is_vms} ) {
440 # VMS has a 255-byte limit on the length of %ENV entries, so
441 # toss the ones that involve perl_root, the install location
442 @inc = grep !/perl_root/i, @inc;
444 } elsif ( $self->{_is_win32} ) {
445 # Lose any trailing backslashes in the Win32 paths
446 s/[\\\/+]$// foreach @inc;
450 @inc = grep !$dupes{$_}++, @inc;
456 =head2 C<_restore_PERL5LIB>
458 $self->_restore_PERL5LIB;
460 This restores the original value of the C<PERL5LIB> environment variable.
461 Necessary on VMS, otherwise a no-op.
465 sub _restore_PERL5LIB {
468 return unless $self->{_is_vms};
470 if (defined $self->{_old5lib}) {
471 $ENV{PERL5LIB} = $self->{_old5lib};
477 Methods for identifying what sort of line you're looking at.
479 =head2 C<_is_comment>
481 my $is_comment = $strap->_is_comment($line, \$comment);
483 Checks if the given line is a comment. If so, it will place it into
484 C<$comment> (sans #).
489 my($self, $line, $comment) = @_;
491 if( $line =~ /^\s*\#(.*)/ ) {
502 my $is_header = $strap->_is_header($line);
504 Checks if the given line is a header (1..M) line. If so, it places how
505 many tests there will be in C<< $strap->{max} >>, a list of which tests
506 are todo in C<< $strap->{todo} >> and if the whole test was skipped
507 C<< $strap->{skip_all} >> contains the reason.
511 # Regex for parsing a header. Will be run with /x
512 my $Extra_Header_Re = <<'REGEX';
514 (?: \s+ todo \s+ ([\d \t]+) )? # optional todo set
515 (?: \s* \# \s* ([\w:]+\s?) (.*) )? # optional skip with optional reason
519 my($self, $line) = @_;
521 if( my($max, $extra) = $line =~ /^1\.\.(\d+)(.*)/ ) {
523 assert( $self->{max} >= 0, 'Max # of tests looks right' );
525 if( defined $extra ) {
526 my($todo, $skip, $reason) = $extra =~ /$Extra_Header_Re/xo;
528 $self->{todo} = { map { $_ => 1 } split /\s+/, $todo } if $todo;
530 if( $self->{max} == 0 ) {
531 $reason = '' unless defined $skip and $skip =~ /^Skip/i;
534 $self->{skip_all} = $reason;
546 my $is_test = $strap->_is_test($line, \%test);
548 Checks if the $line is a test report (ie. 'ok/not ok'). Reports the
549 result back in C<%test> which will contain:
551 ok did it succeed? This is the literal 'ok' or 'not ok'.
552 name name of the test (if any)
553 number test number (if any)
555 type 'todo' or 'skip' (if any)
556 reason why is it todo or skip? (if any)
558 If will also catch lone 'not' lines, note it saw them
559 C<< $strap->{saw_lone_not} >> and the line in C<< $strap->{lone_not_line} >>.
563 my $Report_Re = <<'REGEX';
567 (?:\s+(\d+))? # optional test number
572 my $Extra_Re = <<'REGEX';
574 (.*?) (?:(?:[^\\]|^)# (.*))?
579 my($self, $line, $test) = @_;
581 # We pulverize the line down into pieces in three parts.
582 if( my($not, $num, $extra) = $line =~ /$Report_Re/ox ) {
583 my ($name, $control) = $extra ? split(/(?:[^\\]|^)#/, $extra) : ();
584 my ($type, $reason) = $control ? $control =~ /^\s*(\S+)(?:\s+(.*))?$/ : ();
586 $test->{number} = $num;
587 $test->{ok} = $not ? 0 : 1;
588 $test->{name} = $name;
590 if( defined $type ) {
591 $test->{type} = $type =~ /^TODO$/i ? 'todo' :
592 $type =~ /^Skip/i ? 'skip' : 0;
597 $test->{reason} = $reason;
602 # Sometimes the "not " and "ok" will be on separate lines on VMS.
603 # We catch this and remember we saw it.
604 if( $line =~ /^not\s+$/ ) {
605 $self->{saw_lone_not} = 1;
606 $self->{lone_not_line} = $self->{line};
613 =head2 C<_is_bail_out>
615 my $is_bail_out = $strap->_is_bail_out($line, \$reason);
617 Checks if the line is a "Bail out!". Places the reason for bailing
623 my($self, $line, $reason) = @_;
625 if( $line =~ /^Bail out!\s*(.*)/i ) {
634 =head2 C<_reset_file_state>
636 $strap->_reset_file_state;
638 Resets things like C<< $strap->{max} >> , C<< $strap->{skip_all} >>,
639 etc. so it's ready to parse the next file.
643 sub _reset_file_state {
646 delete @{$self}{qw(max skip_all todo)};
648 $self->{saw_header} = 0;
649 $self->{saw_bailout}= 0;
650 $self->{saw_lone_not} = 0;
651 $self->{lone_not_line} = 0;
652 $self->{bailout_reason} = '';
658 The C<%results> returned from C<analyze()> contain the following
661 passing true if the whole test is considered a pass
662 (or skipped), false if its a failure
664 exit the exit code of the test run, if from a file
665 wait the wait code of the test run, if from a file
667 max total tests which should have been run
668 seen total tests actually seen
669 skip_all if the whole test was skipped, this will
672 ok number of tests which passed
673 (including todo and skips)
675 todo number of todo tests seen
676 bonus number of todo tests which
679 skip number of tests skipped
681 So a successful test should have max == seen == ok.
684 There is one final item, the details.
686 details an array ref reporting the result of
687 each test looks like this:
689 $results{details}[$test_num - 1] =
690 { ok => is the test considered ok?
691 actual_ok => did it literally say 'ok'?
692 name => name of the test (if any)
693 type => 'skip' or 'todo' (if any)
694 reason => reason for the above (if any)
697 Element 0 of the details is test #1. I tried it with element 1 being
698 #1 and 0 being empty, this is less awkward.
702 my %details = $strap->_detailize($pass, \%test);
704 Generates the details based on the last test line seen. C<$pass> is
705 true if it was considered to be a passed test. C<%test> is the results
706 of the test you're summarizing.
711 my($self, $pass, $test) = @_;
713 my %details = ( ok => $pass,
714 actual_ok => $test->{ok}
717 assert( !(grep !defined $details{$_}, keys %details),
718 'test contains the ok and actual_ok info' );
720 # We don't want these to be undef because they are often
721 # checked and don't want the checker to have to deal with
722 # uninitialized vars.
723 foreach my $piece (qw(name type reason)) {
724 $details{$piece} = defined $test->{$piece} ? $test->{$piece} : '';
732 See F<examples/mini_harness.plx> for an example of use.
736 Michael G Schwern C<< <schwern@pobox.com> >>, currently maintained by
737 Andy Lester C<< <andy@petdance.com> >>.