Test::Harness and skiping tests functionality
[p5sagit/p5-mst-13.2.git] / lib / Test / Harness / Straps.pm
CommitLineData
13287dd5 1# -*- Mode: cperl; cperl-indent-level: 4 -*-
6e5a998b 2# $Id: Straps.pm,v 1.6 2002/05/17 23:04:11 schwern Exp $
13287dd5 3
4package Test::Harness::Straps;
5
6use strict;
7use vars qw($VERSION);
8use Config;
6e5a998b 9$VERSION = '0.11';
13287dd5 10
11use Test::Harness::Assert;
12use Test::Harness::Iterator;
13
14# Flags used as return values from our methods. Just for internal
15# clarification.
16my $TRUE = (1==1);
17my $FALSE = !$TRUE;
18my $YES = $TRUE;
19my $NO = $FALSE;
20
21
22=head1 NAME
23
24Test::Harness::Straps - detailed analysis of test results
25
26=head1 SYNOPSIS
27
28 use Test::Harness::Straps;
29
30 my $strap = Test::Harness::Straps->new;
31
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);
36
37 # UNIMPLEMENTED
38 my %total = $strap->total_results;
39
40 # Altering the behavior of the strap UNIMPLEMENTED
41 my $verbose_output = $strap->dump_verbose();
42 $strap->dump_verbose_fh($output_filehandle);
43
44
45=head1 DESCRIPTION
46
47B<THIS IS ALPHA SOFTWARE> in that the interface is subject to change
48in incompatible ways. It is otherwise stable.
49
50Test::Harness is limited to printing out its results. This makes
51analysis of the test results difficult for anything but a human. To
52make it easier for programs to work with test results, we provide
53Test::Harness::Straps. Instead of printing the results, straps
54provide them as raw data. You can also configure how the tests are to
55be run.
56
57The interface is currently incomplete. I<Please> contact the author
58if you'd like a feature added or something change or just have
59comments.
60
61=head2 Construction
62
63=over 4
64
65=item B<new>
66
67 my $strap = Test::Harness::Straps->new;
68
69Initialize a new strap.
70
71=cut
72
73sub new {
74 my($proto) = shift;
75 my($class) = ref $proto || $proto;
76
77 my $self = bless {}, $class;
78 $self->_init;
79
80 return $self;
81}
82
83=begin _private
84
85=item B<_init>
86
87 $strap->_init;
88
89Initialize the internal state of a strap to make it ready for parsing.
90
91=cut
92
93sub _init {
94 my($self) = shift;
95
96 $self->{_is_vms} = $^O eq 'VMS';
97}
98
99=end _private
100
101=back
102
103=head2 Analysis
104
105=over 4
106
107=item B<analyze>
108
109 my %results = $strap->analyze($name, \@test_output);
110
111Analyzes the output of a single test, assigning it the given $name for
112use in the total report. Returns the %results of the test. See
113L<Results>.
114
115@test_output should be the raw output from the test, including newlines.
116
117=cut
118
119sub analyze {
120 my($self, $name, $test_output) = @_;
121
122 my $it = Test::Harness::Iterator->new($test_output);
123 return $self->_analyze_iterator($name, $it);
124}
125
126
127sub _analyze_iterator {
128 my($self, $name, $it) = @_;
129
130 $self->_reset_file_state;
131 $self->{file} = $name;
132 my %totals = (
133 max => 0,
134 seen => 0,
135
136 ok => 0,
137 todo => 0,
138 skip => 0,
139 bonus => 0,
308957f5 140
13287dd5 141 details => []
142 );
143
308957f5 144 # Set them up here so callbacks can have them.
145 $self->{totals}{$name} = \%totals;
13287dd5 146 while( defined(my $line = $it->next) ) {
147 $self->_analyze_line($line, \%totals);
148 last if $self->{saw_bailout};
149 }
150
356733da 151 $totals{skip_all} = $self->{skip_all} if defined $self->{skip_all};
152
13287dd5 153 my $passed = $totals{skip_all} ||
356733da 154 ($totals{max} && $totals{seen} &&
155 $totals{max} == $totals{seen} &&
13287dd5 156 $totals{max} == $totals{ok});
157 $totals{passing} = $passed ? 1 : 0;
158
13287dd5 159 return %totals;
160}
161
162
163sub _analyze_line {
164 my($self, $line, $totals) = @_;
165
166 my %result = ();
308957f5 167
13287dd5 168 $self->{line}++;
169
170 my $type;
171 if( $self->_is_header($line) ) {
172 $type = 'header';
173
174 $self->{saw_header}++;
308957f5 175
13287dd5 176 $totals->{max} += $self->{max};
177 }
178 elsif( $self->_is_test($line, \%result) ) {
179 $type = 'test';
180
181 $totals->{seen}++;
182 $result{number} = $self->{'next'} unless $result{number};
183
184 # sometimes the 'not ' and the 'ok' are on different lines,
185 # happens often on VMS if you do:
186 # print "not " unless $test;
187 # print "ok $num\n";
188 if( $self->{saw_lone_not} &&
189 ($self->{lone_not_line} == $self->{line} - 1) )
190 {
191 $result{ok} = 0;
192 }
193
194 my $pass = $result{ok};
195 $result{type} = 'todo' if $self->{todo}{$result{number}};
196
197 if( $result{type} eq 'todo' ) {
198 $totals->{todo}++;
199 $pass = 1;
200 $totals->{bonus}++ if $result{ok}
201 }
202 elsif( $result{type} eq 'skip' ) {
203 $totals->{skip}++;
204 $pass = 1;
205 }
206
207 $totals->{ok}++ if $pass;
208
356733da 209 if( $result{number} > 100000 ) {
210 warn "Enourmous test number seen [test $result{number}]\n";
211 warn "Can't detailize, too big.\n";
212 }
213 else {
214 $totals->{details}[$result{number} - 1] =
13287dd5 215 {$self->_detailize($pass, \%result)};
356733da 216 }
13287dd5 217
218 # XXX handle counter mismatch
219 }
220 elsif ( $self->_is_bail_out($line, \$self->{bailout_reason}) ) {
221 $type = 'bailout';
222 $self->{saw_bailout} = 1;
223 }
224 else {
225 $type = 'other';
226 }
227
228 $self->{callback}->($self, $line, $type, $totals) if $self->{callback};
229
230 $self->{'next'} = $result{number} + 1 if $type eq 'test';
231}
232
233=item B<analyze_fh>
234
235 my %results = $strap->analyze_fh($name, $test_filehandle);
236
237Like C<analyze>, but it reads from the given filehandle.
238
239=cut
240
241sub analyze_fh {
242 my($self, $name, $fh) = @_;
243
244 my $it = Test::Harness::Iterator->new($fh);
245 $self->_analyze_iterator($name, $it);
246}
247
248=item B<analyze_file>
249
250 my %results = $strap->analyze_file($test_file);
251
356733da 252Like C<analyze>, but it runs the given $test_file and parses it's
253results. It will also use that name for the total report.
13287dd5 254
255=cut
256
257sub analyze_file {
258 my($self, $file) = @_;
259
260 local $ENV{PERL5LIB} = $self->_INC2PERL5LIB;
261
262 # Is this necessary anymore?
263 my $cmd = $self->{_is_vms} ? "MCR $^X" : $^X;
264
265 my $switches = $self->_switches($file);
266
267 # *sigh* this breaks under taint, but open -| is unportable.
268 unless( open(FILE, "$cmd $switches $file|") ) {
269 print "can't run $file. $!\n";
270 return;
271 }
272
273 my %results = $self->analyze_fh($file, \*FILE);
356733da 274 my $exit = close FILE;
275 $results{'wait'} = $?;
f0008e52 276 if( $? && $self->{_is_vms} ) {
277 eval q{use vmsish "status"; $results{'exit'} = $?};
278 }
279 else {
6e5a998b 280 $results{'exit'} = _wait2exit($?);
f0008e52 281 }
356733da 282 $results{passing} = 0 unless $? == 0;
13287dd5 283
284 $self->_restore_PERL5LIB();
285
286 return %results;
287}
288
6e5a998b 289
290eval { require POSIX; &POSIX::WEXITSTATUS(0) };
291if( $@ ) {
292 *_wait2exit = sub { $_[0] >> 8 };
293}
294else {
295 *_wait2exit = sub { POSIX::WEXITSTATUS($_[0]) }
296}
297
298
13287dd5 299=begin _private
300
301=item B<_switches>
302
303 my $switches = $self->_switches($file);
304
305Formats and returns the switches necessary to run the test.
306
307=cut
308
309sub _switches {
310 my($self, $file) = @_;
311
312 local *TEST;
313 open(TEST, $file) or print "can't open $file. $!\n";
314 my $first = <TEST>;
315 my $s = '';
316 $s .= " $ENV{'HARNESS_PERL_SWITCHES'}"
317 if exists $ENV{'HARNESS_PERL_SWITCHES'};
318 $s .= join " ", qq[ "-$1"], map {qq["-I$_"]} $self->_filtered_INC
6e5a998b 319 if $first =~ /^#!.*\bperl.*\s-\w*([Tt]+)/;
13287dd5 320
321 close(TEST) or print "can't close $file. $!\n";
322
323 return $s;
324}
325
326
327=item B<_INC2PERL5LIB>
328
329 local $ENV{PERL5LIB} = $self->_INC2PERL5LIB;
330
331Takes the current value of @INC and turns it into something suitable
332for putting onto PERL5LIB.
333
334=cut
335
336sub _INC2PERL5LIB {
337 my($self) = shift;
338
339 $self->{_old5lib} = $ENV{PERL5LIB};
340
341 return join $Config{path_sep}, $self->_filtered_INC;
342}
343
344=item B<_filtered_INC>
345
346 my @filtered_inc = $self->_filtered_INC;
347
348Shortens @INC by removing redundant and unnecessary entries.
349Necessary for OS's with limited command line lengths, like VMS.
350
351=cut
352
353sub _filtered_INC {
354 my($self, @inc) = @_;
355 @inc = @INC unless @inc;
356
357 # VMS has a 255-byte limit on the length of %ENV entries, so
358 # toss the ones that involve perl_root, the install location
359 # for VMS
360 if( $self->{_is_vms} ) {
361 @inc = grep !/perl_root/i, @inc;
362 }
363
364 return @inc;
365}
366
367
368=item B<_restore_PERL5LIB>
369
370 $self->_restore_PERL5LIB;
371
372This restores the original value of the PERL5LIB environment variable.
373Necessary on VMS, otherwise a no-op.
374
375=cut
376
377sub _restore_PERL5LIB {
378 my($self) = shift;
379
380 return unless $self->{_is_vms};
381
382 if (defined $self->{_old5lib}) {
383 $ENV{PERL5LIB} = $self->{_old5lib};
384 }
385}
386
387
388=end _private
389
390=back
391
392
393=begin _private
394
395=head2 Parsing
396
397Methods for identifying what sort of line you're looking at.
398
399=over 4
400
401=item B<_is_comment>
402
403 my $is_comment = $strap->_is_comment($line, \$comment);
404
405Checks if the given line is a comment. If so, it will place it into
406$comment (sans #).
407
408=cut
409
410sub _is_comment {
411 my($self, $line, $comment) = @_;
412
413 if( $line =~ /^\s*\#(.*)/ ) {
414 $$comment = $1;
415 return $YES;
416 }
417 else {
418 return $NO;
419 }
420}
421
422=item B<_is_header>
423
424 my $is_header = $strap->_is_header($line);
425
426Checks if the given line is a header (1..M) line. If so, it places
427how many tests there will be in $strap->{max}, a list of which tests
428are todo in $strap->{todo} and if the whole test was skipped
429$strap->{skip_all} contains the reason.
430
431=cut
432
433# Regex for parsing a header. Will be run with /x
434my $Extra_Header_Re = <<'REGEX';
435 ^
436 (?: \s+ todo \s+ ([\d \t]+) )? # optional todo set
437 (?: \s* \# \s* ([\w:]+\s?) (.*) )? # optional skip with optional reason
438REGEX
439
440sub _is_header {
441 my($self, $line) = @_;
442
443 if( my($max, $extra) = $line =~ /^1\.\.(\d+)(.*)/ ) {
444 $self->{max} = $max;
445 assert( $self->{max} >= 0, 'Max # of tests looks right' );
446
195d559b 447 if( not $max and not $extra) {
448 #We're skipping, thats for sure
449 $self->{skip_all} = 'no reason given';
450 }
451 elsif( defined $extra ) {
13287dd5 452 my($todo, $skip, $reason) = $extra =~ /$Extra_Header_Re/xo;
453
454 $self->{todo} = { map { $_ => 1 } split /\s+/, $todo } if $todo;
455
456 $self->{skip_all} = $reason if defined $skip and $skip =~ /^Skip/i;
457 }
458
459 return $YES;
460 }
461 else {
462 return $NO;
463 }
464}
465
466=item B<_is_test>
467
468 my $is_test = $strap->_is_test($line, \%test);
469
470Checks if the $line is a test report (ie. 'ok/not ok'). Reports the
471result back in %test which will contain:
472
473 ok did it succeed? This is the literal 'ok' or 'not ok'.
474 name name of the test (if any)
475 number test number (if any)
476
477 type 'todo' or 'skip' (if any)
478 reason why is it todo or skip? (if any)
479
480If will also catch lone 'not' lines, note it saw them
481$strap->{saw_lone_not} and the line in $strap->{lone_not_line}.
482
483=cut
484
485my $Report_Re = <<'REGEX';
486 ^
487 (not\ )? # failure?
488 ok\b
489 (?:\s+(\d+))? # optional test number
490 \s*
491 (.*) # and the rest
492REGEX
493
494my $Extra_Re = <<'REGEX';
495 ^
496 (.*?) (?:(?:[^\\]|^)# (.*))?
497 $
498REGEX
499
500sub _is_test {
501 my($self, $line, $test) = @_;
502
503 # We pulverize the line down into pieces in three parts.
504 if( my($not, $num, $extra) = $line =~ /$Report_Re/ox ) {
505 my($name, $control) = split /(?:[^\\]|^)#/, $extra if $extra;
506 my($type, $reason) = $control =~ /^\s*(\S+)(?:\s+(.*))?$/ if $control;
507
508 $test->{number} = $num;
509 $test->{ok} = $not ? 0 : 1;
510 $test->{name} = $name;
511
512 if( defined $type ) {
513 $test->{type} = $type =~ /^TODO$/i ? 'todo' :
514 $type =~ /^Skip/i ? 'skip' : 0;
515 }
516 else {
517 $test->{type} = '';
518 }
519 $test->{reason} = $reason;
520
521 return $YES;
522 }
523 else{
524 # Sometimes the "not " and "ok" will be on seperate lines on VMS.
525 # We catch this and remember we saw it.
526 if( $line =~ /^not\s+$/ ) {
527 $self->{saw_lone_not} = 1;
528 $self->{lone_not_line} = $self->{line};
529 }
530
531 return $NO;
532 }
533}
534
535=item B<_is_bail_out>
536
537 my $is_bail_out = $strap->_is_bail_out($line, \$reason);
538
539Checks if the line is a "Bail out!". Places the reason for bailing
540(if any) in $reason.
541
542=cut
543
544sub _is_bail_out {
545 my($self, $line, $reason) = @_;
546
547 if( $line =~ /^Bail out!\s*(.*)/i ) {
548 $$reason = $1 if $1;
549 return $YES;
550 }
551 else {
552 return $NO;
553 }
554}
555
556=item B<_reset_file_state>
557
558 $strap->_reset_file_state;
559
560Resets things like $strap->{max}, $strap->{skip_all}, etc... so its
561ready to parse the next file.
562
563=cut
564
565sub _reset_file_state {
566 my($self) = shift;
567
568 delete @{$self}{qw(max skip_all todo)};
569 $self->{line} = 0;
570 $self->{saw_header} = 0;
571 $self->{saw_bailout}= 0;
572 $self->{saw_lone_not} = 0;
573 $self->{lone_not_line} = 0;
574 $self->{bailout_reason} = '';
575 $self->{'next'} = 1;
576}
577
578=back
579
580=end _private
581
582
583=head2 Results
584
585The %results returned from analyze() contain the following information:
586
587 passing true if the whole test is considered a pass
588 (or skipped), false if its a failure
589
356733da 590 exit the exit code of the test run, if from a file
591 wait the wait code of the test run, if from a file
592
13287dd5 593 max total tests which should have been run
594 seen total tests actually seen
595 skip_all if the whole test was skipped, this will
596 contain the reason.
597
598 ok number of tests which passed
599 (including todo and skips)
600
601 todo number of todo tests seen
602 bonus number of todo tests which
603 unexpectedly passed
604
605 skip number of tests skipped
606
607So a successful test should have max == seen == ok.
608
609
610There is one final item, the details.
611
612 details an array ref reporting the result of
613 each test looks like this:
614
615 $results{details}[$test_num - 1] =
616 { ok => is the test considered ok?
617 actual_ok => did it literally say 'ok'?
618 name => name of the test (if any)
619 type => 'skip' or 'todo' (if any)
620 reason => reason for the above (if any)
621 };
622
623Element 0 of the details is test #1. I tried it with element 1 being
624#1 and 0 being empty, this is less awkward.
625
626=begin _private
627
628=over 4
629
630=item B<_detailize>
631
632 my %details = $strap->_detailize($pass, \%test);
633
634Generates the details based on the last test line seen. $pass is true
635if it was considered to be a passed test. %test is the results of the
636test you're summarizing.
637
638=cut
639
640sub _detailize {
641 my($self, $pass, $test) = @_;
642
643 my %details = ( ok => $pass,
644 actual_ok => $test->{ok}
645 );
646
647 assert( !(grep !defined $details{$_}, keys %details),
648 'test contains the ok and actual_ok info' );
649
308957f5 650 # We don't want these to be undef because they are often
651 # checked and don't want the checker to have to deal with
652 # uninitialized vars.
13287dd5 653 foreach my $piece (qw(name type reason)) {
308957f5 654 $details{$piece} = defined $test->{$piece} ? $test->{$piece} : '';
13287dd5 655 }
656
657 return %details;
658}
659
660=back
661
662=end _private
663
664=head1 EXAMPLES
665
666See F<examples/mini_harness.plx> for an example of use.
667
668=head1 AUTHOR
669
670Michael G Schwern E<lt>schwern@pobox.comE<gt>
671
672=head1 SEE ALSO
673
674L<Test::Harness>
675
676=cut
677
678
6791;