Integrate macperl patches #16926 and #16938;
[p5sagit/p5-mst-13.2.git] / lib / Test / Harness / Straps.pm
CommitLineData
13287dd5 1# -*- Mode: cperl; cperl-indent-level: 4 -*-
0be28027 2# $Id: Straps.pm,v 1.8 2002/05/29 23:02:48 schwern Exp $
13287dd5 3
4package Test::Harness::Straps;
5
6use strict;
7use vars qw($VERSION);
8use Config;
0be28027 9$VERSION = '0.12';
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
0be28027 153 my $passed = !$totals{max} ||
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
0be28027 260 unless( -e $file ) {
261 $self->{error} = "$file does not exist";
262 return;
263 }
264
265 unless( -r $file ) {
266 $self->{error} = "$file is not readable";
267 return;
268 }
269
13287dd5 270 local $ENV{PERL5LIB} = $self->_INC2PERL5LIB;
271
272 # Is this necessary anymore?
273 my $cmd = $self->{_is_vms} ? "MCR $^X" : $^X;
274
275 my $switches = $self->_switches($file);
276
277 # *sigh* this breaks under taint, but open -| is unportable.
278 unless( open(FILE, "$cmd $switches $file|") ) {
279 print "can't run $file. $!\n";
280 return;
281 }
282
283 my %results = $self->analyze_fh($file, \*FILE);
356733da 284 my $exit = close FILE;
285 $results{'wait'} = $?;
f0008e52 286 if( $? && $self->{_is_vms} ) {
287 eval q{use vmsish "status"; $results{'exit'} = $?};
288 }
289 else {
6e5a998b 290 $results{'exit'} = _wait2exit($?);
f0008e52 291 }
356733da 292 $results{passing} = 0 unless $? == 0;
13287dd5 293
294 $self->_restore_PERL5LIB();
295
296 return %results;
297}
298
6e5a998b 299
300eval { require POSIX; &POSIX::WEXITSTATUS(0) };
301if( $@ ) {
302 *_wait2exit = sub { $_[0] >> 8 };
303}
304else {
305 *_wait2exit = sub { POSIX::WEXITSTATUS($_[0]) }
306}
307
308
13287dd5 309=begin _private
310
311=item B<_switches>
312
313 my $switches = $self->_switches($file);
314
315Formats and returns the switches necessary to run the test.
316
317=cut
318
319sub _switches {
320 my($self, $file) = @_;
321
322 local *TEST;
323 open(TEST, $file) or print "can't open $file. $!\n";
324 my $first = <TEST>;
325 my $s = '';
326 $s .= " $ENV{'HARNESS_PERL_SWITCHES'}"
327 if exists $ENV{'HARNESS_PERL_SWITCHES'};
0be28027 328
329 # When taint mode is on, PERL5LIB is ignored. So we need to put
330 # all that on the command line as -Is.
d5201bd2 331 if ($first =~ /^#!.*\bperl.*\s-\w*([Tt]+)/) {
332 $s .= join " ", qq[ "-$1"], map {qq["-I$_"]} $self->_filtered_INC;
333 } elsif ($^O eq 'MacOS') {
334 $s .= join " ", map {qq["-I$_"]} $self->_filtered_INC;
335 }
13287dd5 336
337 close(TEST) or print "can't close $file. $!\n";
338
339 return $s;
340}
341
342
343=item B<_INC2PERL5LIB>
344
345 local $ENV{PERL5LIB} = $self->_INC2PERL5LIB;
346
347Takes the current value of @INC and turns it into something suitable
348for putting onto PERL5LIB.
349
350=cut
351
352sub _INC2PERL5LIB {
353 my($self) = shift;
354
355 $self->{_old5lib} = $ENV{PERL5LIB};
356
357 return join $Config{path_sep}, $self->_filtered_INC;
358}
359
360=item B<_filtered_INC>
361
362 my @filtered_inc = $self->_filtered_INC;
363
364Shortens @INC by removing redundant and unnecessary entries.
365Necessary for OS's with limited command line lengths, like VMS.
366
367=cut
368
369sub _filtered_INC {
370 my($self, @inc) = @_;
371 @inc = @INC unless @inc;
372
373 # VMS has a 255-byte limit on the length of %ENV entries, so
374 # toss the ones that involve perl_root, the install location
375 # for VMS
376 if( $self->{_is_vms} ) {
377 @inc = grep !/perl_root/i, @inc;
378 }
379
380 return @inc;
381}
382
383
384=item B<_restore_PERL5LIB>
385
386 $self->_restore_PERL5LIB;
387
388This restores the original value of the PERL5LIB environment variable.
389Necessary on VMS, otherwise a no-op.
390
391=cut
392
393sub _restore_PERL5LIB {
394 my($self) = shift;
395
396 return unless $self->{_is_vms};
397
398 if (defined $self->{_old5lib}) {
399 $ENV{PERL5LIB} = $self->{_old5lib};
400 }
401}
402
403
404=end _private
405
406=back
407
408
409=begin _private
410
411=head2 Parsing
412
413Methods for identifying what sort of line you're looking at.
414
415=over 4
416
417=item B<_is_comment>
418
419 my $is_comment = $strap->_is_comment($line, \$comment);
420
421Checks if the given line is a comment. If so, it will place it into
422$comment (sans #).
423
424=cut
425
426sub _is_comment {
427 my($self, $line, $comment) = @_;
428
429 if( $line =~ /^\s*\#(.*)/ ) {
430 $$comment = $1;
431 return $YES;
432 }
433 else {
434 return $NO;
435 }
436}
437
438=item B<_is_header>
439
440 my $is_header = $strap->_is_header($line);
441
442Checks if the given line is a header (1..M) line. If so, it places
443how many tests there will be in $strap->{max}, a list of which tests
444are todo in $strap->{todo} and if the whole test was skipped
445$strap->{skip_all} contains the reason.
446
447=cut
448
449# Regex for parsing a header. Will be run with /x
450my $Extra_Header_Re = <<'REGEX';
451 ^
452 (?: \s+ todo \s+ ([\d \t]+) )? # optional todo set
453 (?: \s* \# \s* ([\w:]+\s?) (.*) )? # optional skip with optional reason
454REGEX
455
456sub _is_header {
457 my($self, $line) = @_;
458
459 if( my($max, $extra) = $line =~ /^1\.\.(\d+)(.*)/ ) {
460 $self->{max} = $max;
461 assert( $self->{max} >= 0, 'Max # of tests looks right' );
0be28027 462
463 if( defined $extra ) {
13287dd5 464 my($todo, $skip, $reason) = $extra =~ /$Extra_Header_Re/xo;
465
466 $self->{todo} = { map { $_ => 1 } split /\s+/, $todo } if $todo;
467
468 $self->{skip_all} = $reason if defined $skip and $skip =~ /^Skip/i;
469 }
470
471 return $YES;
472 }
473 else {
474 return $NO;
475 }
476}
477
478=item B<_is_test>
479
480 my $is_test = $strap->_is_test($line, \%test);
481
482Checks if the $line is a test report (ie. 'ok/not ok'). Reports the
483result back in %test which will contain:
484
485 ok did it succeed? This is the literal 'ok' or 'not ok'.
486 name name of the test (if any)
487 number test number (if any)
488
489 type 'todo' or 'skip' (if any)
490 reason why is it todo or skip? (if any)
491
492If will also catch lone 'not' lines, note it saw them
493$strap->{saw_lone_not} and the line in $strap->{lone_not_line}.
494
495=cut
496
497my $Report_Re = <<'REGEX';
498 ^
499 (not\ )? # failure?
500 ok\b
501 (?:\s+(\d+))? # optional test number
502 \s*
503 (.*) # and the rest
504REGEX
505
506my $Extra_Re = <<'REGEX';
507 ^
508 (.*?) (?:(?:[^\\]|^)# (.*))?
509 $
510REGEX
511
512sub _is_test {
513 my($self, $line, $test) = @_;
514
515 # We pulverize the line down into pieces in three parts.
516 if( my($not, $num, $extra) = $line =~ /$Report_Re/ox ) {
517 my($name, $control) = split /(?:[^\\]|^)#/, $extra if $extra;
518 my($type, $reason) = $control =~ /^\s*(\S+)(?:\s+(.*))?$/ if $control;
519
520 $test->{number} = $num;
521 $test->{ok} = $not ? 0 : 1;
522 $test->{name} = $name;
523
524 if( defined $type ) {
525 $test->{type} = $type =~ /^TODO$/i ? 'todo' :
526 $type =~ /^Skip/i ? 'skip' : 0;
527 }
528 else {
529 $test->{type} = '';
530 }
531 $test->{reason} = $reason;
532
533 return $YES;
534 }
535 else{
536 # Sometimes the "not " and "ok" will be on seperate lines on VMS.
537 # We catch this and remember we saw it.
538 if( $line =~ /^not\s+$/ ) {
539 $self->{saw_lone_not} = 1;
540 $self->{lone_not_line} = $self->{line};
541 }
542
543 return $NO;
544 }
545}
546
547=item B<_is_bail_out>
548
549 my $is_bail_out = $strap->_is_bail_out($line, \$reason);
550
551Checks if the line is a "Bail out!". Places the reason for bailing
552(if any) in $reason.
553
554=cut
555
556sub _is_bail_out {
557 my($self, $line, $reason) = @_;
558
559 if( $line =~ /^Bail out!\s*(.*)/i ) {
560 $$reason = $1 if $1;
561 return $YES;
562 }
563 else {
564 return $NO;
565 }
566}
567
568=item B<_reset_file_state>
569
570 $strap->_reset_file_state;
571
572Resets things like $strap->{max}, $strap->{skip_all}, etc... so its
573ready to parse the next file.
574
575=cut
576
577sub _reset_file_state {
578 my($self) = shift;
579
580 delete @{$self}{qw(max skip_all todo)};
581 $self->{line} = 0;
582 $self->{saw_header} = 0;
583 $self->{saw_bailout}= 0;
584 $self->{saw_lone_not} = 0;
585 $self->{lone_not_line} = 0;
586 $self->{bailout_reason} = '';
587 $self->{'next'} = 1;
588}
589
590=back
591
592=end _private
593
594
595=head2 Results
596
597The %results returned from analyze() contain the following information:
598
599 passing true if the whole test is considered a pass
600 (or skipped), false if its a failure
601
356733da 602 exit the exit code of the test run, if from a file
603 wait the wait code of the test run, if from a file
604
13287dd5 605 max total tests which should have been run
606 seen total tests actually seen
607 skip_all if the whole test was skipped, this will
608 contain the reason.
609
610 ok number of tests which passed
611 (including todo and skips)
612
613 todo number of todo tests seen
614 bonus number of todo tests which
615 unexpectedly passed
616
617 skip number of tests skipped
618
619So a successful test should have max == seen == ok.
620
621
622There is one final item, the details.
623
624 details an array ref reporting the result of
625 each test looks like this:
626
627 $results{details}[$test_num - 1] =
628 { ok => is the test considered ok?
629 actual_ok => did it literally say 'ok'?
630 name => name of the test (if any)
631 type => 'skip' or 'todo' (if any)
632 reason => reason for the above (if any)
633 };
634
635Element 0 of the details is test #1. I tried it with element 1 being
636#1 and 0 being empty, this is less awkward.
637
638=begin _private
639
640=over 4
641
642=item B<_detailize>
643
644 my %details = $strap->_detailize($pass, \%test);
645
646Generates the details based on the last test line seen. $pass is true
647if it was considered to be a passed test. %test is the results of the
648test you're summarizing.
649
650=cut
651
652sub _detailize {
653 my($self, $pass, $test) = @_;
654
655 my %details = ( ok => $pass,
656 actual_ok => $test->{ok}
657 );
658
659 assert( !(grep !defined $details{$_}, keys %details),
660 'test contains the ok and actual_ok info' );
661
308957f5 662 # We don't want these to be undef because they are often
663 # checked and don't want the checker to have to deal with
664 # uninitialized vars.
13287dd5 665 foreach my $piece (qw(name type reason)) {
308957f5 666 $details{$piece} = defined $test->{$piece} ? $test->{$piece} : '';
13287dd5 667 }
668
669 return %details;
670}
671
672=back
673
674=end _private
675
676=head1 EXAMPLES
677
678See F<examples/mini_harness.plx> for an example of use.
679
680=head1 AUTHOR
681
682Michael G Schwern E<lt>schwern@pobox.comE<gt>
683
684=head1 SEE ALSO
685
686L<Test::Harness>
687
688=cut
689
690
6911;