File Coverage

support/Test/Harness.pm
Criterion Covered Total %
statement 157 329 47.7
branch 46 142 32.3
condition 10 36 27.7
subroutine 24 31 77.4
pod 2 9 22.2
total 239 547 43.6


line stmt bran cond sub pod time code
1             # -*- Mode: cperl; cperl-indent-level: 4 -*-
2              
3             package Test::Harness;
4              
5             require 5.00405;
6 1     1   422090 use Test::Harness::Straps;
  1         2  
  1         36  
7 1     1   6 use Test::Harness::Assert;
  1         2  
  1         49  
8 1     1   5 use Exporter;
  1         2  
  1         40  
9 1     1   563 use Benchmark;
  1         6709  
  1         5  
10 1     1   128 use Config;
  1         2  
  1         34  
11 1     1   5 use strict;
  1         2  
  1         32  
12              
13              
14 1         137 use vars qw(
15             $VERSION
16             @ISA @EXPORT @EXPORT_OK
17             $Verbose $Switches $Debug
18             $verbose $switches $debug
19             $Columns
20             $Timer
21             $ML $Last_ML_Print
22             $Strap
23             $has_time_hires
24 1     1   4 );
  1         2  
25              
26             BEGIN {
27 1     1   70 eval "use Time::HiRes 'time'";
  1     1   6  
  1         2  
  1         4  
28 1         622 $has_time_hires = !$@;
29             }
30              
31             =head1 NAME
32              
33             Test::Harness - Run Perl standard test scripts with statistics
34              
35             =head1 VERSION
36              
37             Version 2.62
38              
39             =cut
40              
41             $VERSION = '2.62';
42              
43             # Backwards compatibility for exportable variable names.
44             *verbose = *Verbose;
45             *switches = *Switches;
46             *debug = *Debug;
47              
48             $ENV{HARNESS_ACTIVE} = 1;
49             $ENV{HARNESS_VERSION} = $VERSION;
50              
51             END {
52             # For VMS.
53 1     1   17 delete $ENV{HARNESS_ACTIVE};
54 1         0 delete $ENV{HARNESS_VERSION};
55             }
56              
57             my $Files_In_Dir = $ENV{HARNESS_FILELEAK_IN_DIR};
58              
59             $Strap = Test::Harness::Straps->new;
60              
61 0     0 0 0 sub strap { return $Strap };
62              
63             @ISA = ('Exporter');
64             @EXPORT = qw(&runtests);
65             @EXPORT_OK = qw(&execute_tests $verbose $switches);
66              
67             $Verbose = $ENV{HARNESS_VERBOSE} || 0;
68             $Debug = $ENV{HARNESS_DEBUG} || 0;
69             $Switches = "-w";
70             $Columns = $ENV{HARNESS_COLUMNS} || $ENV{COLUMNS} || 80;
71             $Columns--; # Some shells have trouble with a full line of text.
72             $Timer = $ENV{HARNESS_TIMER} || 0;
73              
74             =head1 SYNOPSIS
75              
76             use Test::Harness;
77              
78             runtests(@test_files);
79              
80             =head1 DESCRIPTION
81              
82             B If all you want to do is write a test script, consider
83             using Test::Simple. Test::Harness is the module that reads the
84             output from Test::Simple, Test::More and other modules based on
85             Test::Builder. You don't need to know about Test::Harness to use
86             those modules.
87              
88             Test::Harness runs tests and expects output from the test in a
89             certain format. That format is called TAP, the Test Anything
90             Protocol. It is defined in L.
91              
92             C runs all the testscripts named
93             as arguments and checks standard output for the expected strings
94             in TAP format.
95              
96             The F utility is a thin wrapper around Test::Harness.
97              
98             =head2 Taint mode
99              
100             Test::Harness will honor the C<-T> or C<-t> in the #! line on your
101             test files. So if you begin a test with:
102              
103             #!perl -T
104              
105             the test will be run with taint mode on.
106              
107             =head2 Configuration variables.
108              
109             These variables can be used to configure the behavior of
110             Test::Harness. They are exported on request.
111              
112             =over 4
113              
114             =item C<$Test::Harness::Verbose>
115              
116             The package variable C<$Test::Harness::Verbose> is exportable and can be
117             used to let C display the standard output of the script
118             without altering the behavior otherwise. The F utility's C<-v>
119             flag will set this.
120              
121             =item C<$Test::Harness::switches>
122              
123             The package variable C<$Test::Harness::switches> is exportable and can be
124             used to set perl command line options used for running the test
125             script(s). The default value is C<-w>. It overrides C.
126              
127             =item C<$Test::Harness::Timer>
128              
129             If set to true, and C is available, print elapsed seconds
130             after each test file.
131              
132             =back
133              
134              
135             =head2 Failure
136              
137             When tests fail, analyze the summary report:
138              
139             t/base..............ok
140             t/nonumbers.........ok
141             t/ok................ok
142             t/test-harness......ok
143             t/waterloo..........dubious
144             Test returned status 3 (wstat 768, 0x300)
145             DIED. FAILED tests 1, 3, 5, 7, 9, 11, 13, 15, 17, 19
146             Failed 10/20 tests, 50.00% okay
147             Failed Test Stat Wstat Total Fail List of Failed
148             ---------------------------------------------------------------
149             t/waterloo.t 3 768 20 10 1 3 5 7 9 11 13 15 17 19
150             Failed 1/5 test scripts, 80.00% okay. 10/44 subtests failed, 77.27% okay.
151              
152             Everything passed but F. It failed 10 of 20 tests and
153             exited with non-zero status indicating something dubious happened.
154              
155             The columns in the summary report mean:
156              
157             =over 4
158              
159             =item B
160              
161             The test file which failed.
162              
163             =item B
164              
165             If the test exited with non-zero, this is its exit status.
166              
167             =item B
168              
169             The wait status of the test.
170              
171             =item B
172              
173             Total number of tests expected to run.
174              
175             =item B
176              
177             Number which failed, either from "not ok" or because they never ran.
178              
179             =item B
180              
181             A list of the tests which failed. Successive failures may be
182             abbreviated (ie. 15-20 to indicate that tests 15, 16, 17, 18, 19 and
183             20 failed).
184              
185             =back
186              
187              
188             =head1 FUNCTIONS
189              
190             The following functions are available.
191              
192             =head2 runtests( @test_files )
193              
194             This runs all the given I<@test_files> and divines whether they passed
195             or failed based on their output to STDOUT (details above). It prints
196             out each individual test which failed along with a summary report and
197             a how long it all took.
198              
199             It returns true if everything was ok. Otherwise it will C with
200             one of the messages in the DIAGNOSTICS section.
201              
202             =cut
203              
204             sub runtests {
205 1     1 1 11455 my(@tests) = @_;
206              
207 1         6 local ($\, $,);
208              
209 1         6 my ($tot, $failedtests,$todo_passed) = execute_tests(tests => \@tests);
210 1         192 print get_results($tot, $failedtests,$todo_passed);
211              
212 1         7 my $ok = _all_ok($tot);
213              
214 1   25     19 assert(($ok xor keys %$failedtests),
215             q{ok status jives with $failedtests});
216              
217 1 50       3 if (! $ok) {
218 0         0 die("Failed $tot->{bad}/$tot->{tests} test programs. " .
219 0         0 "@{[$tot->{max} - $tot->{ok}]}/$tot->{max} subtests failed.\n");
220             }
221              
222 1         233 return $ok;
223             }
224              
225             # my $ok = _all_ok(\%tot);
226             # Tells you if this test run is overall successful or not.
227              
228             sub _all_ok {
229 2     2   4 my($tot) = shift;
230              
231 2 50 33     25 return $tot->{bad} == 0 && ($tot->{max} || $tot->{skipped}) ? 1 : 0;
232             }
233              
234             # Returns all the files in a directory. This is shorthand for backwards
235             # compatibility on systems where C doesn't work right.
236              
237             sub _globdir {
238 0     0   0 local *DIRH;
239              
240 0         0 opendir DIRH, shift;
241 0         0 my @f = readdir DIRH;
242 0         0 closedir DIRH;
243              
244 0         0 return @f;
245             }
246              
247             =head2 execute_tests( tests => \@test_files, out => \*FH )
248              
249             Runs all the given C<@test_files> (just like C) but
250             doesn't generate the final report. During testing, progress
251             information will be written to the currently selected output
252             filehandle (usually C), or to the filehandle given by the
253             C parameter. The I is optional.
254              
255             Returns a list of two values, C<$total> and C<$failed>, describing the
256             results. C<$total> is a hash ref summary of all the tests run. Its
257             keys and values are this:
258              
259             bonus Number of individual todo tests unexpectedly passed
260             max Number of individual tests ran
261             ok Number of individual tests passed
262             sub_skipped Number of individual tests skipped
263             todo Number of individual todo tests
264              
265             files Number of test files ran
266             good Number of test files passed
267             bad Number of test files failed
268             tests Number of test files originally given
269             skipped Number of test files skipped
270              
271             If C<< $total->{bad} == 0 >> and C<< $total->{max} > 0 >>, you've
272             got a successful test.
273              
274             C<$failed> is a hash ref of all the test scripts that failed. Each key
275             is the name of a test script, each value is another hash representing
276             how that script failed. Its keys are these:
277              
278             name Name of the test which failed
279             estat Script's exit value
280             wstat Script's wait status
281             max Number of individual tests
282             failed Number which failed
283             canon List of tests which failed (as string).
284              
285             C<$failed> should be empty if everything passed.
286              
287             =cut
288              
289             sub execute_tests {
290 1     1 1 4 my %args = @_;
291 1         2 my @tests = @{$args{tests}};
  1         7  
292 1   33     10 my $out = $args{out} || select();
293              
294             # We allow filehandles that are symbolic refs
295 1     1   7 no strict 'refs';
  1         2  
  1         3724  
296 1         4 _autoflush($out);
297 1         3 _autoflush(\*STDERR);
298              
299 1         2 my %failedtests;
300             my %todo_passed;
301              
302             # Test-wide totals.
303 1         11 my(%tot) = (
304             bonus => 0,
305             max => 0,
306             ok => 0,
307             files => 0,
308             bad => 0,
309             good => 0,
310             tests => scalar @tests,
311             sub_skipped => 0,
312             todo => 0,
313             skipped => 0,
314             bench => 0,
315             );
316              
317 1         2 my @dir_files;
318 1 50       6 @dir_files = _globdir $Files_In_Dir if defined $Files_In_Dir;
319 1         8 my $run_start_time = Benchmark->new;
320              
321 1         52 my $width = _leader_width(@tests);
322 1         2 foreach my $tfile (@tests) {
323 58         193 $Last_ML_Print = 0; # so each test prints at least once
324 58         600 my($leader, $ml) = _mk_leader($tfile, $width);
325 58         150 local $ML = $ml;
326              
327 58         1511 print $out $leader;
328              
329 58         328 $tot{files}++;
330              
331 58         157 $Strap->{_seen_header} = 0;
332 58 50       176 if ( $Test::Harness::Debug ) {
333 0         0 print $out "# Running: ", $Strap->_command_line($tfile), "\n";
334             }
335 58 50       203 my $test_start_time = $Timer ? time : 0;
336             my %results = $Strap->analyze_file($tfile) or
337 58 50       866 do { warn $Strap->{error}, "\n"; next };
  0         0  
  0         0  
338 58         241 my $elapsed;
339 58 50       232 if ( $Timer ) {
340 0         0 $elapsed = time - $test_start_time;
341 0 0       0 if ( $has_time_hires ) {
342 0         0 $elapsed = sprintf( " %8d ms", $elapsed*1000 );
343             }
344             else {
345 0 0       0 $elapsed = sprintf( " %8s s", $elapsed ? $elapsed : "<1" );
346             }
347             }
348             else {
349 58         399 $elapsed = "";
350             }
351              
352             # state of the current test.
353 48543         74031 my @failed = grep { !$results{details}[$_-1]{ok} }
354 58         230 1..@{$results{details}};
  58         3936  
355             my @todo_pass = grep { $results{details}[$_-1]{actual_ok} &&
356 48543 50       127565 $results{details}[$_-1]{type} eq 'todo' }
357 58         953 1..@{$results{details}};
  58         626  
358              
359             my %test = (
360             ok => $results{ok},
361             'next' => $Strap->{'next'},
362             max => $results{max},
363             failed => \@failed,
364             todo_pass => \@todo_pass,
365             todo => $results{todo},
366             bonus => $results{bonus},
367             skipped => $results{skip},
368             skip_reason => $results{skip_reason},
369             skip_all => $Strap->{skip_all},
370 58         3103 ml => $ml,
371             );
372              
373 58         480 $tot{bonus} += $results{bonus};
374 58         401 $tot{max} += $results{max};
375 58         191 $tot{ok} += $results{ok};
376 58         99 $tot{todo} += $results{todo};
377 58         100 $tot{sub_skipped} += $results{skip};
378              
379 58         477 my($estatus, $wstatus) = @results{qw(exit wait)};
380              
381 58 50       295 if ($results{passing}) {
382             # XXX Combine these first two
383 58 100 100     789 if ($test{max} and $test{skipped} + $test{bonus}) {
    100 33        
    50          
384 7         21 my @msg;
385             push(@msg, "$test{skipped}/$test{max} skipped: $test{skip_reason}")
386 7 50       92 if $test{skipped};
387 7 50       30 if ($test{bonus}) {
388             my ($txt, $canon) = _canondetail($test{todo},0,'TODO passed',
389 0         0 @{$test{todo_pass}});
  0         0  
390             $todo_passed{$tfile} = {
391             canon => $canon,
392             max => $test{todo},
393             failed => $test{bonus},
394 0         0 name => $tfile,
395             estat => '',
396             wstat => '',
397             };
398              
399 0         0 push(@msg, "$test{bonus}/$test{max} unexpectedly succeeded\n$txt");
400             }
401 7         3154 print $out "$test{ml}ok$elapsed\n ".join(', ', @msg)."\n";
402             }
403             elsif ( $test{max} ) {
404 50         3446 print $out "$test{ml}ok$elapsed\n";
405             }
406             elsif ( defined $test{skip_all} and length $test{skip_all} ) {
407 1         77 print $out "skipped\n all skipped: $test{skip_all}\n";
408 1         13 $tot{skipped}++;
409             }
410             else {
411 0         0 print $out "skipped\n all skipped: no reason given\n";
412 0         0 $tot{skipped}++;
413             }
414 58         247 $tot{good}++;
415             }
416             else {
417             # List unrun tests as failures.
418 0 0       0 if ($test{'next'} <= $test{max}) {
419 0         0 push @{$test{failed}}, $test{'next'}..$test{max};
  0         0  
420             }
421             # List overruns as failures.
422             else {
423 0         0 my $details = $results{details};
424 0         0 foreach my $overrun ($test{max}+1..@$details) {
425 0 0       0 next unless ref $details->[$overrun-1];
426 0         0 push @{$test{failed}}, $overrun
  0         0  
427             }
428             }
429              
430 0 0       0 if ($wstatus) {
    0          
431 0         0 $failedtests{$tfile} = _dubious_return(\%test, \%tot,
432             $estatus, $wstatus);
433 0         0 $failedtests{$tfile}{name} = $tfile;
434             }
435             elsif($results{seen}) {
436 0 0 0     0 if (@{$test{failed}} and $test{max}) {
  0         0  
437             my ($txt, $canon) = _canondetail($test{max},$test{skipped},'Failed',
438 0         0 @{$test{failed}});
  0         0  
439 0         0 print $out "$test{ml}$txt";
440             $failedtests{$tfile} = { canon => $canon,
441             max => $test{max},
442 0         0 failed => scalar @{$test{failed}},
  0         0  
443             name => $tfile,
444             estat => '',
445             wstat => '',
446             };
447             }
448             else {
449 0         0 print $out "Don't know which tests failed: got $test{ok} ok, ".
450             "expected $test{max}\n";
451             $failedtests{$tfile} = { canon => '??',
452             max => $test{max},
453 0         0 failed => '??',
454             name => $tfile,
455             estat => '',
456             wstat => '',
457             };
458             }
459 0         0 $tot{bad}++;
460             }
461             else {
462 0         0 print $out "FAILED before any test output arrived\n";
463 0         0 $tot{bad}++;
464 0         0 $failedtests{$tfile} = { canon => '??',
465             max => '??',
466             failed => '??',
467             name => $tfile,
468             estat => '',
469             wstat => '',
470             };
471             }
472             }
473              
474 58 50       1309 if (defined $Files_In_Dir) {
475 0         0 my @new_dir_files = _globdir $Files_In_Dir;
476 0 0       0 if (@new_dir_files != @dir_files) {
477 0         0 my %f;
478 0         0 @f{@new_dir_files} = (1) x @new_dir_files;
479 0         0 delete @f{@dir_files};
480 0         0 my @f = sort keys %f;
481 0         0 print $out "LEAKED FILES: @f\n";
482 0         0 @dir_files = @new_dir_files;
483             }
484             }
485             } # foreach test
486 1         53 $tot{bench} = timediff(Benchmark->new, $run_start_time);
487              
488 1         144 $Strap->_restore_PERL5LIB;
489              
490 1         61 return(\%tot, \%failedtests, \%todo_passed);
491             }
492              
493             # Turns on autoflush for the handle passed
494             sub _autoflush {
495 2     2   4 my $flushy_fh = shift;
496 2         6 my $old_fh = select $flushy_fh;
497 2         6 $| = 1;
498 2         5 select $old_fh;
499             }
500              
501             =for private _mk_leader
502              
503             my($leader, $ml) = _mk_leader($test_file, $width);
504              
505             Generates the 't/foo........' leader for the given C<$test_file> as well
506             as a similar version which will overwrite the current line (by use of
507             \r and such). C<$ml> may be empty if Test::Harness doesn't think you're
508             on TTY.
509              
510             The C<$width> is the width of the "yada/blah.." string.
511              
512             =cut
513              
514             sub _mk_leader {
515 58     58   387 my($te, $width) = @_;
516 58         228 chomp($te);
517 58         1318 $te =~ s/\.\w+$/./;
518              
519 58 50       955 if ($^O eq 'VMS') {
520 0         0 $te =~ s/^.*\.t\./\[.t./s;
521             }
522 58         391 my $leader = "$te" . '.' x ($width - length($te));
523 58         155 my $ml = "";
524              
525 58 0 33     618 if ( -t STDOUT and not $ENV{HARNESS_NOTTY} and not $Verbose ) {
      33        
526 0         0 $ml = "\r" . (' ' x 77) . "\r$leader"
527             }
528              
529 58         322 return($leader, $ml);
530             }
531              
532             =for private _leader_width
533              
534             my($width) = _leader_width(@test_files);
535              
536             Calculates how wide the leader should be based on the length of the
537             longest test name.
538              
539             =cut
540              
541             sub _leader_width {
542 1     1   2 my $maxlen = 0;
543 1         2 my $maxsuflen = 0;
544 1         3 foreach (@_) {
545 58 50       148 my $suf = /\.(\w+)$/ ? $1 : '';
546 58         67 my $len = length;
547 58         52 my $suflen = length $suf;
548 58 100       75 $maxlen = $len if $len > $maxlen;
549 58 100       89 $maxsuflen = $suflen if $suflen > $maxsuflen;
550             }
551             # + 3 : we want three dots between the test name and the "ok"
552 1         3 return $maxlen + 3 - $maxsuflen;
553             }
554              
555             sub get_results {
556 1     1 0 9 my $tot = shift;
557 1         3 my $failedtests = shift;
558 1         8 my $todo_passed = shift;
559              
560 1         8 my $out = '';
561              
562 1         17 my $bonusmsg = _bonusmsg($tot);
563              
564 1 50       6 if (_all_ok($tot)) {
    0          
    0          
565 1         6 $out .= "All tests successful$bonusmsg.\n";
566 1 50       4 if ($tot->{bonus}) {
567 0         0 my($fmt_top, $fmt) = _create_fmts("Passed TODO",$todo_passed);
568             # Now write to formats
569 0         0 $out .= swrite( $fmt_top );
570 0 0       0 for my $script (sort keys %{$todo_passed||{}}) {
  0         0  
571 0         0 my $Curtest = $todo_passed->{$script};
572 0         0 $out .= swrite( $fmt, @{ $Curtest }{qw(name estat wstat max failed canon)} );
  0         0  
573             }
574             }
575             }
576             elsif (!$tot->{tests}){
577 0         0 die "FAILED--no tests were run for some reason.\n";
578             }
579             elsif (!$tot->{max}) {
580 0 0       0 my $blurb = $tot->{tests}==1 ? "script" : "scripts";
581 0         0 die "FAILED--$tot->{tests} test $blurb could be run, ".
582             "alas--no output ever seen\n";
583             }
584             else {
585             my $subresults = sprintf( " %d/%d subtests failed.",
586 0         0 $tot->{max} - $tot->{ok}, $tot->{max} );
587              
588 0         0 my($fmt_top, $fmt1, $fmt2) = _create_fmts("Failed Test",$failedtests);
589              
590             # Now write to formats
591 0         0 $out .= swrite( $fmt_top );
592 0         0 for my $script (sort keys %$failedtests) {
593 0         0 my $Curtest = $failedtests->{$script};
594 0         0 $out .= swrite( $fmt1, @{ $Curtest }{qw(name estat wstat max failed canon)} );
  0         0  
595 0         0 $out .= swrite( $fmt2, $Curtest->{canon} );
596             }
597 0 0       0 if ($tot->{bad}) {
598 0         0 $bonusmsg =~ s/^,\s*//;
599 0 0       0 $out .= "$bonusmsg.\n" if $bonusmsg;
600 0         0 $out .= "Failed $tot->{bad}/$tot->{tests} test scripts.$subresults\n";
601             }
602             }
603              
604             $out .= sprintf("Files=%d, Tests=%d, %s\n",
605 1         7 $tot->{files}, $tot->{max}, timestr($tot->{bench}, 'nop'));
606 1         182 return $out;
607             }
608              
609             sub swrite {
610 0     0 0 0 my $format = shift;
611 0         0 $^A = '';
612 0         0 formline($format,@_);
613 0         0 my $out = $^A;
614 0         0 $^A = '';
615 0         0 return $out;
616             }
617              
618              
619             my %Handlers = (
620             header => \&header_handler,
621             test => \&test_handler,
622             bailout => \&bailout_handler,
623             );
624              
625             $Strap->{callback} = \&strap_callback;
626             sub strap_callback {
627 64823     64823 0 102325 my($self, $line, $type, $totals) = @_;
628 64823 50       99461 print $line if $Verbose;
629              
630 64823         94440 my $meth = $Handlers{$type};
631 64823 100       129486 $meth->($self, $line, $type, $totals) if $meth;
632             };
633              
634              
635             sub header_handler {
636 58     58 0 294 my($self, $line, $type, $totals) = @_;
637              
638 58 50       203 warn "Test header seen more than once!\n" if $self->{_seen_header};
639              
640 58         308 $self->{_seen_header}++;
641              
642             warn "1..M can only appear at the beginning or end of tests\n"
643             if $totals->{seen} &&
644 58 50 33     357 $totals->{max} < $totals->{seen};
645             };
646              
647             sub test_handler {
648 48543     48543 0 67905 my($self, $line, $type, $totals) = @_;
649              
650 48543         65924 my $curr = $totals->{seen};
651 48543         63967 my $next = $self->{'next'};
652 48543         55807 my $max = $totals->{max};
653 48543         59046 my $detail = $totals->{details}[-1];
654              
655 48543 50       74108 if( $detail->{ok} ) {
656 48543         144354 _print_ml_less("ok $curr/$max");
657              
658 48543 100       101783 if( $detail->{type} eq 'skip' ) {
659             $totals->{skip_reason} = $detail->{reason}
660 560 100       1208 unless defined $totals->{skip_reason};
661             $totals->{skip_reason} = 'various reasons'
662 560 50       1208 if $totals->{skip_reason} ne $detail->{reason};
663             }
664             }
665             else {
666 0         0 _print_ml("NOK $curr");
667             }
668              
669 48543 50       136578 if( $curr > $next ) {
    50          
670 0         0 print "Test output counter mismatch [test $curr]\n";
671             }
672             elsif( $curr < $next ) {
673 0         0 print "Confused test output: test $curr answered after ".
674             "test ", $next - 1, "\n";
675             }
676              
677             };
678              
679             sub bailout_handler {
680 0     0 0 0 my($self, $line, $type, $totals) = @_;
681              
682             die "FAILED--Further testing stopped" .
683 0 0       0 ($self->{bailout_reason} ? ": $self->{bailout_reason}\n" : ".\n");
684             };
685              
686              
687             sub _print_ml {
688 119 50   119   443 print join '', $ML, @_ if $ML;
689             }
690              
691              
692             # Print updates only once per second.
693             sub _print_ml_less {
694 48543     48543   58826 my $now = CORE::time;
695 48543 100       87081 if ( $Last_ML_Print != $now ) {
696 119         585 _print_ml(@_);
697 119         461 $Last_ML_Print = $now;
698             }
699             }
700              
701             sub _bonusmsg {
702 1     1   8 my($tot) = @_;
703              
704 1         22 my $bonusmsg = '';
705             $bonusmsg = (" ($tot->{bonus} subtest".($tot->{bonus} > 1 ? 's' : '').
706             " UNEXPECTEDLY SUCCEEDED)")
707 1 0       7 if $tot->{bonus};
    50          
708              
709 1 50       11 if ($tot->{skipped}) {
    0          
710             $bonusmsg .= ", $tot->{skipped} test"
711 1 50       23 . ($tot->{skipped} != 1 ? 's' : '');
712 1 50       10 if ($tot->{sub_skipped}) {
713             $bonusmsg .= " and $tot->{sub_skipped} subtest"
714 1 50       8 . ($tot->{sub_skipped} != 1 ? 's' : '');
715             }
716 1         8 $bonusmsg .= ' skipped';
717             }
718             elsif ($tot->{sub_skipped}) {
719             $bonusmsg .= ", $tot->{sub_skipped} subtest"
720 0 0       0 . ($tot->{sub_skipped} != 1 ? 's' : '')
721             . " skipped";
722             }
723 1         4 return $bonusmsg;
724             }
725              
726             # Test program go boom.
727             sub _dubious_return {
728 0     0     my($test, $tot, $estatus, $wstatus) = @_;
729              
730 0           my $failed = '??';
731 0           my $canon = '??';
732              
733 0           printf "$test->{ml}dubious\n\tTest returned status $estatus ".
734             "(wstat %d, 0x%x)\n",
735             $wstatus,$wstatus;
736 0 0         print "\t\t(VMS status is $estatus)\n" if $^O eq 'VMS';
737              
738 0           $tot->{bad}++;
739              
740 0 0         if ($test->{max}) {
741 0 0 0       if ($test->{'next'} == $test->{max} + 1 and not @{$test->{failed}}) {
  0            
742 0           print "\tafter all the subtests completed successfully\n";
743 0           $failed = 0; # But we do not set $canon!
744             }
745             else {
746 0           push @{$test->{failed}}, $test->{'next'}..$test->{max};
  0            
747 0           $failed = @{$test->{failed}};
  0            
748 0           (my $txt, $canon) = _canondetail($test->{max},$test->{skipped},'Failed',@{$test->{failed}});
  0            
749 0           print "DIED. ",$txt;
750             }
751             }
752              
753 0   0       return { canon => $canon, max => $test->{max} || '??',
754             failed => $failed,
755             estat => $estatus, wstat => $wstatus,
756             };
757             }
758              
759              
760             sub _create_fmts {
761 0     0     my $failed_str = shift;
762 0           my $failedtests = shift;
763              
764 0           my ($type) = split /\s/,$failed_str;
765 0           my $short = substr($type,0,4);
766 0 0         my $total = $short eq 'Pass' ? 'TODOs' : 'Total';
767 0           my $middle_str = " Stat Wstat $total $short ";
768 0           my $list_str = "List of $type";
769              
770             # Figure out our longest name string for formatting purposes.
771 0           my $max_namelen = length($failed_str);
772 0           foreach my $script (keys %$failedtests) {
773 0           my $namelen = length $failedtests->{$script}->{name};
774 0 0         $max_namelen = $namelen if $namelen > $max_namelen;
775             }
776              
777 0           my $list_len = $Columns - length($middle_str) - $max_namelen;
778 0 0         if ($list_len < length($list_str)) {
779 0           $list_len = length($list_str);
780 0           $max_namelen = $Columns - length($middle_str) - $list_len;
781 0 0         if ($max_namelen < length($failed_str)) {
782 0           $max_namelen = length($failed_str);
783 0           $Columns = $max_namelen + length($middle_str) + $list_len;
784             }
785             }
786              
787 0           my $fmt_top = sprintf("%-${max_namelen}s", $failed_str)
788             . $middle_str
789             . $list_str . "\n"
790             . "-" x $Columns
791             . "\n";
792              
793 0           my $fmt1 = "@" . "<" x ($max_namelen - 1)
794             . " @>> @>>>> @>>>> @>>> "
795             . "^" . "<" x ($list_len - 1) . "\n";
796 0           my $fmt2 = "~~" . " " x ($Columns - $list_len - 2) . "^"
797             . "<" x ($list_len - 1) . "\n";
798              
799 0           return($fmt_top, $fmt1, $fmt2);
800             }
801              
802             sub _canondetail {
803 0     0     my $max = shift;
804 0           my $skipped = shift;
805 0           my $type = shift;
806 0           my @detail = @_;
807 0           my %seen;
808 0           @detail = sort {$a <=> $b} grep !$seen{$_}++, @detail;
  0            
809 0           my $detail = @detail;
810 0           my @result = ();
811 0           my @canon = ();
812 0           my $min;
813 0           my $last = $min = shift @detail;
814 0           my $canon;
815 0           my $uc_type = uc($type);
816 0 0         if (@detail) {
817 0           for (@detail, $detail[-1]) { # don't forget the last one
818 0 0 0       if ($_ > $last+1 || $_ == $last) {
819 0 0         push @canon, ($min == $last) ? $last : "$min-$last";
820 0           $min = $_;
821             }
822 0           $last = $_;
823             }
824 0           local $" = ", ";
825 0           push @result, "$uc_type tests @canon\n";
826 0           $canon = join ' ', @canon;
827             }
828             else {
829 0           push @result, "$uc_type test $last\n";
830 0           $canon = $last;
831             }
832              
833 0 0         return (join("", @result), $canon)
834             if $type=~/todo/i;
835 0           push @result, "\t$type $detail/$max tests, ";
836 0 0         if ($max) {
837 0           push @result, sprintf("%.2f",100*(1-$detail/$max)), "% okay";
838             }
839             else {
840 0           push @result, "?% okay";
841             }
842 0           my $ender = 's' x ($skipped > 1);
843 0 0         if ($skipped) {
844 0           my $good = $max - $detail - $skipped;
845 0           my $skipmsg = " (less $skipped skipped test$ender: $good okay, ";
846 0 0         if ($max) {
847 0           my $goodper = sprintf("%.2f",100*($good/$max));
848 0           $skipmsg .= "$goodper%)";
849             }
850             else {
851 0           $skipmsg .= "?%)";
852             }
853 0           push @result, $skipmsg;
854             }
855 0           push @result, "\n";
856 0           my $txt = join "", @result;
857 0           return ($txt, $canon);
858             }
859              
860             1;
861             __END__