File Coverage

blib/lib/Test2/Formatter/TAP.pm
Criterion Covered Total %
statement 225 228 98.6
branch 126 138 91.3
condition 94 111 84.6
subroutine 26 26 100.0
pod 2 16 12.5
total 473 519 91.1


line stmt bran cond sub pod time code
1             package Test2::Formatter::TAP;
2 245     245   3245 use strict;
  245         525  
  245         7115  
3 245     245   1230 use warnings;
  245         489  
  245         23339  
4              
5             our $VERSION = '1.302181';
6              
7 245     245   2637 use Test2::Util qw/clone_io/;
  245         524  
  245         13801  
8              
9 245         1716 use Test2::Util::HashBase qw{
10             no_numbers handles _encoding _last_fh
11             -made_assertion
12 245     245   2948 };
  245         533  
13              
14             sub OUT_STD() { 0 }
15             sub OUT_ERR() { 1 }
16              
17 245     245   102291 BEGIN { require Test2::Formatter; our @ISA = qw(Test2::Formatter) }
  245         122424  
18              
19             my $supports_tables;
20             sub supports_tables {
21 5 100   5 0 25 if (!defined $supports_tables) {
22 1         10 local $SIG{__DIE__} = 'DEFAULT';
23 1         3 local $@;
24             $supports_tables
25             = ($INC{'Term/Table.pm'} && $INC{'Term/Table/Util.pm'})
26 1   50     8 || eval { require Term::Table; require Term::Table::Util; 1 }
27             || 0;
28             }
29 5         19 return $supports_tables;
30             }
31              
32             sub _autoflush {
33 1196     1196   2420 my($fh) = pop;
34 1196         3244 my $old_fh = select $fh;
35 1196         2736 $| = 1;
36 1196         3048 select $old_fh;
37             }
38              
39             _autoflush(\*STDOUT);
40             _autoflush(\*STDERR);
41              
42 77     77 0 203 sub hide_buffered { 1 }
43              
44             sub init {
45 364     364 0 857 my $self = shift;
46              
47 364   66     3933 $self->{+HANDLES} ||= $self->_open_handles;
48 364 100       1898 if(my $enc = delete $self->{encoding}) {
49 1         8 $self->encoding($enc);
50             }
51             }
52              
53             sub _open_handles {
54 353     353   837 my $self = shift;
55              
56 353         2147 require Test2::API;
57 353         1866 my $out = clone_io(Test2::API::test2_stdout());
58 353         1684 my $err = clone_io(Test2::API::test2_stderr());
59              
60 353         1707 _autoflush($out);
61 353         1657 _autoflush($err);
62              
63 353         1759 return [$out, $err];
64             }
65              
66             sub encoding {
67 3     3 1 14 my $self = shift;
68              
69 3 100 66     16 if ($] ge "5.007003" and @_) {
70 2         5 my ($enc) = @_;
71 2         5 my $handles = $self->{+HANDLES};
72              
73             # https://rt.perl.org/Public/Bug/Display.html?id=31923
74             # If utf8 is requested we use ':utf8' instead of ':encoding(utf8)' in
75             # order to avoid the thread segfault.
76 2 50       16 if ($enc =~ m/^utf-?8$/i) {
77 2         13 binmode($_, ":utf8") for @$handles;
78             }
79             else {
80 0         0 binmode($_, ":encoding($enc)") for @$handles;
81             }
82 2         8 $self->{+_ENCODING} = $enc;
83             }
84              
85 3         10 return $self->{+_ENCODING};
86             }
87              
88             if ($^C) {
89 245     245   2014 no warnings 'redefine';
  245         524  
  245         53220  
90       4     *write = sub {};
91             }
92             sub write {
93 4987     4987 1 13034 my ($self, $e, $num, $f) = @_;
94              
95             # The most common case, a pass event with no amnesty and a normal name.
96 4987 100       14741 return if $self->print_optimal_pass($e, $num);
97              
98 2174   66     5442 $f ||= $e->facet_data;
99              
100 2174 50       5903 $self->encoding($f->{control}->{encoding}) if $f->{control}->{encoding};
101              
102 2174 100       5751 my @tap = $self->event_tap($f, $num) or return;
103              
104 1958 100       5060 $self->{+MADE_ASSERTION} = 1 if $f->{assert};
105              
106 1958   100     6699 my $nesting = $f->{trace}->{nested} || 0;
107 1958         3595 my $handles = $self->{+HANDLES};
108 1958         4617 my $indent = ' ' x $nesting;
109              
110             # Local is expensive! Only do it if we really need to.
111 1958 100 66     9265 local($\, $,) = (undef, '') if $\ || $,;
112 1958         4209 for my $set (@tap) {
113 245     245   1979 no warnings 'uninitialized';
  245         599  
  245         610125  
114 2858         6660 my ($hid, $msg) = @$set;
115 2858 50       6077 next unless $msg;
116 2858 50       6857 my $io = $handles->[$hid] or next;
117              
118             print $io "\n"
119             if $ENV{HARNESS_ACTIVE}
120             && $hid == OUT_ERR
121 2858 100 100     11200 && $self->{+_LAST_FH} != $io
      100        
      100        
122             && $msg =~ m/^#\s*Failed( \(TODO\))? test /;
123              
124 2858 100       7936 $msg =~ s/^/$indent/mg if $nesting;
125 2858         75763 print $io $msg;
126 2858         16321 $self->{+_LAST_FH} = $io;
127             }
128             }
129              
130             sub print_optimal_pass {
131 4999     4999 0 10314 my ($self, $e, $num) = @_;
132              
133 4999         9133 my $type = ref($e);
134              
135             # Only optimal if this is a Pass or a passing Ok
136 4999 100 100     24320 return unless $type eq 'Test2::Event::Pass' || ($type eq 'Test2::Event::Ok' && $e->{pass});
      100        
137              
138             # Amnesty requires further processing (todo is a form of amnesty)
139 2860 100 66     15601 return if ($e->{amnesty} && @{$e->{amnesty}}) || defined($e->{todo});
  13   100     59  
140              
141             # A name with a newline or hash symbol needs extra processing
142 2834 100 100     15745 return if defined($e->{name}) && (-1 != index($e->{name}, "\n") || -1 != index($e->{name}, '#'));
      100        
143              
144 2819         6361 my $ok = 'ok';
145 2819 100 66     14212 $ok .= " $num" if $num && !$self->{+NO_NUMBERS};
146 2819 100       8617 $ok .= defined($e->{name}) ? " - $e->{name}\n" : "\n";
147              
148 2819 100       7058 if (my $nesting = $e->{trace}->{nested}) {
149 89         245 my $indent = ' ' x $nesting;
150 89         241 $ok = "$indent$ok";
151             }
152              
153 2819         6625 my $io = $self->{+HANDLES}->[OUT_STD];
154              
155 2819 50 33     15120 local($\, $,) = (undef, '') if $\ || $,;
156 2819         173380 print $io $ok;
157 2819         12351 $self->{+_LAST_FH} = $io;
158              
159 2819         15418 return 1;
160             }
161              
162             sub event_tap {
163 2949     2949 0 5514 my ($self, $f, $num) = @_;
164              
165 2949         4340 my @tap;
166              
167             # If this IS the first event the plan should come first
168             # (plan must be before or after assertions, not in the middle)
169 2949 100 100     9726 push @tap => $self->plan_tap($f) if $f->{plan} && !$self->{+MADE_ASSERTION};
170              
171             # The assertion is most important, if present.
172 2949 100       6329 if ($f->{assert}) {
173 1327         3223 push @tap => $self->assert_tap($f, $num);
174 1327 100 100     4083 push @tap => $self->debug_tap($f, $num) unless $f->{assert}->{no_debug} || $f->{assert}->{pass};
175             }
176              
177             # Almost as important as an assertion
178 2949 100       6490 push @tap => $self->error_tap($f) if $f->{errors};
179              
180             # Now lets see the diagnostics messages
181 2949 100       7963 push @tap => $self->info_tap($f) if $f->{info};
182              
183             # If this IS NOT the first event the plan should come last
184             # (plan must be before or after assertions, not in the middle)
185 2949 100 100     10305 push @tap => $self->plan_tap($f) if $self->{+MADE_ASSERTION} && $f->{plan};
186              
187             # Bail out
188 2949 100       6889 push @tap => $self->halt_tap($f) if $f->{control}->{halt};
189              
190 2949 100       11285 return @tap if @tap;
191 217 100       1124 return @tap if $f->{control}->{halt};
192 215 100       746 return @tap if grep { $f->{$_} } qw/assert plan info errors/;
  860         2244  
193              
194             # Use the summary as a fallback if nothing else is usable.
195 211         1763 return $self->summary_tap($f, $num);
196             }
197              
198             sub error_tap {
199 4     4 0 146 my $self = shift;
200 4         7 my ($f) = @_;
201              
202 4 50 33     14 my $IO = ($f->{amnesty} && @{$f->{amnesty}}) ? OUT_STD : OUT_ERR;
203              
204             return map {
205 6         10 my $details = $_->{details};
206              
207 6         10 my $msg;
208 6 100       15 if (ref($details)) {
209 1         6 require Data::Dumper;
210 1         11 my $dumper = Data::Dumper->new([$details])->Indent(2)->Terse(1)->Pad('# ')->Useqq(1)->Sortkeys(1);
211 1         48 chomp($msg = $dumper->Dump);
212             }
213             else {
214 5         10 chomp($msg = $details);
215 5         21 $msg =~ s/^/# /;
216 5         17 $msg =~ s/\n/\n# /g;
217             }
218              
219 6         49 [$IO, "$msg\n"];
220 4         8 } @{$f->{errors}};
  4         10  
221             }
222              
223             sub plan_tap {
224 498     498 0 1214 my $self = shift;
225 498         1335 my ($f) = @_;
226 498 100       2657 my $plan = $f->{plan} or return;
227              
228 497 100       1754 return if $plan->{none};
229              
230 495 100       1494 if ($plan->{skip}) {
231 19 100       84 my $reason = $plan->{details} or return [OUT_STD, "1..0 # SKIP\n"];
232 16         444 chomp($reason);
233 16         112 return [OUT_STD, '1..0 # SKIP ' . $reason . "\n"];
234             }
235              
236 476         2473 return [OUT_STD, "1.." . $plan->{count} . "\n"];
237             }
238              
239 736     736 0 1455 sub no_subtest_space { 0 }
240             sub assert_tap {
241 1367     1367 0 2171 my $self = shift;
242 1367         2271 my ($f, $num) = @_;
243              
244 1367 50       3056 my $assert = $f->{assert} or return;
245 1367         2079 my $pass = $assert->{pass};
246 1367         2099 my $name = $assert->{details};
247              
248 1367 100       2742 my $ok = $pass ? 'ok' : 'not ok';
249 1367 100 66     5499 $ok .= " $num" if $num && !$self->{+NO_NUMBERS};
250              
251             # The regex form is ~250ms, the index form is ~50ms
252 1367         2063 my @extra;
253 1367 100 66     7514 defined($name) && (
      100        
254             (index($name, "\n") != -1 && (($name, @extra) = split(/\n\r?/, $name, -1))),
255             ((index($name, "#" ) != -1 || substr($name, -1) eq '\\') && (($name =~ s|\\|\\\\|g), ($name =~ s|#|\\#|g)))
256             );
257              
258 1367 100       3074 my $extra_space = @extra ? ' ' x (length($ok) + 2) : '';
259 1367         2110 my $extra_indent = '';
260              
261 1367         2097 my ($directives, $reason, $is_skip);
262 1367 100       3006 if ($f->{amnesty}) {
263 329         486 my %directives;
264              
265 329         436 for my $am (@{$f->{amnesty}}) {
  329         769  
266 342 50       684 next if $am->{inherited};
267 342 50       702 my $tag = $am->{tag} or next;
268 342 100       750 $is_skip = 1 if $tag eq 'skip';
269              
270 342   100     1392 $directives{$tag} ||= $am->{details};
271             }
272              
273 329         543 my %seen;
274              
275             # Sort so that TODO comes before skip even on systems where lc sorts
276             # before uc, as other code depends on that ordering.
277 329         1009 my @order = grep { !$seen{$_}++ } sort { lc $b cmp lc $a } keys %directives;
  339         1416  
  10         54  
278              
279 329         837 $directives = ' # ' . join ' & ' => @order;
280              
281 329         609 for my $tag ('skip', @order) {
282 630 100 100     2081 next unless defined($directives{$tag}) && length($directives{$tag});
283 272         467 $reason = $directives{$tag};
284 272         750 last;
285             }
286             }
287              
288 1367 100 100     5451 $ok .= " - $name" if defined $name && !($is_skip && !$name);
      100        
289              
290 1367         1960 my @subtap;
291 1367 100 100     3151 if ($f->{parent} && $f->{parent}->{buffered}) {
292 89         185 $ok .= ' {';
293              
294             # In a verbose harness we indent the extra since they will appear
295             # inside the subtest braces. This helps readability. In a non-verbose
296             # harness we do not do this because it is less readable.
297 89 50 33     421 if ($ENV{HARNESS_IS_VERBOSE} || !$ENV{HARNESS_ACTIVE}) {
298 0         0 $extra_indent = " ";
299 0         0 $extra_space = ' ';
300             }
301              
302             # Render the sub-events, we use our own counter for these.
303 89         161 my $count = 0;
304             @subtap = map {
305 766         1362 my $f2 = $_;
306              
307             # Bump the count for any event that should bump it.
308 766 100       1634 $count++ if $f2->{assert};
309              
310             # This indents all output lines generated for the sub-events.
311             # index 0 is the filehandle, index 1 is the message we want to indent.
312 766         1727 map { $_->[1] =~ s/^(.*\S.*)$/ $1/mg; $_ } $self->event_tap($f2, $count);
  892         5651  
  892         2435  
313 89         141 } @{$f->{parent}->{children}};
  89         240  
314              
315 89         292 push @subtap => [OUT_STD, "}\n"];
316             }
317              
318 1367 100       2948 if ($directives) {
319 329 100       681 $directives = ' # TODO & SKIP' if $directives eq ' # TODO & skip';
320 329         503 $ok .= $directives;
321 329 100       723 $ok .= " $reason" if defined($reason);
322             }
323              
324 1367 100       3155 $extra_space = ' ' if $self->no_subtest_space;
325              
326 1367         4316 my @out = ([OUT_STD, "$ok\n"]);
327 1367 100       2970 push @out => map {[OUT_STD, "${extra_indent}#${extra_space}$_\n"]} @extra if @extra;
  31         110  
328 1367         2222 push @out => @subtap;
329              
330 1367         3781 return @out;
331             }
332              
333             sub debug_tap {
334 23     23 0 62 my ($self, $f, $num) = @_;
335              
336             # Figure out the debug info, this is typically the file name and line
337             # number, but can also be a custom message. If no trace object is provided
338             # then we have nothing useful to display.
339 23         49 my $name = $f->{assert}->{details};
340 23         53 my $trace = $f->{trace};
341              
342 23         46 my $debug = "[No trace info available]";
343 23 100       70 if ($trace->{details}) {
    100          
344 1         4 $debug = $trace->{details};
345             }
346             elsif ($trace->{frame}) {
347 19         35 my ($pkg, $file, $line) = @{$trace->{frame}};
  19         42  
348 19 50 33     108 $debug = "at $file line $line." if $file && $line;
349             }
350              
351 23 100 100     64 my $amnesty = $f->{amnesty} && @{$f->{amnesty}}
352             ? ' (with amnesty)'
353             : '';
354              
355             # Create the initial diagnostics. If the test has a name we put the debug
356             # info on a second line, this behavior is inherited from Test::Builder.
357 23 100       97 my $msg = defined($name)
358             ? qq[# Failed test${amnesty} '$name'\n# $debug\n]
359             : qq[# Failed test${amnesty} $debug\n];
360              
361 23 100 100     88 my $IO = $f->{amnesty} && @{$f->{amnesty}} ? OUT_STD : OUT_ERR;
362              
363 23         106 return [$IO, $msg];
364             }
365              
366             sub halt_tap {
367 13     13 0 49 my ($self, $f) = @_;
368              
369 13 100 100     65 return if $f->{trace}->{nested} && !$f->{trace}->{buffered};
370 10         40 my $details = $f->{control}->{details};
371              
372 10 100 100     68 return [OUT_STD, "Bail out!\n"] unless defined($details) && length($details);
373 7         41 return [OUT_STD, "Bail out! $details\n"];
374             }
375              
376             sub info_tap {
377 917     917 0 1744 my ($self, $f) = @_;
378              
379             return map {
380 927         1564 my $details = $_->{details};
381 927         1519 my $table = $_->{table};
382              
383 927 100 100     3219 my $IO = $_->{debug} && !($f->{amnesty} && @{$f->{amnesty}}) ? OUT_ERR : OUT_STD;
384              
385 927         1384 my $msg;
386 927 100 100     2817 if ($table && $self->supports_tables) {
    100          
387 36         21143 $msg = join "\n" => map { "# $_" } Term::Table->new(
388             header => $table->{header},
389             rows => $table->{rows},
390             collapse => $table->{collapse},
391             no_collapse => $table->{no_collapse},
392 4         24 sanitize => 1,
393             mark_tail => 1,
394             max_width => $self->calc_table_size($f),
395             )->render();
396             }
397             elsif (ref($details)) {
398 2         10 require Data::Dumper;
399 2         11 my $dumper = Data::Dumper->new([$details])->Indent(2)->Terse(1)->Pad('# ')->Useqq(1)->Sortkeys(1);
400 2         110 chomp($msg = $dumper->Dump);
401             }
402             else {
403 921         2279 chomp($msg = $details);
404 921         4820 $msg =~ s/^/# /;
405 921         3442 $msg =~ s/\n/\n# /g;
406             }
407              
408 927         5067 [$IO, "$msg\n"];
409 917         1299 } @{$f->{info}};
  917         2129  
410             }
411              
412             sub summary_tap {
413 214     214 0 844 my ($self, $f, $num) = @_;
414              
415 214 100       1203 return if $f->{about}->{no_display};
416              
417 213 100       1768 my $summary = $f->{about}->{details} or return;
418 2         6 chomp($summary);
419 2         12 $summary =~ s/^/# /smg;
420              
421 2         17 return [OUT_STD, "$summary\n"];
422             }
423              
424             sub calc_table_size {
425 4     4 0 8 my $self = shift;
426 4         9 my ($f) = @_;
427              
428 4         10 my $term = Term::Table::Util::term_size();
429 4   100     26 my $nesting = 2 + (($f->{trace}->{nested} || 0) * 4); # 4 spaces per level, also '# ' prefix
430 4         7 my $total = $term - $nesting;
431              
432             # Sane minimum width, any smaller and we are asking for pain
433 4 50       10 return 50 if $total < 50;
434              
435 4         17 return $total;
436             }
437              
438             1;
439              
440             __END__
441              
442             =pod
443              
444             =encoding UTF-8
445              
446             =head1 NAME
447              
448             Test2::Formatter::TAP - Standard TAP formatter
449              
450             =head1 DESCRIPTION
451              
452             This is what takes events and turns them into TAP.
453              
454             =head1 SYNOPSIS
455              
456             use Test2::Formatter::TAP;
457             my $tap = Test2::Formatter::TAP->new();
458              
459             # Switch to utf8
460             $tap->encoding('utf8');
461              
462             $tap->write($event, $number); # Output an event
463              
464             =head1 METHODS
465              
466             =over 4
467              
468             =item $bool = $tap->no_numbers
469              
470             =item $tap->set_no_numbers($bool)
471              
472             Use to turn numbers on and off.
473              
474             =item $arrayref = $tap->handles
475              
476             =item $tap->set_handles(\@handles);
477              
478             Can be used to get/set the filehandles. Indexes are identified by the
479             C<OUT_STD> and C<OUT_ERR> constants.
480              
481             =item $encoding = $tap->encoding
482              
483             =item $tap->encoding($encoding)
484              
485             Get or set the encoding. By default no encoding is set, the original settings
486             of STDOUT and STDERR are used.
487              
488             This directly modifies the stored filehandles, it does not create new ones.
489              
490             =item $tap->write($e, $num)
491              
492             Write an event to the console.
493              
494             =back
495              
496             =head1 SOURCE
497              
498             The source code repository for Test2 can be found at
499             F<http://github.com/Test-More/test-more/>.
500              
501             =head1 MAINTAINERS
502              
503             =over 4
504              
505             =item Chad Granum E<lt>exodist@cpan.orgE<gt>
506              
507             =back
508              
509             =head1 AUTHORS
510              
511             =over 4
512              
513             =item Chad Granum E<lt>exodist@cpan.orgE<gt>
514              
515             =item Kent Fredric E<lt>kentnl@cpan.orgE<gt>
516              
517             =back
518              
519             =head1 COPYRIGHT
520              
521             Copyright 2020 Chad Granum E<lt>exodist@cpan.orgE<gt>.
522              
523             This program is free software; you can redistribute it and/or
524             modify it under the same terms as Perl itself.
525              
526             See F<http://dev.perl.org/licenses/>
527              
528             =cut