1 # -*- Mode: cperl; cperl-indent-level: 4 -*-
2 # $Id: Straps.pm,v 1.13 2002/06/19 21:01:04 schwern 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
67 my $strap = Test::Harness::Straps->new;
69 Initialize a new strap.
75 my($class) = ref $proto || $proto;
77 my $self = bless {}, $class;
89 Initialize the internal state of a strap to make it ready for parsing.
96 $self->{_is_vms} = $^O eq 'VMS';
109 my %results = $strap->analyze($name, \@test_output);
111 Analyzes the output of a single test, assigning it the given $name for
112 use in the total report. Returns the %results of the test. See
115 @test_output should be the raw output from the test, including newlines.
120 my($self, $name, $test_output) = @_;
122 my $it = Test::Harness::Iterator->new($test_output);
123 return $self->_analyze_iterator($name, $it);
127 sub _analyze_iterator {
128 my($self, $name, $it) = @_;
130 $self->_reset_file_state;
131 $self->{file} = $name;
144 # Set them up here so callbacks can have them.
145 $self->{totals}{$name} = \%totals;
146 while( defined(my $line = $it->next) ) {
147 $self->_analyze_line($line, \%totals);
148 last if $self->{saw_bailout};
151 $totals{skip_all} = $self->{skip_all} if defined $self->{skip_all};
153 my $passed = !$totals{max} ||
154 ($totals{max} && $totals{seen} &&
155 $totals{max} == $totals{seen} &&
156 $totals{max} == $totals{ok});
157 $totals{passing} = $passed ? 1 : 0;
164 my($self, $line, $totals) = @_;
171 if( $self->_is_header($line) ) {
174 $self->{saw_header}++;
176 $totals->{max} += $self->{max};
178 elsif( $self->_is_test($line, \%result) ) {
182 $result{number} = $self->{'next'} unless $result{number};
184 # sometimes the 'not ' and the 'ok' are on different lines,
185 # happens often on VMS if you do:
186 # print "not " unless $test;
188 if( $self->{saw_lone_not} &&
189 ($self->{lone_not_line} == $self->{line} - 1) )
194 my $pass = $result{ok};
195 $result{type} = 'todo' if $self->{todo}{$result{number}};
197 if( $result{type} eq 'todo' ) {
200 $totals->{bonus}++ if $result{ok}
202 elsif( $result{type} eq 'skip' ) {
207 $totals->{ok}++ if $pass;
209 if( $result{number} > 100000 ) {
210 warn "Enormous test number seen [test $result{number}]\n";
211 warn "Can't detailize, too big.\n";
214 $totals->{details}[$result{number} - 1] =
215 {$self->_detailize($pass, \%result)};
218 # XXX handle counter mismatch
220 elsif ( $self->_is_bail_out($line, \$self->{bailout_reason}) ) {
222 $self->{saw_bailout} = 1;
228 $self->{callback}->($self, $line, $type, $totals) if $self->{callback};
230 $self->{'next'} = $result{number} + 1 if $type eq 'test';
235 my %results = $strap->analyze_fh($name, $test_filehandle);
237 Like C<analyze>, but it reads from the given filehandle.
242 my($self, $name, $fh) = @_;
244 my $it = Test::Harness::Iterator->new($fh);
245 $self->_analyze_iterator($name, $it);
248 =item B<analyze_file>
250 my %results = $strap->analyze_file($test_file);
252 Like C<analyze>, but it runs the given $test_file and parses it's
253 results. It will also use that name for the total report.
258 my($self, $file) = @_;
261 $self->{error} = "$file does not exist";
266 $self->{error} = "$file is not readable";
270 local $ENV{PERL5LIB} = $self->_INC2PERL5LIB;
272 # Is this necessary anymore?
273 my $cmd = $self->{_is_vms} ? "MCR $^X" : $^X;
275 my $switches = $self->_switches($file);
277 # *sigh* this breaks under taint, but open -| is unportable.
278 unless( open(FILE, "$cmd $switches $file|") ) {
279 print "can't run $file. $!\n";
283 my %results = $self->analyze_fh($file, \*FILE);
284 my $exit = close FILE;
285 $results{'wait'} = $?;
286 if( $? && $self->{_is_vms} ) {
287 eval q{use vmsish "status"; $results{'exit'} = $?};
290 $results{'exit'} = _wait2exit($?);
292 $results{passing} = 0 unless $? == 0;
294 $self->_restore_PERL5LIB();
300 eval { require POSIX; &POSIX::WEXITSTATUS(0) };
302 *_wait2exit = sub { $_[0] >> 8 };
305 *_wait2exit = sub { POSIX::WEXITSTATUS($_[0]) }
313 my $switches = $self->_switches($file);
315 Formats and returns the switches necessary to run the test.
320 my($self, $file) = @_;
323 open(TEST, $file) or print "can't open $file. $!\n";
326 $s .= " $ENV{'HARNESS_PERL_SWITCHES'}"
327 if exists $ENV{'HARNESS_PERL_SWITCHES'};
329 if ($first =~ /^#!.*\bperl.*\s-\w*([Tt]+)/) {
330 # When taint mode is on, PERL5LIB is ignored. So we need to put
331 # all that on the command line as -Is.
332 $s .= join " ", qq[ "-$1"], map {qq["-I$_"]} $self->_filtered_INC;
334 elsif ($^O eq 'MacOS') {
335 # MacPerl's putenv is broken, so it will not see PERL5LIB.
336 $s .= join " ", map {qq["-I$_"]} $self->_filtered_INC;
339 close(TEST) or print "can't close $file. $!\n";
345 =item B<_INC2PERL5LIB>
347 local $ENV{PERL5LIB} = $self->_INC2PERL5LIB;
349 Takes the current value of @INC and turns it into something suitable
350 for putting onto PERL5LIB.
357 $self->{_old5lib} = $ENV{PERL5LIB};
359 return join $Config{path_sep}, $self->_filtered_INC;
362 =item B<_filtered_INC>
364 my @filtered_inc = $self->_filtered_INC;
366 Shortens @INC by removing redundant and unnecessary entries.
367 Necessary for OS's with limited command line lengths, like VMS.
372 my($self, @inc) = @_;
373 @inc = @INC unless @inc;
375 # VMS has a 255-byte limit on the length of %ENV entries, so
376 # toss the ones that involve perl_root, the install location
378 if( $self->{_is_vms} ) {
379 @inc = grep !/perl_root/i, @inc;
386 =item B<_restore_PERL5LIB>
388 $self->_restore_PERL5LIB;
390 This restores the original value of the PERL5LIB environment variable.
391 Necessary on VMS, otherwise a no-op.
395 sub _restore_PERL5LIB {
398 return unless $self->{_is_vms};
400 if (defined $self->{_old5lib}) {
401 $ENV{PERL5LIB} = $self->{_old5lib};
415 Methods for identifying what sort of line you're looking at.
421 my $is_comment = $strap->_is_comment($line, \$comment);
423 Checks if the given line is a comment. If so, it will place it into
429 my($self, $line, $comment) = @_;
431 if( $line =~ /^\s*\#(.*)/ ) {
442 my $is_header = $strap->_is_header($line);
444 Checks if the given line is a header (1..M) line. If so, it places
445 how many tests there will be in $strap->{max}, a list of which tests
446 are todo in $strap->{todo} and if the whole test was skipped
447 $strap->{skip_all} contains the reason.
451 # Regex for parsing a header. Will be run with /x
452 my $Extra_Header_Re = <<'REGEX';
454 (?: \s+ todo \s+ ([\d \t]+) )? # optional todo set
455 (?: \s* \# \s* ([\w:]+\s?) (.*) )? # optional skip with optional reason
459 my($self, $line) = @_;
461 if( my($max, $extra) = $line =~ /^1\.\.(\d+)(.*)/ ) {
463 assert( $self->{max} >= 0, 'Max # of tests looks right' );
465 if( defined $extra ) {
466 my($todo, $skip, $reason) = $extra =~ /$Extra_Header_Re/xo;
468 $self->{todo} = { map { $_ => 1 } split /\s+/, $todo } if $todo;
470 $self->{skip_all} = $reason if defined $skip and $skip =~ /^Skip/i;
482 my $is_test = $strap->_is_test($line, \%test);
484 Checks if the $line is a test report (ie. 'ok/not ok'). Reports the
485 result back in %test which will contain:
487 ok did it succeed? This is the literal 'ok' or 'not ok'.
488 name name of the test (if any)
489 number test number (if any)
491 type 'todo' or 'skip' (if any)
492 reason why is it todo or skip? (if any)
494 If will also catch lone 'not' lines, note it saw them
495 $strap->{saw_lone_not} and the line in $strap->{lone_not_line}.
499 my $Report_Re = <<'REGEX';
503 (?:\s+(\d+))? # optional test number
508 my $Extra_Re = <<'REGEX';
510 (.*?) (?:(?:[^\\]|^)# (.*))?
515 my($self, $line, $test) = @_;
517 # We pulverize the line down into pieces in three parts.
518 if( my($not, $num, $extra) = $line =~ /$Report_Re/ox ) {
519 my($name, $control) = split /(?:[^\\]|^)#/, $extra if $extra;
520 my($type, $reason) = $control =~ /^\s*(\S+)(?:\s+(.*))?$/ if $control;
522 $test->{number} = $num;
523 $test->{ok} = $not ? 0 : 1;
524 $test->{name} = $name;
526 if( defined $type ) {
527 $test->{type} = $type =~ /^TODO$/i ? 'todo' :
528 $type =~ /^Skip/i ? 'skip' : 0;
533 $test->{reason} = $reason;
538 # Sometimes the "not " and "ok" will be on seperate lines on VMS.
539 # We catch this and remember we saw it.
540 if( $line =~ /^not\s+$/ ) {
541 $self->{saw_lone_not} = 1;
542 $self->{lone_not_line} = $self->{line};
549 =item B<_is_bail_out>
551 my $is_bail_out = $strap->_is_bail_out($line, \$reason);
553 Checks if the line is a "Bail out!". Places the reason for bailing
559 my($self, $line, $reason) = @_;
561 if( $line =~ /^Bail out!\s*(.*)/i ) {
570 =item B<_reset_file_state>
572 $strap->_reset_file_state;
574 Resets things like $strap->{max}, $strap->{skip_all}, etc... so its
575 ready to parse the next file.
579 sub _reset_file_state {
582 delete @{$self}{qw(max skip_all todo)};
584 $self->{saw_header} = 0;
585 $self->{saw_bailout}= 0;
586 $self->{saw_lone_not} = 0;
587 $self->{lone_not_line} = 0;
588 $self->{bailout_reason} = '';
599 The %results returned from analyze() contain the following information:
601 passing true if the whole test is considered a pass
602 (or skipped), false if its a failure
604 exit the exit code of the test run, if from a file
605 wait the wait code of the test run, if from a file
607 max total tests which should have been run
608 seen total tests actually seen
609 skip_all if the whole test was skipped, this will
612 ok number of tests which passed
613 (including todo and skips)
615 todo number of todo tests seen
616 bonus number of todo tests which
619 skip number of tests skipped
621 So a successful test should have max == seen == ok.
624 There is one final item, the details.
626 details an array ref reporting the result of
627 each test looks like this:
629 $results{details}[$test_num - 1] =
630 { ok => is the test considered ok?
631 actual_ok => did it literally say 'ok'?
632 name => name of the test (if any)
633 type => 'skip' or 'todo' (if any)
634 reason => reason for the above (if any)
637 Element 0 of the details is test #1. I tried it with element 1 being
638 #1 and 0 being empty, this is less awkward.
646 my %details = $strap->_detailize($pass, \%test);
648 Generates the details based on the last test line seen. $pass is true
649 if it was considered to be a passed test. %test is the results of the
650 test you're summarizing.
655 my($self, $pass, $test) = @_;
657 my %details = ( ok => $pass,
658 actual_ok => $test->{ok}
661 assert( !(grep !defined $details{$_}, keys %details),
662 'test contains the ok and actual_ok info' );
664 # We don't want these to be undef because they are often
665 # checked and don't want the checker to have to deal with
666 # uninitialized vars.
667 foreach my $piece (qw(name type reason)) {
668 $details{$piece} = defined $test->{$piece} ? $test->{$piece} : '';
680 See F<examples/mini_harness.plx> for an example of use.
684 Michael G Schwern E<lt>schwern@pobox.comE<gt>