| 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 |