1 # -*- Mode: cperl; cperl-indent-level: 4 -*-
2 # $Id: Straps.pm,v 1.1.2.17 2002/01/07 22:34:33 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;
145 while( defined(my $line = $it->next) ) {
146 $self->_analyze_line($line, \%totals);
147 last if $self->{saw_bailout};
150 my $passed = $totals{skip_all} ||
151 ($totals{max} == $totals{seen} &&
152 $totals{max} == $totals{ok});
153 $totals{passing} = $passed ? 1 : 0;
155 $totals{skip_all} = $self->{skip_all} if defined $self->{skip_all};
157 $self->{totals}{$name} = \%totals;
163 my($self, $line, $totals) = @_;
170 if( $self->_is_header($line) ) {
173 $self->{saw_header}++;
175 $totals->{max} += $self->{max};
177 elsif( $self->_is_test($line, \%result) ) {
181 $result{number} = $self->{'next'} unless $result{number};
183 # sometimes the 'not ' and the 'ok' are on different lines,
184 # happens often on VMS if you do:
185 # print "not " unless $test;
187 if( $self->{saw_lone_not} &&
188 ($self->{lone_not_line} == $self->{line} - 1) )
193 my $pass = $result{ok};
194 $result{type} = 'todo' if $self->{todo}{$result{number}};
196 if( $result{type} eq 'todo' ) {
199 $totals->{bonus}++ if $result{ok}
201 elsif( $result{type} eq 'skip' ) {
206 $totals->{ok}++ if $pass;
208 $totals->{details}[$result{number} - 1] =
209 {$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 =item B<analyze_file>
243 my %results = $strap->analyze_file($test_file);
245 Like C<analyze>, but it reads from the given $test_file. It will also
246 use that name for the total report.
251 my($self, $file) = @_;
253 local $ENV{PERL5LIB} = $self->_INC2PERL5LIB;
255 # Is this necessary anymore?
256 my $cmd = $self->{_is_vms} ? "MCR $^X" : $^X;
258 my $switches = $self->_switches($file);
260 # *sigh* this breaks under taint, but open -| is unportable.
261 unless( open(FILE, "$cmd $switches $file|") ) {
262 print "can't run $file. $!\n";
266 my %results = $self->analyze_fh($file, \*FILE);
269 $self->_restore_PERL5LIB();
278 my $switches = $self->_switches($file);
280 Formats and returns the switches necessary to run the test.
285 my($self, $file) = @_;
288 open(TEST, $file) or print "can't open $file. $!\n";
291 $s .= " $ENV{'HARNESS_PERL_SWITCHES'}"
292 if exists $ENV{'HARNESS_PERL_SWITCHES'};
293 $s .= join " ", qq[ "-$1"], map {qq["-I$_"]} $self->_filtered_INC
294 if $first =~ /^#!.*\bperl.*-\w*([Tt]+)/;
296 close(TEST) or print "can't close $file. $!\n";
302 =item B<_INC2PERL5LIB>
304 local $ENV{PERL5LIB} = $self->_INC2PERL5LIB;
306 Takes the current value of @INC and turns it into something suitable
307 for putting onto PERL5LIB.
314 $self->{_old5lib} = $ENV{PERL5LIB};
316 return join $Config{path_sep}, $self->_filtered_INC;
319 =item B<_filtered_INC>
321 my @filtered_inc = $self->_filtered_INC;
323 Shortens @INC by removing redundant and unnecessary entries.
324 Necessary for OS's with limited command line lengths, like VMS.
329 my($self, @inc) = @_;
330 @inc = @INC unless @inc;
332 # VMS has a 255-byte limit on the length of %ENV entries, so
333 # toss the ones that involve perl_root, the install location
335 if( $self->{_is_vms} ) {
336 @inc = grep !/perl_root/i, @inc;
343 =item B<_restore_PERL5LIB>
345 $self->_restore_PERL5LIB;
347 This restores the original value of the PERL5LIB environment variable.
348 Necessary on VMS, otherwise a no-op.
352 sub _restore_PERL5LIB {
355 return unless $self->{_is_vms};
357 if (defined $self->{_old5lib}) {
358 $ENV{PERL5LIB} = $self->{_old5lib};
372 Methods for identifying what sort of line you're looking at.
378 my $is_comment = $strap->_is_comment($line, \$comment);
380 Checks if the given line is a comment. If so, it will place it into
386 my($self, $line, $comment) = @_;
388 if( $line =~ /^\s*\#(.*)/ ) {
399 my $is_header = $strap->_is_header($line);
401 Checks if the given line is a header (1..M) line. If so, it places
402 how many tests there will be in $strap->{max}, a list of which tests
403 are todo in $strap->{todo} and if the whole test was skipped
404 $strap->{skip_all} contains the reason.
408 # Regex for parsing a header. Will be run with /x
409 my $Extra_Header_Re = <<'REGEX';
411 (?: \s+ todo \s+ ([\d \t]+) )? # optional todo set
412 (?: \s* \# \s* ([\w:]+\s?) (.*) )? # optional skip with optional reason
416 my($self, $line) = @_;
418 if( my($max, $extra) = $line =~ /^1\.\.(\d+)(.*)/ ) {
420 assert( $self->{max} >= 0, 'Max # of tests looks right' );
422 if( defined $extra ) {
423 my($todo, $skip, $reason) = $extra =~ /$Extra_Header_Re/xo;
425 $self->{todo} = { map { $_ => 1 } split /\s+/, $todo } if $todo;
427 $self->{skip_all} = $reason if defined $skip and $skip =~ /^Skip/i;
439 my $is_test = $strap->_is_test($line, \%test);
441 Checks if the $line is a test report (ie. 'ok/not ok'). Reports the
442 result back in %test which will contain:
444 ok did it succeed? This is the literal 'ok' or 'not ok'.
445 name name of the test (if any)
446 number test number (if any)
448 type 'todo' or 'skip' (if any)
449 reason why is it todo or skip? (if any)
451 If will also catch lone 'not' lines, note it saw them
452 $strap->{saw_lone_not} and the line in $strap->{lone_not_line}.
456 my $Report_Re = <<'REGEX';
460 (?:\s+(\d+))? # optional test number
465 my $Extra_Re = <<'REGEX';
467 (.*?) (?:(?:[^\\]|^)# (.*))?
472 my($self, $line, $test) = @_;
474 # We pulverize the line down into pieces in three parts.
475 if( my($not, $num, $extra) = $line =~ /$Report_Re/ox ) {
476 my($name, $control) = split /(?:[^\\]|^)#/, $extra if $extra;
477 my($type, $reason) = $control =~ /^\s*(\S+)(?:\s+(.*))?$/ if $control;
479 $test->{number} = $num;
480 $test->{ok} = $not ? 0 : 1;
481 $test->{name} = $name;
483 if( defined $type ) {
484 $test->{type} = $type =~ /^TODO$/i ? 'todo' :
485 $type =~ /^Skip/i ? 'skip' : 0;
490 $test->{reason} = $reason;
495 # Sometimes the "not " and "ok" will be on seperate lines on VMS.
496 # We catch this and remember we saw it.
497 if( $line =~ /^not\s+$/ ) {
498 $self->{saw_lone_not} = 1;
499 $self->{lone_not_line} = $self->{line};
506 =item B<_is_bail_out>
508 my $is_bail_out = $strap->_is_bail_out($line, \$reason);
510 Checks if the line is a "Bail out!". Places the reason for bailing
516 my($self, $line, $reason) = @_;
518 if( $line =~ /^Bail out!\s*(.*)/i ) {
527 =item B<_reset_file_state>
529 $strap->_reset_file_state;
531 Resets things like $strap->{max}, $strap->{skip_all}, etc... so its
532 ready to parse the next file.
536 sub _reset_file_state {
539 delete @{$self}{qw(max skip_all todo)};
541 $self->{saw_header} = 0;
542 $self->{saw_bailout}= 0;
543 $self->{saw_lone_not} = 0;
544 $self->{lone_not_line} = 0;
545 $self->{bailout_reason} = '';
556 The %results returned from analyze() contain the following information:
558 passing true if the whole test is considered a pass
559 (or skipped), false if its a failure
561 max total tests which should have been run
562 seen total tests actually seen
563 skip_all if the whole test was skipped, this will
566 ok number of tests which passed
567 (including todo and skips)
569 todo number of todo tests seen
570 bonus number of todo tests which
573 skip number of tests skipped
575 So a successful test should have max == seen == ok.
578 There is one final item, the details.
580 details an array ref reporting the result of
581 each test looks like this:
583 $results{details}[$test_num - 1] =
584 { ok => is the test considered ok?
585 actual_ok => did it literally say 'ok'?
586 name => name of the test (if any)
587 type => 'skip' or 'todo' (if any)
588 reason => reason for the above (if any)
591 Element 0 of the details is test #1. I tried it with element 1 being
592 #1 and 0 being empty, this is less awkward.
600 my %details = $strap->_detailize($pass, \%test);
602 Generates the details based on the last test line seen. $pass is true
603 if it was considered to be a passed test. %test is the results of the
604 test you're summarizing.
609 my($self, $pass, $test) = @_;
611 my %details = ( ok => $pass,
612 actual_ok => $test->{ok}
615 assert( !(grep !defined $details{$_}, keys %details),
616 'test contains the ok and actual_ok info' );
618 foreach my $piece (qw(name type reason)) {
619 $details{$piece} = $test->{$piece} if $test->{$piece};
631 See F<examples/mini_harness.plx> for an example of use.
635 Michael G Schwern E<lt>schwern@pobox.comE<gt>