File Coverage

blib/lib/Test/Smoke/Reporter.pm
Criterion Covered Total %
statement 531 614 86.4
branch 197 278 70.8
condition 88 153 57.5
subroutine 44 46 95.6
pod 27 27 100.0
total 887 1118 79.3


line stmt bran cond sub pod time code
1             package Test::Smoke::Reporter;
2 6     6   212526 use warnings;
  6         31  
  6         216  
3 6     6   31 use strict;
  6         11  
  6         177  
4              
5 6     6   31 use vars qw( $VERSION );
  6         14  
  6         410  
6             $VERSION = '0.054';
7              
8             require File::Path;
9             require Test::Smoke;
10 6     6   44 use Cwd;
  6         30  
  6         391  
11 6     6   1662 use Encode qw( decode encode );
  6         46022  
  6         367  
12 6     6   924 use File::Spec::Functions;
  6         1792  
  6         482  
13 6     6   3003 use Test::Smoke::Util::LoadAJSON;
  6         17  
  6         40  
14 6     6   40 use POSIX qw( strftime );
  6         13  
  6         83  
15 6     6   12974 use System::Info;
  6         93062  
  6         349  
16 6         607 use Test::Smoke::Util qw(
17             grepccmsg grepnonfatal get_smoked_Config read_logfile
18             time_in_hhmm get_local_patches
19 6     6   1917 );
  6         20  
20 6     6   48 use Text::ParseWords;
  6         25  
  6         336  
21 6     6   38 use Test::Smoke::LogMixin;
  6         13  
  6         341  
22              
23 6     6   41 use constant USERNOTE_ON_TOP => 'top';
  6         10  
  6         55237  
