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