387c40c128ce65d344b63be29f4523590d80450f
[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
173 sub corestatus {
174     my($st) = @_;
175     my($ret);
176
177     eval {require 'wait.ph'};
178     if ($@) {
179       SWITCH: {
180             $ret = ($st & 0200); # Tim says, this is for 90%
181         }
182     } else {
183         $ret = WCOREDUMP($st);
184     }
185
186     eval {require Devel::CoreStack};
187     $have_devel_corestack++ unless $@;
188
189     $ret;
190 }
191
192 sub canonfailed ($@) {
193     my($max,@failed) = @_;
194     my %seen;
195     @failed = sort {$a <=> $b} grep !$seen{$_}++, @failed;
196     my $failed = @failed;
197     my @result = ();
198     my @canon = ();
199     my $min;
200     my $last = $min = shift @failed;
201     my $canon;
202     if (@failed) {
203         for (@failed, $failed[-1]) { # don't forget the last one
204             if ($_ > $last+1 || $_ == $last) {
205                 if ($min == $last) {
206                     push @canon, $last;
207                 } else {
208                     push @canon, "$min-$last";
209                 }
210                 $min = $_;
211             }
212             $last = $_;
213         }
214         local $" = ", ";
215         push @result, "FAILED tests @canon\n";
216         $canon = "@canon";
217     } else {
218         push @result, "FAILED test $last\n";
219         $canon = $last;
220     }
221
222     push @result, "\tFailed $failed/$max tests, ";
223     push @result, sprintf("%.2f",100*(1-$failed/$max)), "% okay\n";
224     my $txt = join "", @result;
225     ($txt, $canon);
226 }
227
228 1;
229 __END__
230
231 =head1 NAME
232
233 Test::Harness - run perl standard test scripts with statistics
234
235 =head1 SYNOPSIS
236
237 use Test::Harness;
238
239 runtests(@tests);
240
241 =head1 DESCRIPTION
242
243 Perl test scripts print to standard output C<"ok N"> for each single
244 test, where C<N> is an increasing sequence of integers. The first line
245 output by a standard test script is C<"1..M"> with C<M> being the
246 number of tests that should be run within the test
247 script. Test::Harness::runtests(@tests) runs all the testscripts
248 named as arguments and checks standard output for the expected
249 C<"ok N"> strings.
250
251 After all tests have been performed, runtests() prints some
252 performance statistics that are computed by the Benchmark module.
253
254 =head2 The test script output
255
256 Any output from the testscript to standard error is ignored and
257 bypassed, thus will be seen by the user. Lines written to standard
258 output containing C</^(not\s+)?ok\b/> are interpreted as feedback for
259 runtests().  All other lines are discarded.
260
261 It is tolerated if the test numbers after C<ok> are omitted. In this
262 case Test::Harness maintains temporarily its own counter until the
263 script supplies test numbers again. So the following test script
264
265     print <<END;
266     1..6
267     not ok
268     ok
269     not ok
270     ok
271     ok
272     END
273
274 will generate 
275
276     FAILED tests 1, 3, 6
277     Failed 3/6 tests, 50.00% okay
278
279 The global variable $Test::Harness::verbose is exportable and can be
280 used to let runtests() display the standard output of the script
281 without altering the behavior otherwise.
282
283 =head1 EXPORT
284
285 C<&runtests> is exported by Test::Harness per default.
286
287 =head1 DIAGNOSTICS
288
289 =over 4
290
291 =item C<All tests successful.\nFiles=%d,  Tests=%d, %s>
292
293 If all tests are successful some statistics about the performance are
294 printed.
295
296 =item C<FAILED tests %s\n\tFailed %d/%d tests, %.2f%% okay.>
297
298 For any single script that has failing subtests statistics like the
299 above are printed.
300
301 =item C<Test returned status %d (wstat %d)>
302
303 Scripts that return a non-zero exit status, both $?>>8 and $? are
304 printed in a message similar to the above.
305
306 =item C<Failed 1 test, %.2f%% okay. %s>
307
308 =item C<Failed %d/%d tests, %.2f%% okay. %s>
309
310 If not all tests were successful, the script dies with one of the
311 above messages.
312
313 =back
314
315 =head1 SEE ALSO
316
317 See L<Benchmark> for the underlying timing routines.
318
319 =head1 AUTHORS
320
321 Either Tim Bunce or Andreas Koenig, we don't know. What we know for
322 sure is, that it was inspired by Larry Wall's TEST script that came
323 with perl distributions for ages. Current maintainer is Andreas
324 Koenig.
325
326 =head1 BUGS
327
328 Test::Harness uses $^X to determine the perl binary to run the tests
329 with. Test scripts running via the shebang (C<#!>) line may not be
330 portable because $^X is not consistent for shebang scripts across
331 platforms. This is no problem when Test::Harness is run with an
332 absolute path to the perl binary or when $^X can be found in the path.
333
334 =cut