24              
25             my %CONFIG = (
26             df_ddir => curdir(),
27             df_outfile => 'mktest.out',
28             df_rptfile => 'mktest.rpt',
29             df_jsnfile => 'mktest.jsn',
30             df_cfg => undef,
31             df_lfile => undef,
32             df_showcfg => 0,
33              
34             df_locale => undef,
35             df_defaultenv => undef,
36             df_perlio_only => undef,
37             df_is56x => undef,
38             df_skip_tests => undef,
39              
40             df_harnessonly => undef,
41             df_harness3opts => undef,
42              
43             df_v => 0,
44             df_hostname => undef,
45             df_from => '',
46             df_send_log => 'on_fail',
47             df_send_out => 'never',
48             df_user_note => '',
49             df_un_file => undef,
50             df_un_position => 'bottom', # != USERNOTE_ON_TOP for bottom
51             );
52              
53             =head1 NAME
54              
55             Test::Smoke::Reporter - OO interface for handling the testresults (mktest.out)
56              
57             =head1 SYNOPSIS
58              
59             use Test::Smoke;
60             use Test::Smoke::Reporter;
61              
62             my $reporter = Test::Smoke::Reporter->new( %args );
63             $reporter->write_to_file;
64             $reporter->transport( $url );
65              
66             =head1 DESCRIPTION
67              
68             Handle the parsing of the F file.
69              
70             =head1 METHODS
71              
72             =head2 Test::Smoke::Reporter->new( %args )
73              
74             [ Constructor | Public ]
75              
76             Initialise a new object.
77              
78             =cut
79              
80             sub new {
81 69     69 1 118277 my $proto = shift;
82 69 50       342 my $class = ref $proto ? ref $proto : $proto;
83              
84 69 50       1074 my %args_raw = @_ ? UNIVERSAL::isa( $_[0], 'HASH' ) ? %{ $_[0] } : @_ : ();
  0 50       0  
85              
86             my %args = map {
87 69         322 ( my $key = $_ ) =~ s/^-?(.+)$/lc $1/e;
  398         1943  
  398         1105  
88 398         1364 ( $key => $args_raw{ $_ } );
89             } keys %args_raw;
90              
91             my %fields = map {
92 1518 100       3207 my $value = exists $args{$_} ? $args{ $_ } : $CONFIG{ "df_$_" };
93 1518         2894 ( $_ => $value )
94 69         213 } keys %{ $class->config( 'all_defaults' ) };
  69         468  
95              
96 69         637 $fields{_conf_args} = { %args_raw };
97 69         233 my $self = bless \%fields, $class;
98 69         520 $self->read_parse( );
99             }
100              
101             =head2 $reporter->verbose()
102              
103             Accessor to the C attribute.
104              
105             =cut
106              
107             sub verbose {
108 1157     1157 1 1859 my $self = shift;
109              
110 1157 50       2255 $self->{v} = shift if @_;
111              
112 1157         4677 $self->{v};
113             }
114              
115             =head2 Test::Smoke::Reporter->config( $key[, $value] )
116              
117             [ Accessor | Public ]
118              
119             C is an interface to the package lexical C<%CONFIG>,
120             which holds all the default values for the C arguments.
121              
122             With the special key B this returns a reference
123             to a hash holding all the default values.
124              
125             =cut
126              
127             sub config {
128 69     69 1 169 my $dummy = shift;
129              
130 69         359 my $key = lc shift;
131              
132 69 50       253 if ( $key eq 'all_defaults' ) {
133             my %default = map {
134 69         2731 my( $pass_key ) = $_ =~ /^df_(.+)/;
  1518         4106  
135 1518         4415 ( $pass_key => $CONFIG{ $_ } );
136             } grep /^df_/ => keys %CONFIG;
137 69         936 return \%default;
138             }
139              
140 0 0       0 return undef unless exists $CONFIG{ "df_$key" };
141              
142 0 0       0 $CONFIG{ "df_$key" } = shift if @_;
143              
144 0         0 return $CONFIG{ "df_$key" };
145             }
146              
147             =head2 $self->read_parse( [$result_file] )
148              
149             C reads the smokeresults file and parses it.
150              
151             =cut
152              
153             sub read_parse {
154 116     116 1 24699 my $self = shift;
155              
156             my $result_file = @_ ? $_[0] : $self->{outfile}
157             ? catfile( $self->{ddir}, $self->{outfile} )
158 116 100       659 : "";
    100          
159 116         602 $self->log_debug("[%s::read_parse] found '%s'", ref($self), $result_file);
160              
161 116 100       288 if ( $result_file ) {
162 66         217 $self->_read( $result_file );
163 66         448 $self->_parse;
164             }
165 116         682 return $self;
166             }
167              
168             =head2 $self->_read( $nameorref )
169              
170             C<_read()> is a private method that handles the reading.
171              
172             =over 8
173              
174             =item B smokeresults are in C<$$nameorref>
175              
176             =item B smokeresults are in C<@$nameorref>
177              
178             =item B smokeresults are read from the filehandle
179              
180             =item B are taken as the filename for the smokeresults
181              
182             =back
183              
184             =cut
185              
186             sub _read {
187 66     66   177 my $self = shift;
188 66         162 my( $nameorref ) = @_;
189 66 50       152 $nameorref = '' unless defined $nameorref;
190              
191 66         305 my $vmsg = "";
192 66         467 local *SMOKERSLT;
193 66 100       360 if ( ref $nameorref eq 'SCALAR' ) {
    50          
    50          
194 50         126 $self->{_outfile} = $$nameorref;
195 50         85 $vmsg = "from internal content";
196             } elsif ( ref $nameorref eq 'ARRAY' ) {
197 0         0 $self->{_outfile} = join "", @$nameorref;
198 0         0 $vmsg = "from internal content";
199             } elsif ( ref $nameorref eq 'GLOB' ) {
200 0         0 *SMOKERSLT = *$nameorref;
201 0         0 $self->{_outfile} = do { local $/; };
  0         0  
  0         0  
202 0         0 $vmsg = "from anonymous filehandle";
203             } else {
204 16 50       60 if ( $nameorref ) {
205 16         97 $vmsg = "from $nameorref";
206 16         146 $self->{_outfile} = read_logfile($nameorref, $self->{v});
207 16 50       70 defined($self->{_outfile}) or do {
208 0         0 require Carp;
209 0         0 Carp::carp( "Cannot read smokeresults ($nameorref): $!" );
210 0         0 $vmsg = "did fail";
211             };
212             } else { # Allow intentional default_buildcfg()
213 0         0 $self->{_outfile} = undef;
214 0         0 $vmsg = "did fail";
215             }
216             }
217 66         277 $self->log_info("Reading smokeresult %s", $vmsg);
218             }
219              
220             =head2 $self->_parse( )
221              
222             Interpret the contents of the outfile and prepare them for processing,
223             so report can be made.
224              
225             =cut
226              
227             sub _parse {
228 66     66   160 my $self = shift;
229              
230 66         187 $self->{_rpt} = \my %rpt;
231 66         160 $self->{_cache} = {};
232 66         141 $self->{_mani} = [];
233 66         136 $self->{configs} = \my @new;
234 66 50       171 return $self unless defined $self->{_outfile};
235              
236 66         143 my ($cfgarg, $debug, $tstenv, $start, $statarg, $fcnt);
237 66         168 $rpt{count} = 0;
238             # reverse and use pop() instead of using unshift()
239 66         3072 my @lines = reverse split m/\n+/, $self->{_outfile};
240 66         396 my $previous = "";
241 66         139 my $previous_failed = "";
242              
243 66         256 while (defined (local $_ = pop @lines)) {
244 3439 100       8887 m/^\s*$/ and next;
245 3181 100       6008 m/^-+$/ and next;
246 3048         24578 s/\s*$//;
247              
248 3048 100       10269 if (my ($status, $time) = /(Started|Stopped) smoke at (\d+)/) {
249 572 100       1548 if ($status eq "Started") {
    100          
250 286         445 $start = $time;
251 286   66     1229 $rpt{started} ||= $time;
252             }
253             elsif (defined $start) {
254 283         727 my $elapsed = $time - $start;
255 283         713 $rpt{secs} += $elapsed;
256 283 100       796 @new and $new[-1]{duration} = $elapsed;
257             }
258 572         1589 next;
259             }
260              
261 2476 100       6046 if (my ($patch) = m/^ \s*
262             Smoking\ patch\s*
263             ((?:[0-9a-f]+\s+\S+)|(?:\d+\S*))
264             /x )
265             {
266 66         265 my ($pl, $descr) = split ' ', $patch;
267 66         179 $rpt{patchlevel} = $patch;
268 66   33     190 $rpt{patch} = $pl || $patch;
269 66   66     176 $rpt{patchdescr} = $descr || $pl;
270 66         218 next;
271             }
272 2410 100       4472 if (/^Smoking branch (\S+)/) {
273 3         25 $rpt{smokebranch} = $1;
274             }
275              
276 2410 100       4399 if (/^MANIFEST /) {
277 19         31 push @{$self->{_mani}}, $_;
  19         80  
278 19         75 next;
279             }
280              
281 2391 100       6133 if (s/^\s*Configuration:\s*//) {
282              
283             # You might need to do something here with
284             # the previous Configuration: $cfgarg
285 229 100       805 $rpt{statcfg}{$statarg} = $fcnt if defined $statarg;
286 229         345 $fcnt = 0;
287              
288 229         339 $rpt{count}++;
289 229         1239 s/-Dusedevel(\s+|$)//;
290 229         606 s/\s*-des//;
291 229         340 $statarg = $_;
292 229 100       1195 $debug = s/-D(DEBUGGING|usevmsdebug)\s*// ? "D" : "N";
293 229 100       719 $debug eq 'D' and $rpt{dbughow} = "-D$1";
294 229         839 s/\s+$//;
295              
296 229   100     612 $cfgarg = $_ || "";
297              
298 229         840 push(
299             @new,
300             {
301             arguments => $_,
302             debugging => $debug,
303             started => __posixdate($start),
304             results => [],
305             }
306             );
307 229 100       1770 push @{$rpt{cfglist}}, $_ unless $rpt{config}->{$cfgarg}++;
  139         450  
308 229         513 $tstenv = "";
309 229         638 $previous_failed = "";
310 229         907 next;
311             }
312              
313 2162 100       4245 if (my ($cinfo) = /^Compiler info: (.+)$/) {
314 149         488 $rpt{$cfgarg}->{cinfo} = $cinfo;
315 149   66     484 $rpt{cinfo} ||= $cinfo;
316 149         527 @{$new[-1]}{qw( cc ccversion )} = split m/ version / => $cinfo, 2;
  149         475  
317 149         468 next;
318             }
319              
320 2013 100 100     6032 if (m/(?:PERLIO|TSTENV)\s*=\s*([-\w:.]+)/
      100        
321             # skip this if it's from a build failure, since the
322             # Unable to build... pushed an M
323             && (!@{$new[-1]{results}}
324             || $new[-1]{results}[0]{summary} ne "M")) {
325 344         924 $tstenv = $1;
326 344         505 $previous_failed = "";
327 344   50     2278 $rpt{$cfgarg}->{summary}{$debug}{$tstenv} ||= "?";
328 344         976 my ($io_env, $locale) = split m/:/ => $tstenv,
329             2;
330             push(
331 344         552 @{$new[-1]{results}},
  344         1841  
332             {
333             io_env => $io_env,
334             locale => $locale,
335             summary => "?",
336             statistics => undef,
337             stat_tests => undef,
338             stat_cpu_time => undef,
339             failures => [],
340             }
341             );
342              
343             # Deal with harness output
344 344         2161 s/^(?:PERLIO|TSTENV)\s*=\s+[-\w:.]+(?: :crlf)?\s*//;
345             }
346              
347 2013 100       8358 if (m/\b(Files=[0-9]+,\s*Tests=([0-9]+),.*?=\s*([0-9.]+)\s*CPU)/) {
    100          
348 2         16 $new[-1]{results}[-1]{statistics} = $1;
349 2         8 $new[-1]{results}[-1]{stat_tests} = $2;
350 2         11 $new[-1]{results}[-1]{stat_cpu_time} = $3;
351             }
352             elsif (
353             m/\b(u=([0-9.]+)\s+
354             s=([0-9.]+)\s+
355             cu=([0-9.]+)\s+
356             cs=([0-9.]+)\s+
357             scripts=[0-9]+\s+
358             tests=([0-9]+))/xi
359             )
360             {
361 198         685 $new[-1]{results}[-1]{statistics} = $1;
362 198         490 $new[-1]{results}[-1]{stat_tests} = $6;
363 198         1396 $new[-1]{results}[-1]{stat_cpu_time} = $2 + $3 + $4 + $5;
364             }
365              
366 2013 100       4232 if (m/^\s*All tests successful/) {
367 149         440 $rpt{$cfgarg}->{summary}{$debug}{$tstenv} = "O";
368 149         273 $new[-1]{results}[-1]{summary} = "O";
369 149         460 next;
370             }
371              
372 1864 100       3760 if (m/Inconsistent test ?results/) {
373             ref $rpt{$cfgarg}->{$debug}{$tstenv}{failed}
374 39 100       306 or $rpt{$cfgarg}->{$debug}{$tstenv}{failed} = [];
375              
376 39 100 66     393 if (not $rpt{$cfgarg}->{summary}{$debug}{$tstenv}
377             or $rpt{$cfgarg}->{summary}{$debug}{$tstenv} ne "F")
378             {
379 24         117 $rpt{$cfgarg}->{summary}{$debug}{$tstenv} = "X";
380 24         71 $new[-1]{results}[-1]{summary} = "X";
381             }
382 39         84 push @{$rpt{$cfgarg}->{$debug}{$tstenv}{failed}}, $_;
  39         174  
383 39         176 while (m/^ \s* (\S+?) \s* \.+(?:\s+\.+)* \s* (\w.*?) \s*$/xgm) {
384 0         0 my ($_test, $_info) = ($1, $2);
385              
386             push(
387 0 0 0     0 @{$new[-1]{results}[-1]{failures}},
  0 0       0  
388             $_info =~ m/^ \w+ $/x
389             ? {
390             test => $_test,
391             status => $_info,
392             extra => []
393             }
394             : # TEST output from minitest
395             $_info =~ m/^ (\w+) \s+at\ test\s+ (\d+) \s* $/x
396             || $_info =~ m/^ (\w+)--(\S.*\S) \s* $/x
397             ? {
398             test => $_test,
399             status => $1,
400             extra => [ $2 ]
401             }
402             : {
403             test => "?",
404             status => "?",
405             extra => []
406             }
407             );
408             }
409             }
410              
411 1864 100       3608 if (/^Finished smoking [\dA-Fa-f]+/) {
412 63         292 $rpt{statcfg}{$statarg} = $fcnt;
413 63         194 $rpt{finished} = "Finished";
414 63         179 next;
415             }
416              
417 1801 100       4094 if (my ($status, $mini) =
418             m/^ \s* Unable\ to
419             \ (?=([cbmt]))(?:build|configure|make|test)
420             \ (anything\ but\ mini)?perl/x
421             )
422             {
423 18 100       59 $mini and $status = uc $status; # M for no perl but miniperl
424             # $tstenv is only set *after* this
425 18 100 33     135 $tstenv ||= $mini ? "minitest" : "stdio";
426 18         77 $rpt{$cfgarg}->{summary}{$debug}{$tstenv} = $status;
427             push(
428 18         26 @{$new[-1]{results}},
  18         116  
429             {
430             io_env => $tstenv,
431             locale => undef,
432             summary => $status,
433             statistics => undef,
434             stat_tests => undef,
435             stat_cpu_time => undef,
436             failures => [],
437             }
438             );
439 18         35 $fcnt++;
440 18         46 next;
441             }
442              
443 1783 100 66     8740 if (m/FAILED/ || m/DIED/ || m/dubious$/ || m/\?\?\?\?\?\?$/) {
      100        
      100        
444             ref $rpt{$cfgarg}->{$debug}{$tstenv}{failed}
445 624 100       2711 or $rpt{$cfgarg}->{$debug}{$tstenv}{failed} = [];
446              
447 624 100       1315 if ($previous_failed ne $_) {
448 612 100 66     3185 if (not $rpt{$cfgarg}->{summary}{$debug}{$tstenv}
449             or $rpt{$cfgarg}->{summary}{$debug}{$tstenv} !~ m/[XM]/)
450             {
451 582         1236 $rpt{$cfgarg}->{summary}{$debug}{$tstenv} = "F";
452 582         997 $new[-1]{results}[-1]{summary} = "F";
453             }
454 612         818 push @{$rpt{$cfgarg}->{$debug}{$tstenv}{failed}}, $_;
  612         1720  
455             push(
456 612 50       865 @{$new[-1]{results}[-1]{failures}},
  612         6808  
457             m{^ \s* # leading space
458             ((?:\S+[/\\])? # Optional leading path to
459             \S(?:[^.]+|\.t)+) # test file name
460             [. ]+ # ....... ......
461             (\w.*?) # result
462             \s* $}x
463             ? {
464             test => $1,
465             status => $2,
466             extra => []
467             }
468             : {
469             test => "?",
470             status => "?",
471             extra => []
472             }
473             );
474              
475 612         1201 $fcnt++;
476             }
477 624         890 $previous_failed = $_;
478              
479 624         817 $previous = "failed";
480 624         1744 next;
481             }
482              
483 1159 100       2402 if (m/PASSED/) {
484             ref $rpt{$cfgarg}->{$debug}{$tstenv}{passed}
485 132 100       681 or $rpt{$cfgarg}->{$debug}{$tstenv}{passed} = [];
486              
487 132         257 push @{$rpt{$cfgarg}->{$debug}{$tstenv}{passed}}, $_;
  132         397  
488             push(
489 132 50       456 @{$new[-1]{results}[-1]{failures}},
  132         1312  
490             m/^ \s* (\S+?) \.+(?:\s+\.+)* (\w+) \s* $/x
491             ? {
492             test => $1,
493             status => $2,
494             extra => []
495             }
496             : {
497             test => "?",
498             status => "?",
499             extra => []
500             }
501             );
502 132         279 $previous = "passed";
503 132         378 next;
504             }
505              
506 1027         1549 my @captures = ();
507 1027 100       9048 if (@captures = $_ =~ m/
508             (?:^|,)\s+
509             (\d+(?:-\d+)?)
510             /gx) {
511 310 100       1042 if (ref $rpt{$cfgarg}->{$debug}{$tstenv}{$previous}) {
512 308         412 push @{$rpt{$cfgarg}->{$debug}{$tstenv}{$previous}}, $_;
  308         826  
513 308         453 push @{$new[-1]{results}[-1]{failures}[-1]{extra}}, @captures;
  308         1295  
514             }
515 310         1065 next;
516             }
517              
518 717 100       7897 if (/^\s+(?:Bad plan)|(?:No plan found)|^\s+(?:Non-zero exit status)/) {
519 42 50       252 if (ref $rpt{$cfgarg}->{$debug}{$tstenv}{failed}) {
520 42         107 push @{$rpt{$cfgarg}->{$debug}{$tstenv}{failed}}, $_;
  42         159  
521 42         164 s/^\s+//;
522 42         68 push @{$new[-1]{results}[-1]{failures}[-1]{extra}}, $_;
  42         193  
523             }
524 42         121 next;
525             }
526 675         1873 next;
527             }
528              
529 66         142 $rpt{last_cfg} = $statarg;
530 66 100       165 exists $rpt{statcfg}{$statarg} or $rpt{running} = $fcnt;
531 66 50       282 $rpt{avg} = $rpt{count} ? $rpt{secs} / $rpt{count} : 0;
532 66         177 $self->{_rpt} = \%rpt;
533 66         210 $self->_post_process;
534             }
535              
536             =head2 $self->_post_process( )
537              
538             C<_post_process()> sets up the report for easy printing. It needs to
539             sort the buildenvironments, statusletters and test failures.
540              
541             =cut
542              
543             sub _post_process {
544 66     66   121 my $self = shift;
545              
546 66 100       223 unless (defined $self->{is56x}) {
547 51         95 $self->{is56x} = 0;
548             # Overly defensive, as .out files might be analyzed outside of the
549             # original smoke environment
550 51 50 33     1255 if ($self->{ddir} && -d $self->{ddir}) {
551 51         357 my %cfg = get_smoked_Config($self->{ddir}, "version");
552 51 100       560 if ($cfg{version} =~ m/^\s* ([0-9]+) \. ([0-9]+) \. ([0-9]+) \s*$/x) {
553 49         386 my $p_version = sprintf "%d.%03d%03d", $1, $2, $3;
554 49         247 $self->{is56x} = $p_version < 5.007;
555             }
556             }
557             }
558 66   100     686 $self->{defaultenv} ||= $self->{is56x};
559              
560 66         118 my (%bldenv, %cfgargs);
561 66         112 my $rpt = $self->{_rpt};
562 66         114 foreach my $config (@{$rpt->{cfglist}}) {
  66         451  
563              
564 139         347 foreach my $buildenv (keys %{$rpt->{$config}{summary}{N}}) {
  139         625  
565 244         491 $bldenv{$buildenv}++;
566             }
567 139         233 foreach my $buildenv (keys %{$rpt->{$config}{summary}{D}}) {
  139         425  
568 118         209 $bldenv{$buildenv}++;
569             }
570 139         435 foreach my $ca (grep defined $_ => quotewords('\s+', 1, $config)) {
571 255         13242 $cfgargs{$ca}++;
572             }
573             }
574             my %common_args =
575 54         270 map { ($_ => 1) }
576 66   100     287 grep $cfgargs{$_} == @{$rpt->{cfglist}}
577             && !/^-[DU]use/ => keys %cfgargs;
578              
579 66         207 $rpt->{_common_args} = \%common_args;
580 66         329 $rpt->{common_args} = join " ", sort keys %common_args;
581 66   100     286 $rpt->{common_args} ||= 'none';
582              
583 66         342 $self->{_tstenv} = [reverse sort keys %bldenv];
584 66         488 my %count = (
585             O => 0,
586             F => 0,
587             X => 0,
588             M => 0,
589             m => 0,
590             c => 0,
591             o => 0,
592             t => 0
593             );
594 66         105 my (%failures, %order);
595 66         107 my $ord = 1;
596 66         83 my (%todo_passed, %order2);
597 66         99 my $ord2 = 1;
598 66   100     482 my $debugging = $rpt->{dbughow} || '-DDEBUGGING';
599              
600 66         139 foreach my $config (@{$rpt->{cfglist}}) {
  66         173  
601 139         226 foreach my $dbinfo (qw( N D )) {
602 278         470 my $cfg = $config;
603 278 100       805 ($cfg = $cfg ? "$debugging $cfg" : $debugging)
    100          
604             if $dbinfo eq "D";
605 278         927 $self->log_info("Processing [%s]", $cfg);
606 278         759 my $status = $self->{_rpt}{$config}{summary}{$dbinfo};
607 278         823 foreach my $tstenv (reverse sort keys %bldenv) {
608 524 100 100     1219 next if $tstenv eq 'minitest' && !exists $status->{$tstenv};
609              
610 512         1116 (my $showenv = $tstenv) =~ s/^locale://;
611 512 100       1043 if ($tstenv =~ /^locale:/) {
612             $self->{_locale_keys}{$showenv}++
613 54 100       198 or push @{$self->{_locale}}, $showenv;
  14         46  
614             }
615             $showenv = 'default'
616 512 100 66     1285 if $self->{defaultenv} && $showenv eq 'stdio';
617              
618 512   100     1747 $status->{$tstenv} ||= '-';
619              
620 512         960 my $status2 = $self->{_rpt}{$config}{$dbinfo};
621 512 100       1164 if (exists $status2->{$tstenv}{failed}) {
622 195         261 my $failed = join "\n", @{$status2->{$tstenv}{failed}};
  195         638  
623 195 100 66     686 if ( exists $failures{$failed}
      100        
624 119         712 && @{$failures{$failed}}
625             && $failures{$failed}->[-1]{cfg} eq $cfg)
626             {
627 62         105 push @{$failures{$failed}->[-1]{env}}, $showenv;
  62         207  
628             }
629             else {
630 133         187 push @{$failures{$failed}},
  133         862  
631             {
632             cfg => $cfg,
633             env => [$showenv]
634             };
635 133   66     885 $order{$failed} ||= $ord++;
636             }
637             }
638 512 100       1146 if (exists $status2->{$tstenv}{passed}) {
639 66         166 my $passed = join "\n", @{$status2->{$tstenv}{passed}};
  66         262  
640 66 100 66     337 if ( exists $todo_passed{$passed}
      100        
641 33         246 && @{$todo_passed{$passed}}
642             && $todo_passed{$passed}->[-1]{cfg} eq $cfg)
643             {
644 30         95 push @{$todo_passed{$passed}->[-1]{env}}, $showenv;
  30         108  
645             }
646             else {
647             push(
648 36         99 @{$todo_passed{$passed}},
  36         345  
649             {
650             cfg => $cfg,
651             env => [$showenv]
652             }
653             );
654 36   66     317 $order2{$passed} ||= $ord2++;
655             }
656              
657             }
658              
659 512         1589 $self->log_debug("\t[%s]: %s", $showenv, $status->{$tstenv});
660 512 100       1392 if ($tstenv eq 'minitest') {
661 12         28 $status->{stdio} = "M";
662 12         32 delete $status->{minitest};
663             }
664             }
665 278 100       621 unless ($self->{defaultenv}) {
666 168 50       347 exists $status->{perlio} or $status->{perlio} = '-';
667 168   100     849 my @locales = split ' ', ($self->{locale} || '');
668 168         569 for my $locale (@locales) {
669             exists $status->{"locale:$locale"}
670 18 50       59 or $status->{"locale:$locale"} = '-';
671             }
672             }
673              
674             $count{$_}++
675 278 50       1092 for map { m/[cmMtFXO]/ ? $_ : m/-/ ? 'O' : 'o' }
  500 100       2635  
676             map $status->{$_} => keys %$status;
677             }
678             }
679 66 100       305 defined $self->{_locale} or $self->{_locale} = [];
680              
681             my @failures = map {
682             {
683             tests => $_,
684             cfgs => [
685             map {
686 133         524 my $cfg_clean = __rm_common_args($_->{cfg}, \%common_args);
687 133         281 my $env = join "/", @{$_->{env}};
  133         366  
688 133         750 "[$env] $cfg_clean";
689 76         136 } @{$failures{$_}}
  76         196  
690             ],
691             }
692 66         342 } sort { $order{$a} <=> $order{$b} } keys %failures;
  57         227  
693 66         230 $self->{_failures} = \@failures;
694              
695             my @todo_passed = map {
696             {
697             tests => $_,
698             cfgs => [
699             map {
700 36         132 my $cfg_clean = __rm_common_args($_->{cfg}, \%common_args);
701 36         105 my $env = join "/", @{$_->{env}};
  36         298  
702 36         301 "[$env] $cfg_clean";
703 33         59 } @{$todo_passed{$_}}
  33         74  
704             ],
705             }
706 66         240 } sort { $order2{$a} <=> $order2{$b} } keys %todo_passed;
  7         42  
707 66         322 $self->{_todo_passed} = \@todo_passed;
708              
709 66         128 $self->{_counters} = \%count;
710              
711             # Need to rebuild the test-environments as minitest changes into stdio
712 66         198 my %bldenv2;
713 66         107 foreach my $config (@{$rpt->{cfglist}}) {
  66         210  
714 139         189 foreach my $buildenv (keys %{$rpt->{$config}{summary}{N}}) {
  139         472  
715 250         439 $bldenv2{$buildenv}++;
716             }
717 139         212 foreach my $buildenv (keys %{$rpt->{$config}{summary}{D}}) {
  139         349  
718 250         415 $bldenv2{$buildenv}++;
719             }
720             }
721 66         133 $self->{_tstenvraw} = $self->{_tstenv};
722 66         644 $self->{_tstenv} = [reverse sort keys %bldenv2];
723             }
724              
725             =head2 __posixdate($time)
726              
727             Returns C.
728              
729             =cut
730              
731             sub __posixdate {
732              
733             # Note that the format "%F %T %z" returns:
734             # Linux: 2012-04-02 10:57:58 +0200
735             # HP-UX: April 08:53:32 METDST
736             # ENOTPORTABLE! %F is C99 only!
737 229   33 229   573 my $stamp = shift || time;
738 229 50       12037 return $^O eq 'MSWin32'
739             ? POSIX::strftime("%Y-%m-%d %H:%M:%S Z", gmtime $stamp)
740             : POSIX::strftime("%Y-%m-%d %H:%M:%S %z", localtime $stamp);
741             }
742              
743             =head2 __rm_common_args( $cfg, \%common )
744              
745             Removes the the arguments stored as keys in C<%common> from C<$cfg>.
746              
747             =cut
748              
749             sub __rm_common_args {
750 169     169   386 my( $cfg, $common ) = @_;
751              
752 169         875 require Test::Smoke::BuildCFG;
753 169         1250 my $bcfg = Test::Smoke::BuildCFG::new_configuration( $cfg );
754              
755 169         579 return $bcfg->rm_arg( keys %$common );
756             }
757              
758             =head2 $reporter->get_logfile()
759              
760             Return the contents of C<< $self->{lfile} >> either by reading the file or
761             returning the cached version.
762              
763             =cut
764              
765             sub get_logfile {
766 0     0 1 0 my $self = shift;
767 0 0       0 return $self->{log_file} if $self->{log_file};
768              
769 0         0 return $self->{log_file} = read_logfile($self->{lfile}, $self->{v});
770             }
771              
772             =head2 $reporter->get_outfile()
773              
774             Return the contents of C<< $self->{outfile} >> either by reading the file or
775             returning the cached version.
776              
777             =cut
778              
779             sub get_outfile {
780 121     121 1 217 my $self = shift;
781 121 50       989 return $self->{_outfile} if $self->{_outfile};
782              
783 0         0 my $fq_outfile = catfile($self->{ddir}, $self->{outfile});
784 0         0 return $self->{_outfile} = read_logfile($fq_outfile, $self->{v});
785             }
786              
787             =head2 $reporter->write_to_file( [$name] )
788              
789             Write the C<< $self->report >> to file. If name is omitted it will
790             use C<< catfile( $self->{ddir}, $self->{rptfile} ) >>.
791              
792             =cut
793              
794             sub write_to_file {
795 1     1 1 1435 my $self = shift;
796 1 50       16 return unless defined $self->{_outfile};
797 1   33     17 my( $name ) = shift || ( catfile $self->{ddir}, $self->{rptfile} );
798              
799 1         12 $self->log_info("Writing report to '%s'", $name);
800 1         10 local *RPT;
801 1 50       69 open RPT, "> $name" or do {
802 0         0 require Carp;
803 0         0 Carp::carp( "Error creating '$name': $!" );
804 0         0 return;
805             };
806 1         14 print RPT $self->report;
807 1 50       57 close RPT or do {
808 0         0 require Carp;
809 0         0 Carp::carp( "Error writing to '$name': $!" );
810 0         0 return;
811             };
812 1         25 $self->log_info("'%s' written OK", $name);
813 1         16 return 1;
814             }
815              
816             =head2 $reporter->smokedb_data()
817              
818             Transport the report to the gateway. The transported data will also be stored
819             locally in the file mktest.jsn
820              
821             =cut
822              
823             sub smokedb_data {
824 0     0 1 0 my $self = shift;
825 0         0 $self->log_info("Gathering CoreSmokeDB information...");
826              
827 0         0 my %rpt = map { $_ => $self->{$_} } keys %$self;
  0         0  
828 0         0 $rpt{manifest_msgs} = delete $rpt{_mani};
829 0         0 $rpt{applied_patches} = [$self->registered_patches];
830 0         0 $rpt{sysinfo} = do {
831 0         0 my %Conf = get_smoked_Config($self->{ddir} => qw( version lfile ));
832 0         0 my $si = System::Info->new;
833 0         0 my ($osname, $osversion) = split m/ - / => $si->os, 2;
834 0   0     0 (my $ncpu = $si->ncpu || "?") =~ s/^\s*(\d+)\s*/$1/;
835 0   0     0 (my $user_note = $self->{user_note} || "") =~ s/(\S)[\s\r\n]*\z/$1\n/;
836             {
837             architecture => lc $si->cpu_type,
838             config_count => $self->{_rpt}{count},
839             cpu_count => $ncpu,
840             cpu_description => $si->cpu,
841             duration => $self->{_rpt}{secs},
842             git_describe => $self->{_rpt}{patchdescr},
843             git_id => $self->{_rpt}{patch},
844             smoke_branch => $self->{_rpt}{smokebranch},
845             hostname => $self->{hostname} || $si->host,
846             lang => $ENV{LANG},
847             lc_all => $ENV{LC_ALL},
848             osname => $osname,
849             osversion => $osversion,
850             perl_id => $Conf{version},
851             reporter => $self->{from},
852             reporter_version => $VERSION,
853             smoke_date => __posixdate($self->{_rpt}{started}),
854             smoke_revision => $Test::Smoke::VERSION,
855             smoker_version => $Test::Smoke::Smoker::VERSION,
856             smoke_version => $Test::Smoke::VERSION,
857             test_jobs => $ENV{TEST_JOBS},
858 0 0 0     0 username => $ENV{LOGNAME} || getlogin || getpwuid($<) || "?",
      0        
859             user_note => $user_note,
860             smoke_perl => ($^V ? sprintf("%vd", $^V) : $]),
861             };
862             };
863 0         0 $rpt{compiler_msgs} = [$self->ccmessages];
864 0         0 $rpt{nonfatal_msgs} = [$self->nonfatalmessages];
865 0         0 $rpt{skipped_tests} = [$self->user_skipped_tests];
866 0         0 $rpt{harness_only} = delete $rpt{harnessonly};
867 0         0 $rpt{summary} = $self->summary;
868              
869 0         0 $rpt{log_file} = undef;
870 0 0       0 my $rpt_fail = $rpt{summary} eq "PASS" ? 0 : 1;
871 0 0       0 if (my $send_log = $self->{send_log}) {
872 0 0 0     0 if ( ($send_log eq "always")
      0        
873             or ($send_log eq "on_fail" && $rpt_fail))
874             {
875 0         0 $rpt{log_file} = $self->get_logfile();
876             }
877             }
878 0         0 $rpt{out_file} = undef;
879 0 0       0 if (my $send_out = $self->{send_out}) {
880 0 0 0     0 if ( ($send_out eq "always")
      0        
881             or ($send_out eq "on_fail" && $rpt_fail))
882             {
883 0         0 $rpt{out_file} = $self->get_outfile();
884             }
885             }
886 0         0 delete $rpt{$_} for qw/from send_log send_out user_note/, grep m/^_/ => keys %rpt;
887              
888 0         0 my $json = Test::Smoke::Util::LoadAJSON->new->utf8(1)->pretty(1)->encode(\%rpt);
889              
890             # write the json to file:
891 0         0 my $jsn_file = catfile($self->{ddir}, $self->{jsnfile});
892 0 0       0 if (open my $jsn, ">", $jsn_file) {
893 0         0 binmode($jsn);
894 0         0 print {$jsn} $json;
  0         0  
895 0         0 close $jsn;
896 0         0 $self->log_info("Write to '%s': ok", $jsn_file);
897             }
898             else {
899 0         0 $self->log_warn("Error creating '%s': %s", $jsn_file, $!);
900             }
901              
902 0         0 return $self->{_json} = $json;
903             }
904              
905             =head2 $reporter->report( )
906              
907             Return a string with the full report
908              
909             =cut
910              
911             sub report {
912 61     61 1 37397 my $self = shift;
913 61 50       199 return unless defined $self->{_outfile};
914 61         165 $self->_get_usernote();
915              
916 61         160 my $report = $self->preamble;
917              
918 61         842 $report .= "Summary: ".$self->summary."\n\n";
919 61         452 $report .= $self->letter_legend . "\n";
920 61         462 $report .= $self->smoke_matrix . $self->bldenv_legend;
921              
922 61         500 $report .= $self->registered_patches;
923              
924 61         433 $report .= $self->harness3_options;
925              
926 61         161 $report .= $self->user_skipped_tests;
927              
928 61 100       159 $report .= "\nFailures: (common-args) $self->{_rpt}{common_args}\n"
929             . $self->failures if $self->has_test_failures;
930 61 100       348 $report .= "\n" . $self->mani_fail if $self->has_mani_failures;
931              
932 61 100       208 $report .= "\nPassed Todo tests: (common-args) $self->{_rpt}{common_args}\n"
933             . $self->todo_passed if $self->has_todo_passed;
934              
935 61         238 $report .= $self->ccmessages;
936              
937 61         177 $report .= $self->nonfatalmessages;
938              
939 61 0 33     195 if ( $self->{showcfg} && $self->{cfg} && $self->has_test_failures ) {
      0        
940 0         0 require Test::Smoke::BuildCFG;
941 0         0 my $bcfg = Test::Smoke::BuildCFG->new( $self->{cfg} );
942 0         0 $report .= "\nBuild configurations:\n" . $bcfg->as_string ."=\n";
943             }
944              
945 61         421 $report .= $self->signature;
946 61         1478 return $report;
947             }
948              
949             =head2 $reporter->_get_usernote()
950              
951             Return $self->{user_note} if exists.
952              
953             Check if C<< $self->{un_file} >> exists, and read contents into C<<
954             $self->{user_note} >>.
955              
956             =cut
957              
958             sub _get_usernote {
959 61     61   86 my $self = shift;
960              
961 61 50 66     346 if (!$self->{user_note} && $self->{un_file}) {
    50          
962 0 0       0 if (open my $unf, '<', $self->{un_file}) {
963 0         0 $self->{user_note} = join('', <$unf>);
964             }
965             else {
966 0         0 $self->log_warn("Cannot read '%s': %s", $self->{un_file}, $!);
967             }
968             }
969             elsif (!defined $self->{user_note}) {
970 0         0 $self->{user_note} = '';
971             }
972 61         295 $self->{user_note} =~ s/(?<=\S)\s*\z/\n/;
973             }
974              
975             =head2 $reporter->ccinfo( )
976              
977             Return the string containing the C-compiler info.
978              
979             =cut
980              
981             sub ccinfo {
982 73     73 1 2629 my $self = shift;
983 73         246 my $cinfo = $self->{_rpt}{cinfo};
984 73 100       211 unless ( $cinfo ) { # Old .out file?
985 53         794 my %Config = get_smoked_Config( $self->{ddir} => qw(
986             cc ccversion gccversion
987             ));
988 53         335 $cinfo = "? ";
989 53   50     743 my $ccvers = $Config{gccversion} || $Config{ccversion} || '';
990 53   50     447 $cinfo .= ( $Config{cc} || 'unknown cc' ) . " version $ccvers";
991 53   50     688 $self->{_ccinfo} = ($Config{cc} || 'cc') . " version $ccvers";
992             }
993 73         376 return $cinfo;
994             }
995              
996             =head2 $reporter->registered_patches()
997              
998             Return a section with the locally applied patches (from patchlevel.h).
999              
1000             =cut
1001              
1002             sub registered_patches {
1003 62     62 1 145 my $self = shift;
1004              
1005 62         784 my @lpatches = get_local_patches($self->{ddir}, $self->{v});
1006 62 50 66     238 @lpatches && $lpatches[0] eq "uncommitted-changes" and shift @lpatches;
1007 62 50       139 wantarray and return @lpatches;
1008              
1009 62 100       497 @lpatches or return "";
1010              
1011 3         47 my $list = join "\n", map " $_" => @lpatches;
1012 3         23 return "\nLocally applied patches:\n$list\n";
1013             }
1014              
1015             =head2 $reporter->harness3_options
1016              
1017             Show indication of the options used for C.
1018              
1019             =cut
1020              
1021             sub harness3_options {
1022 61     61 1 139 my $self = shift;
1023              
1024 61 50       258 $self->{harnessonly} or return "";
1025              
1026 0         0 my $msg = "\nTestsuite was run only with 'harness'";
1027 0 0       0 $self->{harness3opts} or return $msg . "\n";
1028              
1029 0         0 return $msg . " and HARNESS_OPTIONS=$self->{harness3opts}\n";
1030             }
1031              
1032             =head2 $reporter->user_skipped_tests( )
1033              
1034             Show indication for the fact that the user requested to skip some tests.
1035              
1036             =cut
1037              
1038             sub user_skipped_tests {
1039 62     62 1 92 my $self = shift;
1040              
1041 62         98 my @skipped;
1042 62 50 66     369 if ($self->{skip_tests} && -f $self->{skip_tests} and open my $fh,
      66        
1043             "<", $self->{skip_tests})
1044             {
1045 2         53 while (my $raw = <$fh>) {
1046 2 50       23 next, if $raw =~ m/^# One test name on a line/;
1047 2         11 chomp($raw);
1048 2         29 push @skipped, " $raw";
1049             }
1050 2         21 close $fh;
1051             }
1052 62 50       134 wantarray and return @skipped;
1053              
1054 62 100       275 my $skipped = join "\n", @skipped or return "";
1055              
1056 2         20 return "\nTests skipped on user request:\n$skipped";
1057             }
1058              
1059             =head2 $reporter->ccmessages( )
1060              
1061             Use a port of Jarkko's F script to report the compiler messages.
1062              
1063             =cut
1064              
1065             sub ccmessages {
1066 62     62 1 605 my $self = shift;
1067              
1068 62   50     506 my $ccinfo = $self->{_rpt}{cinfo} || $self->{_ccinfo} || "cc";
1069 62         695 $ccinfo =~ s/^(.+)\s+version\s+.+/$1/;
1070              
1071 62 50       680 $^O =~ /^(?:linux|.*bsd.*|darwin)/ and $ccinfo = 'gcc';
1072 62 50       753 my $cc = $ccinfo =~ /(gcc|bcc32)/ ? $1 : $^O;
1073              
1074 62 100       210 if (!$self->{_ccmessages_}) {
1075              
1076 61         252 $self->log_info("Looking for cc messages: '%s'", $cc);
1077             $self->{_ccmessages_} = grepccmsg(
1078             $cc,
1079             $self->get_outfile(),
1080             $self->{v}
1081 61   50     437 ) || [];
1082             }
1083 62         678 $self->log_debug("Finished grepping for %s", $cc);
1084              
1085 62 50       185 return @{$self->{_ccmessages_}} if wantarray;
  0         0  
1086 62 50       170 return "" if !$self->{_ccmessages_};
1087              
1088 62         386 local $" = "\n";
1089 62         216 return <<" EOERRORS";
1090              
1091             Compiler messages($cc):
1092 62         312 @{$self->{_ccmessages_}}
1093             EOERRORS
1094             }
1095              
1096             =head2 $reporter->nonfatalmessages( )
1097              
1098             Find failures worth reporting that won't cause tests to fail
1099              
1100             =cut
1101              
1102             sub nonfatalmessages {
1103 61     61 1 114 my $self = shift;
1104              
1105 61   50     533 my $ccinfo = $self->{_rpt}{cinfo} || $self->{_ccinfo} || "cc";
1106 61         277 $ccinfo =~ s/^(.+)\s+version\s+.+/$1/;
1107              
1108 61 50       703 $^O =~ /^(?:linux|.*bsd.*|darwin)/ and $ccinfo = 'gcc';
1109 61 50       466 my $cc = $ccinfo =~ /(gcc|bcc32)/ ? $1 : $^O;
1110              
1111 61 100       208 if (!$self->{_nonfatal_}) {
1112              
1113 60         213 $self->log_info("Looking for non-fatal messages: '%s'", $cc);
1114             $self->{_nonfatal_} = grepnonfatal(
1115             $cc,
1116             $self->get_outfile(),
1117             $self->{v}
1118 60   50     192 ) || [];
1119             }
1120              
1121 61 50       183 return @{$self->{_nonfatal_}} if wantarray;
  0         0  
1122 61 50       164 return "" if !$self->{_nonfatal_};
1123              
1124 61         135 local $" = "\n";
1125 61         161 return <<" EOERRORS";
1126              
1127             Non-Fatal messages($cc):
1128 61         220 @{$self->{_nonfatal_}}
1129             EOERRORS
1130             }
1131              
1132             =head2 $reporter->preamble( )
1133              
1134             Returns the header of the report.
1135              
1136             =cut
1137              
1138             sub preamble {
1139 63     63 1 1523 my $self = shift;
1140              
1141 63         281 my %Config = get_smoked_Config( $self->{ddir} => qw(
1142             version libc gnulibc_version
1143             ));
1144 63         804 my $si = System::Info->new;
1145 63         582617 my $archname = lc $si->cpu_type;
1146              
1147 63   50     1092 (my $ncpu = $si->ncpu || "") =~ s/^(\d+)\s*/$1 cpu/;
1148 63         1972 $archname .= "/$ncpu";
1149              
1150 63         348 my $cpu = $si->cpu;
1151              
1152 63   66     1157 my $this_host = $self->{hostname} || $si->host;
1153 63         1718 my $time_msg = time_in_hhmm( $self->{_rpt}{secs} );
1154 63         741 my $savg_msg = time_in_hhmm( $self->{_rpt}{avg} );
1155              
1156 63         931 my $cinfo = $self->ccinfo;
1157              
1158 63         532 my $os = $si->os;
1159              
1160 63         1021 my $branch = '';
1161 63 50       263 if ($self->{_rpt}{smokebranch}) {
1162 0         0 $branch = " branch $self->{_rpt}{smokebranch}";
1163             }
1164              
1165 63         682 my $preamble = <<__EOH__;
1166             Automated smoke report for$branch $Config{version} patch $self->{_rpt}{patchlevel}
1167             $this_host: $cpu ($archname)
1168             on $os
1169             using $cinfo
1170             smoketime $time_msg (average $savg_msg)
1171              
1172             __EOH__
1173              
1174 63 100       279 if ($self->{un_position} eq USERNOTE_ON_TOP) {
1175 1         19 (my $user_note = $self->{user_note}) =~ s/(?<=\S)\s*\z/\n/;
1176 1         6 $preamble = "$user_note\n$preamble";
1177             }
1178              
1179 63         4782 return $preamble;
1180             }
1181              
1182             =head2 $reporter->smoke_matrix( )
1183              
1184             C returns a string with the result-letters and their
1185             configs.
1186              
1187             =cut
1188              
1189             sub smoke_matrix {
1190 118     118 1 192839 my $self = shift;
1191 118         312 my $rpt = $self->{_rpt};
1192              
1193             # Maximum of 6 letters => 11 positions
1194 118         272 my $rptl = length $rpt->{patchdescr};
1195 118 100       731 my $pad = $rptl >= 11 ? "" : " " x int( (11 - $rptl)/2 );
1196 118         640 my $patch = $pad . $rpt->{patchdescr};
1197             my $report = sprintf "%-11s Configuration (common) %s\n",
1198 118         802 $patch, $rpt->{common_args};
1199 118         309 $report .= ("-" x 11) . " " . ("-" x 57) . "\n";
1200              
1201 118         166 foreach my $config ( @{ $rpt->{cfglist} } ) {
  118         648  
1202 250         500 my $letters = "";
1203 250         429 foreach my $dbinfo (qw( N D )) {
1204 500         662 foreach my $tstenv ( @{ $self->{_tstenv} } ) {
  500         873  
1205 832         2181 $letters .= "$rpt->{$config}{summary}{$dbinfo}{$tstenv} ";
1206             }
1207             }
1208 250         1220 my $cfg = join " ", grep ! exists $rpt->{_common_args}{ $_ }
1209             => quotewords( '\s+', 1, $config );
1210 250         27481 $report .= sprintf "%-12s%s\n", $letters, $cfg;
1211             }
1212              
1213 118         1126 return $report;
1214             }
1215              
1216             =head2 $reporter->summary( )
1217              
1218             Return the B or B string.
1219              
1220             =cut
1221              
1222             sub summary {
1223 109     109 1 27025 my $self = shift;
1224 109         280 my $count = $self->{_counters};
1225 109         1119 my @rpt_sum_stat = grep $count->{$_} > 0 => qw( X F M m c t );
1226 109         243 my $rpt_summary = "";
1227 109 100       466 if (@rpt_sum_stat) {
1228 77         595 $rpt_summary = "FAIL(" . join("", @rpt_sum_stat) . ")";
1229             }
1230             else {
1231 32 50       257 $rpt_summary = $count->{o} == 0 ? "PASS" : "PASS-so-far";
1232             }
1233              
1234 109         449 return $rpt_summary;
1235             }
1236              
1237             =head2 $reporter->has_test_failures( )
1238              
1239             Returns true if C<< @{ $reporter->{_failures} >>.
1240              
1241             =cut
1242              
1243 61 50   61 1 498 sub has_test_failures { exists $_[0]->{_failures} && @{ $_[0]->{_failures} } }
  61         968  
1244              
1245             =head2 $reporter->failures( )
1246              
1247             report the failures (grouped by configurations).
1248              
1249             =cut
1250              
1251             sub failures {
1252 84     84 1 8032 my $self = shift;
1253              
1254             return join "\n", map {
1255 123         204 join "\n", @{ $_->{cfgs} }, $_->{tests}, ""
  123         1578  
1256 84         151 } @{ $self->{_failures} };
  84         315  
1257             }
1258              
1259             =head2 $reporter->has_todo_passed( )
1260              
1261             Returns true if C<< @{ $reporter->{_todo_pasesd} >>.
1262              
1263             =cut
1264              
1265 61 50   61 1 384 sub has_todo_passed { exists $_[0]->{_todo_passed} && @{ $_[0]->{_todo_passed} } }
  61         664  
1266              
1267             =head2 $reporter->todo_passed( )
1268              
1269             report the todo that passed (grouped by configurations).
1270              
1271             =cut
1272              
1273             sub todo_passed {
1274 54     54 1 16529 my $self = shift;
1275              
1276             return join "\n", map {
1277 66         129 join "\n", @{ $_->{cfgs} }, $_->{tests}, ""
  66         848  
1278 54         252 } @{ $self->{_todo_passed} };
  54         273  
1279             }
1280              
1281             =head2 $reporter->has_mani_failures( )
1282              
1283             Returns true if C<< @{ $reporter->{_mani} >>.
1284              
1285             =cut
1286              
1287 61 50   61 1 399 sub has_mani_failures { exists $_[0]->{_mani} && @{ $_[0]->{_mani} } }
  61         303  
1288              
1289             =head2 $reporter->mani_fail( )
1290              
1291             report the MANIFEST failures.
1292              
1293             =cut
1294              
1295             sub mani_fail {
1296 6     6 1 13 my $self = shift;
1297              
1298 6         16 return join "\n", @{ $self->{_mani} }, "";
  6         47  
1299             }
1300              
1301             =head2 $reporter->bldenv_legend( )
1302              
1303             Returns a string with the legend for build-environments
1304              
1305             =cut
1306              
1307             sub bldenv_legend {
1308 63     63 1 1122 my $self = shift;
1309 0         0 $self->{defaultenv} = ( @{ $self->{_tstenv} } == 1 )
1310 63 50       182 unless defined $self->{defaultenv};
1311 63   100     781 my $debugging = $self->{_rpt}{dbughow} || '-DDEBUGGING';
1312              
1313 63 100 66     467 if ( $self->{_locale} && @{ $self->{_locale} } ) {
  63         288  
1314 10         30 my @locale = ( @{ $self->{_locale} }, @{ $self->{_locale} } );
  10         71  
  10         62  
1315 10         65 my $lcnt = @locale;
1316 10         47 my $half = int(( 4 + $lcnt ) / 2 );
1317 10         30 my $cnt = 2 * $half;
1318              
1319 10         54 my $line = '';
1320 10         39 for my $i ( 0 .. $cnt-1 ) {
1321 62         172 $line .= '| ' x ( $cnt - 1 - $i );
1322 62         100 $line .= '+';
1323 62         115 $line .= '-' x (2 * $i);
1324 62         117 $line .= '- ';
1325              
1326 62 100       166 if ( ($i % $half) < ($lcnt / 2) ) {
1327 22         45 my $locale = shift @locale; # XXX: perhaps pop()
1328 22         73 $line .= "LC_ALL = $locale"
1329             } else {
1330 40 50       77 if ( $self->{perlio_only} ) {
1331 0         0 $line .= "PERLIO = perlio"
1332             }
1333             else {
1334 40 100       51 $line .= ( (($i - @{$self->{_locale}}) % $half) % 2 == 0 )
  40         107  
1335             ? "PERLIO = perlio"
1336             : "PERLIO = stdio ";
1337             }
1338             }
1339 62 100       232 $i < $half and $line .= " $debugging";
1340 62         124 $line .= "\n";
1341             }
1342 10         86 return $line;
1343             }
1344              
1345 53         344 my $locale = ''; # XXX
1346 53 100       748 return $locale ? <{defaultenv} ? <
    50          
1347             | | | | | +- LC_ALL = $locale $debugging
1348             | | | | +--- PERLIO = perlio $debugging
1349             | | | +----- PERLIO = stdio $debugging
1350             | | +------- LC_ALL = $locale
1351             | +--------- PERLIO = perlio
1352             +----------- PERLIO = stdio
1353              
1354             EOL
1355             | +--------- $debugging
1356             +----------- no debugging
1357              
1358             EOS
1359             | | | +----- PERLIO = perlio $debugging
1360             | | +------- PERLIO = stdio $debugging
1361             | +--------- PERLIO = perlio
1362             +----------- PERLIO = stdio
1363              
1364             EOE
1365             }
1366              
1367             =head2 $reporter->letter_legend( )
1368              
1369             Returns a string with the legend for the letters in the matrix.
1370              
1371             =cut
1372              
1373             sub letter_legend {
1374 61     61 1 557 require Test::Smoke::Smoker;
1375             return <<__EOL__
1376             O = OK F = Failure(s), extended report at the bottom
1377             X = Failure(s) under TEST but not under harness
1378             ? = still running or test results not (yet) available
1379             Build failures during: - = unknown or N/A
1380             c = Configure, m = make, M = make (after miniperl), t = make test-prep
1381             __EOL__
1382 61         608 }
1383              
1384             =head2 $reporter->signature()
1385              
1386             Returns the signature for the e-mail message (starting with dash dash space
1387             newline) and some version numbers.
1388              
1389             =cut
1390              
1391             sub signature {
1392 62     62 1 683 my $self = shift;
1393 62 50       1946 my $this_pver = $^V ? sprintf "%vd", $^V : $];
1394 62         367 my $build_info = "$Test::Smoke::VERSION";
1395              
1396 62         262 my $signature = <<" __EOS__";
1397             --
1398             Report by Test::Smoke v$build_info running on perl $this_pver
1399             (Reporter v$VERSION / Smoker v$Test::Smoke::Smoker::VERSION)
1400             __EOS__
1401              
1402 62 50       206 if ($self->{un_position} ne USERNOTE_ON_TOP) {
1403 62         585 (my $user_note = $self->{user_note}) =~ s/(?<=\S)\s*\z/\n/;
1404 62         280 $signature = "\n$user_note\n$signature";
1405             }
1406              
1407 62         184 return $signature;
1408             }
1409              
1410             1;
1411              
1412             =head1 SEE ALSO
1413              
1414             L
1415              
1416             =head1 COPYRIGHT
1417              
1418             (c) 2002-2012, All rights reserved.
1419              
1420             * Abe Timmerman
1421             * H.Merijn Brand
1422              
1423             This library is free software; you can redistribute it and/or modify
1424             it under the same terms as Perl itself.
1425              
1426             See:
1427              
1428             =over 4
1429              
1430             =item * http://www.perl.com/perl/misc/Artistic.html
1431              
1432             =item * http://www.gnu.org/copyleft/gpl.html
1433              
1434             =back
1435              
1436             This program is distributed in the hope that it will be useful,
1437             but WITHOUT ANY WARRANTY; without even the implied warranty of
1438             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1439              
1440             =cut