perl 5.003_04: lib/Test/Harness.pm
[p5sagit/p5-mst-13.2.git] / lib / Test / Harness.pm
1 package Test::Harness;
2
3 use 5.002;
4 use Exporter;
5 use Benchmark;
6 use Config;
7 use FileHandle;
8 use strict;
9
10 use vars qw($VERSION $verbose $switches $have_devel_corestack $curtest
11             @ISA @EXPORT @EXPORT_OK);
12 $have_devel_corestack = 0;
13
14 $VERSION = "1.12";
15
16 @ISA=('Exporter');
17 @EXPORT= qw(&runtests);
18 @EXPORT_OK= qw($verbose $switches);
19
20 format STDOUT_TOP =
21 Failed Test  Status Wstat Total Fail  Failed  List of failed
22 ------------------------------------------------------------------------------
23 .
24
25 format STDOUT =
26 @<<<<<<<<<<<<<< @>> @>>>> @>>>> @>>> ^##.##%  @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
27 { $curtest->{name},
28                 $curtest->{estat},
29                     $curtest->{wstat},
30                           $curtest->{max},
31                                 $curtest->{failed},
32                                      $curtest->{percent},
33                                               $curtest->{canon}
34 }
35 .
36
37
38 $verbose = 0;
39 $switches = "-w";
40
41 sub runtests {
42     my(@tests) = @_;
43     local($|) = 1;
44     my($test,$te,$ok,$next,$max,$pct,$totok,@failed,%failedtests);
45     my $totmax = 0;
46     my $files = 0;
47     my $bad = 0;
48     my $good = 0;
49     my $total = @tests;
50     local($ENV{'PERL5LIB'}) = join($Config{path_sep}, @INC); # pass -I flags to children
51
52     my $t_start = new Benchmark;
53     while ($test = shift(@tests)) {
54         $te = $test;
55         chop($te);
56         print "$te" . '.' x (20 - length($te));
57         my $fh = new FileHandle;
58         $fh->open("$^X $switches $test|") || (print "can't run. $!\n");
59         $ok = $next = $max = 0;
60         @failed = ();
61         while (<$fh>) {
62             if( $verbose ){
63                 print $_;
64             }
65             if (/^1\.\.([0-9]+)/) {
66                 $max = $1;
67                 $totmax += $max;
68                 $files++;
69                 $next = 1;
70             } elsif ($max && /^(not\s+)?ok\b/) {
71                 my $this = $next;
72                 if (/^not ok\s*(\d*)/){
73                     $this = $1 if $1 > 0;
74                     push @failed, $this;
75                 } elsif (/^ok\s*(\d*)/) {
76                     $this = $1 if $1 > 0;
77                     $ok++;
78                     $totok++;
79                 }
80                 if ($this > $next) {
81                     # warn "Test output counter mismatch [test $this]\n";
82                     # no need to warn probably
83                     push @failed, $next..$this-1;
84                 } elsif ($this < $next) {
85                     #we have seen more "ok" lines than the number suggests
86                     warn "Confused test output: test $this answered after test ", $next-1, "\n";
87                     $next = $this;
88                 }
89                 $next = $this + 1;
90             }
91         }
92         $fh->close; # must close to reap child resource values
93         my $wstatus = $?;
94         my $estatus = $^O eq 'VMS' ? $wstatus : $wstatus >> 8;
95         if ($^O eq 'VMS' ? !($wstatus & 1) : $wstatus) {
96             print "dubious\n\tTest returned status $estatus (wstat $wstatus)\n";
97             if (corestatus($wstatus)) { # until we have a wait module
98                 if ($have_devel_corestack) {
99                     Devel::CoreStack::stack($^X);
100                 } else {
101                     print "\ttest program seems to have generated a core\n";
102                 }
103             }
104             $bad++;
105             $failedtests{$test} = { canon => '??',  max => $max || '??',
106                                     failed => '??', 
107                                     name => $test, percent => undef,
108                                     estat => $estatus, wstat => $wstatus,
109                                   };
110         } elsif ($ok == $max && $next == $max+1) {
111             if ($max) {
112                 print "ok\n";
113             } else {
114                 print "skipping test on this platform\n";
115             }
116             $good++;
117         } elsif ($max) {
118             if ($next <= $max) {
119                 push @failed, $next..$max;
120             }
121             if (@failed) {
122                 my ($txt, $canon) = canonfailed($max,@failed);
123                 print $txt;
124                 $failedtests{$test} = { canon => $canon,  max => $max,
125                                         failed => scalar @failed,
126                                         name => $test, percent => 100*(scalar @failed)/$max,
127                                         estat => '', wstat => '',
128                                       };
129             } else {
130                 print "Don't know which tests failed: got $ok ok, expected $max\n";
131                 $failedtests{$test} = { canon => '??',  max => $max,
132                                         failed => '??', 
133                                         name => $test, percent => undef,
134                                         estat => '', wstat => '',
135                                       };
136             }
137             $bad++;
138         } elsif ($next == 0) {
139             print "FAILED before any test output arrived\n";
140             $bad++;
141             $failedtests{$test} = { canon => '??',  max => '??',
142                                     failed => '??',
143                                     name => $test, percent => undef,
144                                     estat => '', wstat => '',
145                                   };
146         }
147     }
148     my $t_total = timediff(new Benchmark, $t_start);
149     
150     if ($bad == 0 && $totmax) {
151             print "All tests successful.\n";
152     } elsif ($total==0){
153         die "FAILED--no tests were run for some reason.\n";
154     } elsif ($totmax==0) {
155         my $blurb = $total==1 ? "script" : "scripts";
156         die "FAILED--$total test $blurb could be run, alas--no output ever seen\n";
157     } else {
158         $pct = sprintf("%.2f", $good / $total * 100);
159         my $subpct = sprintf " %d/%d subtests failed, %.2f%% okay.",
160         $totmax - $totok, $totmax, 100*$totok/$totmax;
161         my $script;
162         for $script (sort keys %failedtests) {
163           $curtest = $failedtests{$script};
164           write;
165         }
166         if ($bad > 1) {
167             die "Failed $bad/$total test scripts, $pct% okay.$subpct\n";
168         }
169     }
170     printf("Files=%d,  Tests=%d, %s\n", $files, $totmax, timestr($t_total, 'nop'));
171
172     return ($bad == 0 && $totmax) ;
173 }
174
175 sub corestatus {
176     my($st) = @_;
177     my($ret);
178
179     eval {require 'wait.ph'};
180     if ($@) {
181       SWITCH: {
182             $ret = ($st & 0200); # Tim says, this is for 90%
183         }
184     } else {
185         $ret = WCOREDUMP($st);
186     }
187
188     eval {require Devel::CoreStack};
189     $have_devel_corestack++ unless $@;
190
191     $ret;
192 }
193
194 sub canonfailed ($@) {
195     my($max,@failed) = @_;
196     my %seen;
197     @failed = sort {$a <=> $b} grep !$seen{$_}++, @failed;
198     my $failed = @failed;
199     my @result = ();
200     my @canon = ();
201     my $min;
202     my $last = $min = shift @failed;
203     my $canon;
204     if (@failed) {
205         for (@failed, $failed[-1]) { # don't forget the last one
206             if ($_ > $last+1 || $_ == $last) {
207                 if ($min == $last) {
208                     push @canon, $last;
209                 } else {
210                     push @canon, "$min-$last";
211                 }
212                 $min = $_;
213             }
214             $last = $_;
215         }
216         local $" = ", ";
217         push @result, "FAILED tests @canon\n";
218         $canon = "@canon";
219     } else {
220         push @result, "FAILED test $last\n";
221         $canon = $last;
222     }
223
224     push @result, "\tFailed $failed/$max tests, ";
225     push @result, sprintf("%.2f",100*(1-$failed/$max)), "% okay\n";
226     my $txt = join "", @result;
227     ($txt, $canon);
228 }
229
230 1;
231 __END__
232
233 =head1 NAME
234
235 Test::Harness - run perl standard test scripts with statistics
236
237 =head1 SYNOPSIS
238
239 use Test::Harness;
240
241 runtests(@tests);
242
243 =head1 DESCRIPTION
244
245 Perl test scripts print to standard output C<"ok N"> for each single
246 test, where C<N> is an increasing sequence of integers. The first line
247 output by a standard test script is C<"1..M"> with C<M> being the
248 number of tests that should be run within the test
249 script. Test::Harness::runtests(@tests) runs all the testscripts
250 named as arguments and checks standard output for the expected
251 C<"ok N"> strings.
252
253 After all tests have been performed, runtests() prints some
254 performance statistics that are computed by the Benchmark module.
255
256 =head2 The test script output
257
258 Any output from the testscript to standard error is ignored and
259 bypassed, thus will be seen by the user. Lines written to standard
260 output containing C</^(not\s+)?ok\b/> are interpreted as feedback for
261 runtests().  All other lines are discarded.
262
263 It is tolerated if the test numbers after C<ok> are omitted. In this
264 case Test::Harness maintains temporarily its own counter until the
265 script supplies test numbers again. So the following test script
266
267     print <<END;
268     1..6
269     not ok
270     ok
271     not ok
272     ok
273     ok
274     END
275
276 will generate 
277
278     FAILED tests 1, 3, 6
279     Failed 3/6 tests, 50.00% okay
280
281 The global variable $Test::Harness::verbose is exportable and can be
282 used to let runtests() display the standard output of the script
283 without altering the behavior otherwise.
284
285 =head1 EXPORT
286
287 C<&runtests> is exported by Test::Harness per default.
288
289 =head1 DIAGNOSTICS
290
291 =over 4
292
293 =item C<All tests successful.\nFiles=%d,  Tests=%d, %s>
294
295 If all tests are successful some statistics about the performance are
296 printed.
297
298 =item C<FAILED tests %s\n\tFailed %d/%d tests, %.2f%% okay.>
299
300 For any single script that has failing subtests statistics like the
301 above are printed.
302
303 =item C<Test returned status %d (wstat %d)>
304
305 Scripts that return a non-zero exit status, both $?>>8 and $? are
306 printed in a message similar to the above.
307
308 =item C<Failed 1 test, %.2f%% okay. %s>
309
310 =item C<Failed %d/%d tests, %.2f%% okay. %s>
311
312 If not all tests were successful, the script dies with one of the
313 above messages.
314
315 =back
316
317 =head1 SEE ALSO
318
319 See L<Benchmark> for the underlying timing routines.
320
321 =head1 AUTHORS
322
323 Either Tim Bunce or Andreas Koenig, we don't know. What we know for
324 sure is, that it was inspired by Larry Wall's TEST script that came
325 with perl distributions for ages. Current maintainer is Andreas
326 Koenig.
327
328 =head1 BUGS
329
330 Test::Harness uses $^X to determine the perl binary to run the tests
331 with. Test scripts running via the shebang (C<#!>) line may not be
332 portable because $^X is not consistent for shebang scripts across
333 platforms. This is no problem when Test::Harness is run with an
334 absolute path to the perl binary or when $^X can be found in the path.
335
336 =cut