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