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   3561 use strict;
  245         566  
  245         7591  
3 245     245   1615 use warnings;
  245         496  
  245         24471  
4              
5             our $VERSION = '1.302182';
6              
7 245     245   2632 use Test2::Util qw/clone_io/;
  245         497  
  245         13839  
8              
9 245         1690 use Test2::Util::HashBase qw{
10             no_numbers handles _encoding _last_fh
11             -made_assertion
12 245     245   2683 };
  245         516  
13              
14             sub OUT_STD() { 0 }
15             sub OUT_ERR() { 1 }
16              
17 245     245   101965 BEGIN { require Test2::Formatter; our @ISA = qw(Test2::Formatter) }
  245         124700  
18              
19             my $supports_tables;
20             sub supports_tables {
21 5 100   5 0 35 if (!defined $supports_tables) {
22 1         8 local $SIG{__DIE__} = 'DEFAULT';
23 1         3 local $@;
24             $supports_tables
25             = ($INC{'Term/Table.pm'} && $INC{'Term/Table/Util.pm'})
26 1   50     7 || eval { require Term::Table; require Term::Table::Util; 1 }
27             || 0;
28             }
29 5         23 return $supports_tables;
30             }
31              
32             sub _autoflush {
33 1196     1196   2499 my($fh) = pop;
34 1196         3396 my $old_fh = select $fh;
35 1196         2941 $| = 1;
36 1196         3081 select $old_fh;
37             }
38              
39             _autoflush(\*STDOUT);
40             _autoflush(\*STDERR);
41              
42 77     77 0 195 sub hide_buffered { 1 }
43              
44             sub init {
45 364     364 0 877 my $self = shift;
46              
47 364   66     3983 $self->{+HANDLES} ||= $self->_open_handles;
48 364 100       1898 if(my $enc = delete $self->{encoding}) {
49 1         5 $self->encoding($enc);
50             }
51             }
52              
53             sub _open_handles {
54 353     353   886 my $self = shift;
55              
56 353         2216 require Test2::API;
57 353         1817 my $out = clone_io(Test2::API::test2_stdout());
58 353         1812 my $err = clone_io(Test2::API::test2_stderr());
59              
60 353         1772 _autoflush($out);
61 353         1711 _autoflush($err);
62              
63 353         1751 return [$out, $err];
64             }
65              
66             sub encoding {
67 3     3 1 12 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       21 if ($enc =~ m/^utf-?8$/i) {
77 2         15 binmode($_, ":utf8") for @$handles;
78             }
79             else {
80 0         0 binmode($_, ":encoding($enc)") for @$handles;
81             }
82 2         6 $self->{+_ENCODING} = $enc;
83             }
84              
85 3         10 return $self->{+_ENCODING};
86             }
87              
88             if ($^C) {
89 245     245   2085 no warnings 'redefine';
  245         670  
  245         54447  
90       4     *write = sub {};
91             }
92             sub write {
93 4987     4987 1 12859 my ($self, $e, $num, $f) = @_;
94              
95             # The most common case, a pass event with no amnesty and a normal name.
96 4987 100       14268 return if $self->print_optimal_pass($e, $num);
97              
98 2174   66     5550 $f ||= $e->facet_data;
99              
100 2174 50       5887 $self->encoding($f->{control}->{encoding}) if $f->{control}->{encoding};
101              
102 2174 100       6185 my @tap = $self->event_tap($f, $num) or return;
103              
104 1958 100       5174 $self->{+MADE_ASSERTION} = 1 if $f->{assert};
105              
106 1958   100     6815 my $nesting = $f->{trace}->{nested} || 0;
107 1958         3665 my $handles = $self->{+HANDLES};
108 1958         4521 my $indent = ' ' x $nesting;
109              
110             # Local is expensive! Only do it if we really need to.
111 1958 100 66     9743 local($\, $,) = (undef, '') if $\ || $,;
112 1958         4363 for my $set (@tap) {
113 245     245   2062 no warnings 'uninitialized';
  245         617  
  245         615978  
114 2858         6615 my ($hid, $msg) = @$set;
115 2858 50       5910 next unless $msg;
116 2858 50       6794 my $io = $handles->[$hid] or next;
117              
118             print $io "\n"
119             if $ENV{HARNESS_ACTIVE}
120             && $hid == OUT_ERR
121 2858 100 100     11644 && $self->{+_LAST_FH} != $io
      100        
      100        
122             && $msg =~ m/^#\s*Failed( \(TODO\))? test /;
123              
124 2858 100       8095 $msg =~ s/^/$indent/mg if $nesting;
125 2858         59248 print $io $msg;
126 2858         16179 $self->{+_LAST_FH} = $io;
127             }
128             }
129              
130             sub print_optimal_pass {
131 4999     4999 0 10160 my ($self, $e, $num) = @_;
132              
133 4999         9408 my $type = ref($e);
134              
135             # Only optimal if this is a Pass or a passing Ok
136 4999 100 100     24605 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     15754 return if ($e->{amnesty} && @{$e->{amnesty}}) || defined($e->{todo});
  13   100     57  
140              
141             # A name with a newline or hash symbol needs extra processing
142 2834 100 100     16288 return if defined($e->{name}) && (-1 != index($e->{name}, "\n") || -1 != index($e->{name}, '#'));
      100        
143              
144 2819         6089 my $ok = 'ok';
145 2819 100 66     14078 $ok .= " $num" if $num && !$self->{+NO_NUMBERS};
146 2819 100       8517 $ok .= defined($e->{name}) ? " - $e->{name}\n" : "\n";
147              
148 2819 100       7274 if (my $nesting = $e->{trace}->{nested}) {
149 89         201 my $indent = ' ' x $nesting;
150 89         235 $ok = "$indent$ok";
151             }
152              
153 2819         6299 my $io = $self->{+HANDLES}->[OUT_STD];
154              
155 2819 50 33     14763 local($\, $,) = (undef, '') if $\ || $,;
156 2819         163960 print $io $ok;
157 2819         12360 $self->{+_LAST_FH} = $io;
158              
159 2819         15556 return 1;
160             }
161              
162             sub event_tap {
163 2949     2949 0 6764 my ($self, $f, $num) = @_;
164              
165 2949         4643 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     10026 push @tap => $self->plan_tap($f) if $f->{plan} && !$self->{+MADE_ASSERTION};
170              
171             # The assertion is most important, if present.
172 2949 100       6793 if ($f->{assert}) {
173 1327         3458 push @tap => $self->assert_tap($f, $num);
174 1327 100 100     4036 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       6626 push @tap => $self->error_tap($f) if $f->{errors};
179              
180             # Now lets see the diagnostics messages
181 2949 100       8189 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     10589 push @tap => $self->plan_tap($f) if $self->{+MADE_ASSERTION} && $f->{plan};
186              
187             # Bail out
188 2949 100       7264 push @tap => $self->halt_tap($f) if $f->{control}->{halt};
189              
190 2949 100       10744 return @tap if @tap;
191 217 100       1138 return @tap if $f->{control}->{halt};
192 215 100       873 return @tap if grep { $f->{$_} } qw/assert plan info errors/;
  860         2293  
193              
194             # Use the summary as a fallback if nothing else is usable.
195 211         1809 return $self->summary_tap($f, $num);
196             }
197              
198             sub error_tap {
199 4     4 0 126 my $self = shift;
200 4         11 my ($f) = @_;
201              
202 4 50 33     16 my $IO = ($f->{amnesty} && @{$f->{amnesty}}) ? OUT_STD : OUT_ERR;
203              
204             return map {
205 6         13 my $details = $_->{details};
206              
207 6         10 my $msg;
208 6 100       16 if (ref($details)) {
209 1         8 require Data::Dumper;
210 1         5 my $dumper = Data::Dumper->new([$details])->Indent(2)->Terse(1)->Pad('# ')->Useqq(1)->Sortkeys(1);
211 1         47 chomp($msg = $dumper->Dump);
212             }
213             else {
214 5         13 chomp($msg = $details);
215 5         23 $msg =~ s/^/# /;
216 5         13 $msg =~ s/\n/\n# /g;
217             }
218              
219 6         54 [$IO, "$msg\n"];
220 4         10 } @{$f->{errors}};
  4         14  
221             }
222              
223             sub plan_tap {
224 498     498 0 1144 my $self = shift;
225 498         1306 my ($f) = @_;
226 498 100       2737 my $plan = $f->{plan} or return;
227              
228 497 100       1676 return if $plan->{none};
229              
230 495 100       1599 if ($plan->{skip}) {
231 19 100       96 my $reason = $plan->{details} or return [OUT_STD, "1..0 # SKIP\n"];
232 16         352 chomp($reason);
233 16         119 return [OUT_STD, '1..0 # SKIP ' . $reason . "\n"];
234             }
235              
236 476         2412 return [OUT_STD, "1.." . $plan->{count} . "\n"];
237             }
238              
239 736     736 0 1496 sub no_subtest_space { 0 }
240             sub assert_tap {
241 1367     1367 0 2222 my $self = shift;
242 1367         2273 my ($f, $num) = @_;
243              
244 1367 50       3009 my $assert = $f->{assert} or return;
245 1367         2156 my $pass = $assert->{pass};
246 1367         2112 my $name = $assert->{details};
247              
248 1367 100       2782 my $ok = $pass ? 'ok' : 'not ok';
249 1367 100 66     5585 $ok .= " $num" if $num && !$self->{+NO_NUMBERS};
250              
251             # The regex form is ~250ms, the index form is ~50ms
252 1367         2098 my @extra;
253 1367 100 66     7983 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       3104 my $extra_space = @extra ? ' ' x (length($ok) + 2) : '';
259 1367         2138 my $extra_indent = '';
260              
261 1367         2231 my ($directives, $reason, $is_skip);
262 1367 100       3190 if ($f->{amnesty}) {
263 329         476 my %directives;
264              
265 329         470 for my $am (@{$f->{amnesty}}) {
  329         820  
266 342 50       699 next if $am->{inherited};
267 342 50       706 my $tag = $am->{tag} or next;
268 342 100       691 $is_skip = 1 if $tag eq 'skip';
269              
270 342   100     1384 $directives{$tag} ||= $am->{details};
271             }
272              
273 329         511 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         1033 my @order = grep { !$seen{$_}++ } sort { lc $b cmp lc $a } keys %directives;
  339         1400  
  10         63  
278              
279 329         902 $directives = ' # ' . join ' & ' => @order;
280              
281 329         595 for my $tag ('skip', @order) {
282 630 100 100     2070 next unless defined($directives{$tag}) && length($directives{$tag});
283 272         482 $reason = $directives{$tag};
284 272         696 last;
285             }
286             }
287              
288 1367 100 100     5666 $ok .= " - $name" if defined $name && !($is_skip && !$name);
      100        
289              
290 1367         2000 my @subtap;
291 1367 100 100     3728 if ($f->{parent} && $f->{parent}->{buffered}) {
292 89         164 $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     426 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         153 my $count = 0;
304             @subtap = map {
305 766         1315 my $f2 = $_;
306              
307             # Bump the count for any event that should bump it.
308 766 100       1656 $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         1749 map { $_->[1] =~ s/^(.*\S.*)$/ $1/mg; $_ } $self->event_tap($f2, $count);
  892         5811  
  892         2562  
313 89         144 } @{$f->{parent}->{children}};
  89         254  
314              
315 89         273 push @subtap => [OUT_STD, "}\n"];
316             }
317              
318 1367 100       2976 if ($directives) {
319 329 100       743 $directives = ' # TODO & SKIP' if $directives eq ' # TODO & skip';
320 329         484 $ok .= $directives;
321 329 100       746 $ok .= " $reason" if defined($reason);
322             }
323              
324 1367 100       3282 $extra_space = ' ' if $self->no_subtest_space;
325              
326 1367         4322 my @out = ([OUT_STD, "$ok\n"]);
327 1367 100       3181 push @out => map {[OUT_STD, "${extra_indent}#${extra_space}$_\n"]} @extra if @extra;
  31         120  
328 1367         2354 push @out => @subtap;
329              
330 1367         3799 return @out;
331             }
332              
333             sub debug_tap {
334 23     23 0 65 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         48 my $name = $f->{assert}->{details};
340 23         35 my $trace = $f->{trace};
341              
342 23         53 my $debug = "[No trace info available]";
343 23 100       76 if ($trace->{details}) {
    100          
344 1         3 $debug = $trace->{details};
345             }
346             elsif ($trace->{frame}) {
347 19         28 my ($pkg, $file, $line) = @{$trace->{frame}};
  19         48  
348 19 50 33     111 $debug = "at $file line $line." if $file && $line;
349             }
350              
351 23 100 100     69 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       86 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     71 my $IO = $f->{amnesty} && @{$f->{amnesty}} ? OUT_STD : OUT_ERR;
362              
363 23         103 return [$IO, $msg];
364             }
365              
366             sub halt_tap {
367 13     13 0 41 my ($self, $f) = @_;
368              
369 13 100 100     75 return if $f->{trace}->{nested} && !$f->{trace}->{buffered};
370 10         38 my $details = $f->{control}->{details};
371              
372 10 100 100     71 return [OUT_STD, "Bail out!\n"] unless defined($details) && length($details);
373 7         51 return [OUT_STD, "Bail out! $details\n"];
374             }
375              
376             sub info_tap {
377 917     917 0 1735 my ($self, $f) = @_;
378              
379             return map {
380 927         1725 my $details = $_->{details};
381 927         1557 my $table = $_->{table};
382              
383 927 100 100     3396 my $IO = $_->{debug} && !($f->{amnesty} && @{$f->{amnesty}}) ? OUT_ERR : OUT_STD;
384              
385 927         1426 my $msg;
386 927 100 100     2828 if ($table && $self->supports_tables) {
    100          
387 36         21866 $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         28 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         9 my $dumper = Data::Dumper->new([$details])->Indent(2)->Terse(1)->Pad('# ')->Useqq(1)->Sortkeys(1);
400 2         112 chomp($msg = $dumper->Dump);
401             }
402             else {
403 921         2170 chomp($msg = $details);
404 921         4817 $msg =~ s/^/# /;
405 921         3263 $msg =~ s/\n/\n# /g;
406             }
407              
408 927         5161 [$IO, "$msg\n"];
409 917         1427 } @{$f->{info}};
  917         2220  
410             }
411              
412             sub summary_tap {
413 214     214 0 904 my ($self, $f, $num) = @_;
414              
415 214 100       1290 return if $f->{about}->{no_display};
416              
417 213 100       1789 my $summary = $f->{about}->{details} or return;
418 2         5 chomp($summary);
419 2         13 $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         12 my ($f) = @_;
427              
428 4         15 my $term = Term::Table::Util::term_size();
429 4   100     30 my $nesting = 2 + (($f->{trace}->{nested} || 0) * 4); # 4 spaces per level, also '# ' prefix
430 4         8 my $total = $term - $nesting;
431              
432             # Sane minimum width, any smaller and we are asking for pain
433 4 50       13 return 50 if $total < 50;
434              
435 4         20 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