1 # -*- Mode: cperl; cperl-indent-level: 4 -*-
2 # $Id: Straps.pm,v 1.17 2003/04/03 17:47:25 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
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';
97 $self->{_is_win32} = $^O eq 'Win32';
110 my %results = $strap->analyze($name, \@test_output);
112 Analyzes the output of a single test, assigning it the given $name for
113 use in the total report. Returns the %results of the test. See
116 @test_output should be the raw output from the test, including newlines.
121 my($self, $name, $test_output) = @_;
123 my $it = Test::Harness::Iterator->new($test_output);
124 return $self->_analyze_iterator($name, $it);
128 sub _analyze_iterator {
129 my($self, $name, $it) = @_;
131 $self->_reset_file_state;
132 $self->{file} = $name;
145 # Set them up here so callbacks can have them.
146 $self->{totals}{$name} = \%totals;
147 while( defined(my $line = $it->next) ) {
148 $self->_analyze_line($line, \%totals);
149 last if $self->{saw_bailout};
152 $totals{skip_all} = $self->{skip_all} if defined $self->{skip_all};
154 my $passed = ($totals{max} == 0 && defined $totals{skip_all}) ||
155 ($totals{max} && $totals{seen} &&
156 $totals{max} == $totals{seen} &&
157 $totals{max} == $totals{ok});
158 $totals{passing} = $passed ? 1 : 0;
165 my($self, $line, $totals) = @_;
172 if( $self->_is_header($line) ) {
175 $self->{saw_header}++;
177 $totals->{max} += $self->{max};
179 elsif( $self->_is_test($line, \%result) ) {
183 $result{number} = $self->{'next'} unless $result{number};
185 # sometimes the 'not ' and the 'ok' are on different lines,
186 # happens often on VMS if you do:
187 # print "not " unless $test;
189 if( $self->{saw_lone_not} &&
190 ($self->{lone_not_line} == $self->{line} - 1) )
195 my $pass = $result{ok};
196 $result{type} = 'todo' if $self->{todo}{$result{number}};
198 if( $result{type} eq 'todo' ) {
201 $totals->{bonus}++ if $result{ok}
203 elsif( $result{type} eq 'skip' ) {
208 $totals->{ok}++ if $pass;
210 if( $result{number} > 100000 && $result{number} > $self->{max} ) {
211 warn "Enormous test number seen [test $result{number}]\n";
212 warn "Can't detailize, too big.\n";
215 $totals->{details}[$result{number} - 1] =
216 {$self->_detailize($pass, \%result)};
219 # XXX handle counter mismatch
221 elsif ( $self->_is_bail_out($line, \$self->{bailout_reason}) ) {
223 $self->{saw_bailout} = 1;
229 $self->{callback}->($self, $line, $type, $totals) if $self->{callback};
231 $self->{'next'} = $result{number} + 1 if $type eq 'test';
236 my %results = $strap->analyze_fh($name, $test_filehandle);
238 Like C<analyze>, but it reads from the given filehandle.
243 my($self, $name, $fh) = @_;
245 my $it = Test::Harness::Iterator->new($fh);
246 $self->_analyze_iterator($name, $it);
249 =item B<analyze_file>
251 my %results = $strap->analyze_file($test_file);
253 Like C<analyze>, but it runs the given $test_file and parses it's
254 results. It will also use that name for the total report.
259 my($self, $file) = @_;
262 $self->{error} = "$file does not exist";
267 $self->{error} = "$file is not readable";
271 local $ENV{PERL5LIB} = $self->_INC2PERL5LIB;
273 my $cmd = $self->{_is_vms} ? "MCR $^X" :
274 $self->{_is_win32} ? Win32::GetShortPathName($^X)
277 my $switches = $self->_switches($file);
279 # *sigh* this breaks under taint, but open -| is unportable.
280 unless( open(FILE, "$cmd $switches $file|") ) {
281 print "can't run $file. $!\n";
285 my %results = $self->analyze_fh($file, \*FILE);
286 my $exit = close FILE;
287 $results{'wait'} = $?;
288 if( $? && $self->{_is_vms} ) {
289 eval q{use vmsish "status"; $results{'exit'} = $?};
292 $results{'exit'} = _wait2exit($?);
294 $results{passing} = 0 unless $? == 0;
296 $self->_restore_PERL5LIB();
302 eval { require POSIX; &POSIX::WEXITSTATUS(0) };
304 *_wait2exit = sub { $_[0] >> 8 };
307 *_wait2exit = sub { POSIX::WEXITSTATUS($_[0]) }
315 my $switches = $self->_switches($file);
317 Formats and returns the switches necessary to run the test.
322 my($self, $file) = @_;
325 open(TEST, $file) or print "can't open $file. $!\n";
327 my $s = $Test::Harness::Switches || '';
328 $s .= " $ENV{'HARNESS_PERL_SWITCHES'}"
329 if exists $ENV{'HARNESS_PERL_SWITCHES'};
331 if ($first =~ /^#!.*\bperl.*\s-\w*([Tt]+)/) {
332 # When taint mode is on, PERL5LIB is ignored. So we need to put
333 # all that on the command line as -Is.
334 $s .= join " ", qq[ "-$1"], map {qq["-I$_"]} $self->_filtered_INC;
336 elsif ($^O eq 'MacOS') {
337 # MacPerl's putenv is broken, so it will not see PERL5LIB.
338 $s .= join " ", map {qq["-I$_"]} $self->_filtered_INC;
341 close(TEST) or print "can't close $file. $!\n";
347 =item B<_INC2PERL5LIB>
349 local $ENV{PERL5LIB} = $self->_INC2PERL5LIB;
351 Takes the current value of @INC and turns it into something suitable
352 for putting onto PERL5LIB.
359 $self->{_old5lib} = $ENV{PERL5LIB};
361 my @filtered_inc = $self->_filtered_INC;
362 my @clean_inc = grep !/\Q$Config{path_sep}/, @filtered_inc;
363 my @naughty_inc = grep /\Q$Config{path_sep}/, @filtered_inc;
364 warn "Test::Harness can't handle \@INC directories with ".
365 "'$Config{path_sep}': @naughty_inc\n" if @naughty_inc;
367 return join $Config{path_sep}, @clean_inc;
370 =item B<_filtered_INC>
372 my @filtered_inc = $self->_filtered_INC;
374 Shortens @INC by removing redundant and unnecessary entries.
375 Necessary for OS's with limited command line lengths, like VMS.
380 my($self, @inc) = @_;
381 @inc = @INC unless @inc;
383 # VMS has a 255-byte limit on the length of %ENV entries, so
384 # toss the ones that involve perl_root, the install location
386 if( $self->{_is_vms} ) {
387 @inc = grep !/perl_root/i, @inc;
394 =item B<_restore_PERL5LIB>
396 $self->_restore_PERL5LIB;
398 This restores the original value of the PERL5LIB environment variable.
399 Necessary on VMS, otherwise a no-op.
403 sub _restore_PERL5LIB {
406 return unless $self->{_is_vms};
408 if (defined $self->{_old5lib}) {
409 $ENV{PERL5LIB} = $self->{_old5lib};
423 Methods for identifying what sort of line you're looking at.
429 my $is_comment = $strap->_is_comment($line, \$comment);
431 Checks if the given line is a comment. If so, it will place it into
437 my($self, $line, $comment) = @_;
439 if( $line =~ /^\s*\#(.*)/ ) {
450 my $is_header = $strap->_is_header($line);
452 Checks if the given line is a header (1..M) line. If so, it places
453 how many tests there will be in $strap->{max}, a list of which tests
454 are todo in $strap->{todo} and if the whole test was skipped
455 $strap->{skip_all} contains the reason.
459 # Regex for parsing a header. Will be run with /x
460 my $Extra_Header_Re = <<'REGEX';
462 (?: \s+ todo \s+ ([\d \t]+) )? # optional todo set
463 (?: \s* \# \s* ([\w:]+\s?) (.*) )? # optional skip with optional reason
467 my($self, $line) = @_;
469 if( my($max, $extra) = $line =~ /^1\.\.(\d+)(.*)/ ) {
471 assert( $self->{max} >= 0, 'Max # of tests looks right' );
473 if( defined $extra ) {
474 my($todo, $skip, $reason) = $extra =~ /$Extra_Header_Re/xo;
476 $self->{todo} = { map { $_ => 1 } split /\s+/, $todo } if $todo;
478 if( $self->{max} == 0 ) {
479 $reason = '' unless defined $skip and $skip =~ /^Skip/i;
482 $self->{skip_all} = $reason;
494 my $is_test = $strap->_is_test($line, \%test);
496 Checks if the $line is a test report (ie. 'ok/not ok'). Reports the
497 result back in %test which will contain:
499 ok did it succeed? This is the literal 'ok' or 'not ok'.
500 name name of the test (if any)
501 number test number (if any)
503 type 'todo' or 'skip' (if any)
504 reason why is it todo or skip? (if any)
506 If will also catch lone 'not' lines, note it saw them
507 $strap->{saw_lone_not} and the line in $strap->{lone_not_line}.
511 my $Report_Re = <<'REGEX';
515 (?:\s+(\d+))? # optional test number
520 my $Extra_Re = <<'REGEX';
522 (.*?) (?:(?:[^\\]|^)# (.*))?
527 my($self, $line, $test) = @_;
529 # We pulverize the line down into pieces in three parts.
530 if( my($not, $num, $extra) = $line =~ /$Report_Re/ox ) {
531 my($name, $control) = split /(?:[^\\]|^)#/, $extra if $extra;
532 my($type, $reason) = $control =~ /^\s*(\S+)(?:\s+(.*))?$/ if $control;
534 $test->{number} = $num;
535 $test->{ok} = $not ? 0 : 1;
536 $test->{name} = $name;
538 if( defined $type ) {
539 $test->{type} = $type =~ /^TODO$/i ? 'todo' :
540 $type =~ /^Skip/i ? 'skip' : 0;
545 $test->{reason} = $reason;
550 # Sometimes the "not " and "ok" will be on seperate lines on VMS.
551 # We catch this and remember we saw it.
552 if( $line =~ /^not\s+$/ ) {
553 $self->{saw_lone_not} = 1;
554 $self->{lone_not_line} = $self->{line};
561 =item B<_is_bail_out>
563 my $is_bail_out = $strap->_is_bail_out($line, \$reason);
565 Checks if the line is a "Bail out!". Places the reason for bailing
571 my($self, $line, $reason) = @_;
573 if( $line =~ /^Bail out!\s*(.*)/i ) {
582 =item B<_reset_file_state>
584 $strap->_reset_file_state;
586 Resets things like $strap->{max}, $strap->{skip_all}, etc... so its
587 ready to parse the next file.
591 sub _reset_file_state {
594 delete @{$self}{qw(max skip_all todo)};
596 $self->{saw_header} = 0;
597 $self->{saw_bailout}= 0;
598 $self->{saw_lone_not} = 0;
599 $self->{lone_not_line} = 0;
600 $self->{bailout_reason} = '';
611 The %results returned from analyze() contain the following information:
613 passing true if the whole test is considered a pass
614 (or skipped), false if its a failure
616 exit the exit code of the test run, if from a file
617 wait the wait code of the test run, if from a file
619 max total tests which should have been run
620 seen total tests actually seen
621 skip_all if the whole test was skipped, this will
624 ok number of tests which passed
625 (including todo and skips)
627 todo number of todo tests seen
628 bonus number of todo tests which
631 skip number of tests skipped
633 So a successful test should have max == seen == ok.
636 There is one final item, the details.
638 details an array ref reporting the result of
639 each test looks like this:
641 $results{details}[$test_num - 1] =
642 { ok => is the test considered ok?
643 actual_ok => did it literally say 'ok'?
644 name => name of the test (if any)
645 type => 'skip' or 'todo' (if any)
646 reason => reason for the above (if any)
649 Element 0 of the details is test #1. I tried it with element 1 being
650 #1 and 0 being empty, this is less awkward.
658 my %details = $strap->_detailize($pass, \%test);
660 Generates the details based on the last test line seen. $pass is true
661 if it was considered to be a passed test. %test is the results of the
662 test you're summarizing.
667 my($self, $pass, $test) = @_;
669 my %details = ( ok => $pass,
670 actual_ok => $test->{ok}
673 assert( !(grep !defined $details{$_}, keys %details),
674 'test contains the ok and actual_ok info' );
676 # We don't want these to be undef because they are often
677 # checked and don't want the checker to have to deal with
678 # uninitialized vars.
679 foreach my $piece (qw(name type reason)) {
680 $details{$piece} = defined $test->{$piece} ? $test->{$piece} : '';
692 See F<examples/mini_harness.plx> for an example of use.
696 Michael G Schwern E<lt>schwern@pobox.comE<gt>