File Coverage

blib/lib/Test2/API/InterceptResult/Event.pm
Criterion Covered Total %
statement 300 321 93.4
branch 175 212 82.5
condition 32 51 62.7
subroutine 82 88 93.1
pod 66 69 95.6
total 655 741 88.3


line stmt bran cond sub pod time code
1             package Test2::API::InterceptResult::Event;
2 35     35   1479 use strict;
  35         100  
  35         1083  
3 35     35   191 use warnings;
  35         69  
  35         1599  
4              
5             our $VERSION = '1.302180';
6              
7 35     35   376 use List::Util qw/first uniq/;
  35         88  
  35         2137  
8 35     35   223 use Test2::Util qw/pkg_to_file/;
  35         77  
  35         1820  
9 35     35   252 use Scalar::Util qw/reftype blessed/;
  35         97  
  35         1902  
10              
11 35     35   1642 use Storable qw/dclone/;
  35         6945  
  35         1731  
12 35     35   235 use Carp qw/confess croak/;
  35         74  
  35         1978  
13              
14 35     35   15707 use Test2::API::InterceptResult::Facet;
  35         108  
  35         1159  
15 35     35   15425 use Test2::API::InterceptResult::Hub;
  35         96  
  35         1148  
16              
17 35         151 use Test2::Util::HashBase qw{
18             +causes_failure
19            
20            
21 35     35   234 };
  35         84  
22              
23             my %FACETS;
24             BEGIN {
25 35     35   186 local $@;
26 35         108 local *plugins;
27 35 50       92 if (eval { require Module::Pluggable; 1 }) {
  35         5986  
  0         0  
28             Module::Pluggable->import(
29             # We will replace the sub later
30             require => 1,
31 0         0 on_require_error => sub { 1 },
32 0         0 search_path => ['Test2::EventFacet'],
33             max_depth => 3,
34             min_depth => 3,
35             );
36              
37 0         0 for my $facet_type (__PACKAGE__->plugins) {
38 0         0 my ($key, $list);
39 0         0 eval {
40 0         0 $key = $facet_type->facet_key;
41 0         0 $list = $facet_type->is_list;
42             };
43 0 0 0     0 next unless $key && defined($list);
44              
45 0         0 $FACETS{$key} = {list => $list, class => $facet_type, loaded => 1};
46             }
47             }
48              
49 35         156073 $FACETS{__GENERIC__} = {class => 'Test2::API::InterceptResult::Facet', loaded => 1};
50             }
51              
52 1     1 0 9 sub facet_map { \%FACETS }
53              
54             sub facet_info {
55 805     805 0 1240 my $facet = pop;
56              
57 805 100       1915 return $FACETS{$facet} if exists $FACETS{$facet};
58              
59 29         83 my $mname = ucfirst(lc($facet));
60 29         85 $mname =~ s/s$//;
61              
62 29         68 for my $name ($mname, "${mname}s") {
63 32         80 my $file = "Test2/EventFacet/$name.pm";
64 32         73 my $class = "Test2::EventFacet::$name";
65              
66 32         49 local $@;
67 32         58 my $ok = eval {
68 32         1822 require $file;
69              
70 26         255 my $key = $class->facet_key;
71 26         141 my $list = $class->is_list;
72              
73 26         102 $FACETS{$key} = {list => $list, class => $class, loaded => 1};
74 26 50       74 $FACETS{$facet} = $FACETS{$key} if $facet ne $key;
75              
76 26         51 1;
77             };
78              
79 32 50 66     238 return $FACETS{$facet} if $ok && $FACETS{$facet};
80             }
81              
82 3         15 return $FACETS{$facet} = $FACETS{__GENERIC__};
83             }
84              
85             sub init {
86 118     118 0 191 my $self = shift;
87              
88 118   100     366 my $rc = $self->{+RESULT_CLASS} ||= 'Test2::API::InterceptResult';
89 118         295 my $rc_file = pkg_to_file($rc);
90 118 100       1423 require($rc_file) unless $INC{$rc_file};
91              
92 118   100     332 my $fd = $self->{+FACET_DATA} ||= {};
93              
94 118         408 for my $facet (keys %$fd) {
95 355         656 my $finfo = $self->facet_info($facet);
96 355         602 my $is_list = $finfo->{list};
97 355 100       671 next unless defined $is_list;
98              
99 349         718 my $type = reftype($fd->{$facet});
100              
101 349 100       632 if ($is_list) {
102 128 100       371 confess "Facet '$facet' is a list facet, but got '$type' instead of an arrayref"
103             unless $type eq 'ARRAY';
104              
105 127         200 for my $item (@{$fd->{$facet}}) {
  127         275  
106 176         368 my $itype = reftype($item);
107 176 100       439 next if $itype eq 'HASH';
108              
109 1         93 confess "Got item type '$itype' in list-facet '$facet', all items must be hashrefs";
110             }
111             }
112             else {
113 221 100       771 confess "Facet '$facet' is an only-one facet, but got '$type' instead of a hashref"
114             unless $type eq 'HASH';
115             }
116             }
117             }
118              
119             sub clone {
120 18     18 1 33 my $self = shift;
121 18         48 my $class = blessed($self);
122              
123 18         71 my %data = %$self;
124              
125 18         563 $data{+FACET_DATA} = dclone($data{+FACET_DATA});
126              
127 18         97 return bless(\%data, $class);
128             }
129              
130             sub _facet_class {
131 217     217   323 my $self = shift;
132 217         372 my ($name) = @_;
133              
134 217         442 my $spec = $self->facet_info($name);
135 217         421 my $class = $spec->{class};
136 217 50       461 unless ($spec->{loaded}) {
137 0         0 my $file = pkg_to_file($class);
138 0 0       0 require $file unless $INC{$file};
139 0         0 $spec->{loaded} = 1;
140             }
141              
142 217         427 return $class;
143             }
144              
145             sub the_facet {
146 155     155 1 252 my $self = shift;
147 155         287 my ($name) = @_;
148              
149 155 100       514 return undef unless defined $self->{+FACET_DATA}->{$name};
150              
151 131         219 my $data = $self->{+FACET_DATA}->{$name};
152              
153 131 50       414 my $type = reftype($data) or confess "Facet '$name' has a value that is not a reference, this should not happen";
154              
155 131 100       381 return $self->_facet_class($name)->new(%{dclone($data)})
  127         3398  
156             if $type eq 'HASH';
157              
158 4 50       11 if ($type eq 'ARRAY') {
159 4 50       11 return undef unless @$data;
160 4 100       188 croak "'the_facet' called for facet '$name', but '$name' has '" . @$data . "' items" if @$data != 1;
161 3         9 return $self->_facet_class($name)->new(%{dclone($data->[0])});
  3         45  
162             }
163              
164 0         0 die "Invalid facet data type: $type";
165             }
166              
167             sub facet {
168 131     131 1 223 my $self = shift;
169 131         248 my ($name) = @_;
170              
171 131 100       607 return () unless exists $self->{+FACET_DATA}->{$name};
172              
173 87         159 my $data = $self->{+FACET_DATA}->{$name};
174              
175 87 50       274 my $type = reftype($data) or confess "Facet '$name' has a value that is not a reference, this should not happen";
176              
177 87         144 my @out;
178 87 100       246 @out = ($data) if $type eq 'HASH';
179 87 100       233 @out = (@$data) if $type eq 'ARRAY';
180              
181 87         212 my $class = $self->_facet_class($name);
182              
183 87         175 return map { $class->new(%{dclone($_)}) } @out;
  216         322  
  216         3621  
184             }
185              
186             sub causes_failure {
187 55     55 1 92 my $self = shift;
188              
189             return $self->{+CAUSES_FAILURE}
190 55 100       158 if exists $self->{+CAUSES_FAILURE};
191              
192 37         135 my $hub = Test2::API::InterceptResult::Hub->new();
193 37         147 $hub->process($self);
194              
195 37 100       115 return $self->{+CAUSES_FAILURE} = ($hub->is_passing ? 0 : 1);
196             }
197              
198 31     31 1 83 sub causes_fail { shift->causes_failure }
199              
200 2     2 1 13 sub trace { $_[0]->facet('trace') }
201 144     144 1 345 sub the_trace { $_[0]->the_facet('trace') }
202 82 100   82 1 160 sub frame { my $t = $_[0]->the_trace or return undef; $t->{frame} || undef }
  64 50       299  
203 24 100   24 1 55 sub trace_details { my $t = $_[0]->the_trace or return undef; $t->{details} || undef }
  19 100       104  
204 3 100   3 1 9 sub trace_package { my $f = $_[0]->frame or return undef; $f->[0] || undef }
  2 100       20  
205 24 100   24 1 58 sub trace_file { my $f = $_[0]->frame or return undef; $f->[1] || undef }
  19 100       85  
206 25 100   25 1 73 sub trace_line { my $f = $_[0]->frame or return undef; $f->[2] || undef }
  20 100       89  
207 24 100   24 1 53 sub trace_subname { my $f = $_[0]->frame or return undef; $f->[3] || undef }
  19 100       78  
208 3 100   3 1 10 sub trace_tool { my $f = $_[0]->frame or return undef; $f->[3] || undef }
  2 100       15  
209              
210 36 50   36 1 93 sub trace_signature { my $t = $_[0]->the_trace or return undef; Test2::EventFacet::Trace::signature($t) || undef }
  36 50       89  
211              
212             sub brief {
213 30     30 1 56 my $self = shift;
214              
215 30         74 my @try = qw{
216             bailout_brief
217             error_brief
218             assert_brief
219             plan_brief
220             };
221              
222 30         61 for my $meth (@try) {
223 79 100       187 my $got = $self->$meth or next;
224 27         117 return $got;
225             }
226              
227 3         21 return;
228             }
229              
230             sub flatten {
231 13     13 1 44 my $self = shift;
232 13         30 my %params = @_;
233              
234 13         26 my $todo = {%{$self->{+FACET_DATA}}};
  13         50  
235 13         31 delete $todo->{hubs};
236 13         20 delete $todo->{meta};
237 13         24 delete $todo->{trace};
238              
239 13         30 my $out = $self->summary;
240 13         27 delete $out->{brief};
241 13         29 delete $out->{facets};
242 13         26 delete $out->{trace_tool};
243 13 100       35 delete $out->{trace_details} unless defined($out->{trace_details});
244              
245 13 100       67 for my $tagged (grep { my $finfo = $self->facet_info($_); $finfo->{list} && $finfo->{class}->can('tag') } keys %FACETS, keys %$todo) {
  221         355  
  221         754  
246 57 100       163 my $set = delete $todo->{$tagged} or next;
247              
248 13         24 my $fd = $self->{+FACET_DATA};
249 13         32 my $has_assert = $self->has_assert;
250 13         28 my $has_parent = $self->has_subtest;
251 13   66     31 my $has_fatal_error = $self->has_errors && grep { $_->{fail} } $self->errors;
252              
253 13 100 100     90 next if $tagged eq 'amnesty' && !($has_assert || $has_parent || $has_fatal_error);
      100        
254              
255 12         26 for my $item (@$set) {
256 34 100       48 push @{$out->{lc($item->{tag})}} => $item->{fail} ? "FATAL: $item->{details}" : $item->{details};
  34         120  
257             }
258             }
259              
260 13 100       49 if (my $assert = delete $todo->{assert}) {
261 7         18 $out->{pass} = $assert->{pass};
262 7         18 $out->{name} = $assert->{details};
263             }
264              
265 13 100       34 if (my $parent = delete $todo->{parent}) {
266 2 50       7 delete $out->{subtest}->{bailed_out} unless defined $out->{subtest}->{bailed_out};
267 2 50       6 delete $out->{subtest}->{skip_reason} unless defined $out->{subtest}->{skip_reason};
268              
269 2 50       6 if (my $res = $self->subtest_result) {
270 2         9 my $state = $res->state;
271 2         10 delete $state->{$_} for grep { !defined($state->{$_}) } keys %$state;
  14         34  
272 2         7 $out->{subtest} = $state;
273             $out->{subevents} = $res->flatten(%params)
274 2 100       12 if $params{include_subevents};
275             }
276             }
277              
278 13 100       44 if (my $control = delete $todo->{control}) {
279 3 100       12 if ($control->{halt}) {
    50          
280 1   50     4 $out->{bailed_out} = $control->{details} || 1;
281             }
282             elsif(defined $control->{details}) {
283 2         7 $out->{control} = $control->{details};
284             }
285             }
286              
287 13 100       31 if (my $plan = delete $todo->{plan}) {
288 1         4 $out->{plan} = $self->plan_brief;
289 1         6 $out->{plan} =~ s/^PLAN\s*//;
290             }
291              
292 13         34 for my $other (keys %$todo) {
293 8 50       22 my $data = $todo->{$other} or next;
294              
295 8 100       32 if (reftype($data) eq 'ARRAY') {
296 2 50 33     6 if (!$out->{$other} || reftype($out->{$other}) eq 'ARRAY') {
297 2         6 for my $item (@$data) {
298 2 50       5 push @{$out->{$other}} => $item->{details} if defined $item->{details};
  2         9  
299             }
300             }
301             }
302             else {
303 6 50 33     74 $out->{$other} = $data->{details} if defined($data->{details}) && !defined($out->{$other});
304             }
305             }
306              
307 13 100       33 if (my $fields = $params{fields}) {
308 1 50       3 $out = { map {exists($out->{$_}) ? ($_ => $out->{$_}) : ()} @$fields };
  2         13  
309             }
310              
311 13 100       33 if (my $remove = $params{remove}) {
312 1         6 delete $out->{$_} for @$remove;
313             }
314              
315 13         126 return $out;
316             }
317              
318             sub summary {
319 21     21 1 50 my $self = shift;
320 21         47 my %params = @_;
321              
322             my $out = {
323             brief => $self->brief || '',
324              
325             causes_failure => $self->causes_failure,
326              
327             trace_line => $self->trace_line,
328             trace_file => $self->trace_file,
329             trace_tool => $self->trace_subname,
330             trace_details => $self->trace_details,
331              
332 21   100     48 facets => [ sort keys(%{$self->{+FACET_DATA}}) ],
  21         168  
333             };
334              
335 21 100       76 if (my $fields = $params{fields}) {
336 1 50       7 $out = { map {exists($out->{$_}) ? ($_ => $out->{$_}) : ()} @$fields };
  2         13  
337             }
338              
339 21 100       52 if (my $remove = $params{remove}) {
340 1         10 delete $out->{$_} for @$remove;
341             }
342              
343 21         85 return $out;
344             }
345              
346 87 100   87 1 301 sub has_assert { $_[0]->{+FACET_DATA}->{assert} ? 1 : 0 }
347 0     0 1 0 sub the_assert { $_[0]->the_facet('assert') }
348 4     4 1 12 sub assert { $_[0]->facet('assert') }
349              
350             sub assert_brief {
351 24     24 1 44 my $self = shift;
352              
353 24         37 my $fd = $self->{+FACET_DATA};
354 24 100       66 my $as = $fd->{assert} or return;
355 18         29 my $am = $fd->{amnesty};
356              
357 18 100       45 my $out = $as->{pass} ? "PASS" : "FAIL";
358 18 100       42 $out .= " with amnesty" if $am;
359 18         50 return $out;
360             }
361              
362 48 100   48 1 165 sub has_subtest { $_[0]->{+FACET_DATA}->{parent} ? 1 : 0 }
363 2     2 1 11 sub the_subtest { $_[0]->the_facet('parent') }
364 2     2 1 9 sub subtest { $_[0]->facet('parent') }
365              
366             sub subtest_result {
367 9     9 1 18 my $self = shift;
368              
369 9 100       32 my $parent = $self->{+FACET_DATA}->{parent} or return;
370 7   50     20 my $children = $parent->{children} || [];
371              
372             $children = $self->{+RESULT_CLASS}->new(@$children)->upgrade
373 7 100 66     52 unless blessed($children) && $children->isa($self->{+RESULT_CLASS});
374              
375 7         41 return $children;
376             }
377              
378 29 100   29 1 80 sub has_bailout { $_[0]->bailout ? 1 : 0 }
379 0     0 1 0 sub the_bailout { my ($b) = $_[0]->bailout; $b }
  0         0  
380              
381             sub bailout {
382 66     66 1 98 my $self = shift;
383 66 100       260 my $control = $self->{+FACET_DATA}->{control} or return;
384 12 100       66 return $control if $control->{halt};
385 2         9 return;
386             }
387              
388             sub bailout_brief {
389 33     33 1 61 my $self = shift;
390 33 100       64 my $bo = $self->bailout or return;
391              
392 7 50       21 my $reason = $bo->{details} or return "BAILED OUT";
393 7         33 return "BAILED OUT: $reason";
394             }
395              
396             sub bailout_reason {
397 2     2 1 6 my $self = shift;
398 2 100       6 my $bo = $self->bailout or return;
399 1   50     8 return $bo->{details} || '';
400             }
401              
402 35 100   35 1 113 sub has_plan { $_[0]->{+FACET_DATA}->{plan} ? 1 : 0 }
403 0     0 1 0 sub the_plan { $_[0]->the_facet('plan') }
404 2     2 1 7 sub plan { $_[0]->facet('plan') }
405              
406             sub plan_brief {
407 13     13 1 24 my $self = shift;
408              
409 13 100       46 my $plan = $self->{+FACET_DATA}->{plan} or return;
410              
411 9         20 my $base = $self->_plan_brief($plan);
412              
413 9 100       33 my $reason = $plan->{details} or return $base;
414 4         25 return "$base: $reason";
415             }
416              
417             sub _plan_brief {
418 9     9   17 my $self = shift;
419 9         16 my ($plan) = @_;
420              
421 9 100       22 return 'NO PLAN' if $plan->{none};
422 8 100 100     33 return "SKIP ALL" if $plan->{skip} || !$plan->{count};
423 6         19 return "PLAN $plan->{count}";
424             }
425              
426 2 100   2 1 19 sub has_amnesty { $_[0]->{+FACET_DATA}->{amnesty} ? 1 : 0 }
427 0     0 1 0 sub the_amnesty { $_[0]->the_facet('amnesty') }
428 22     22 1 64 sub amnesty { $_[0]->facet('amnesty') }
429 2     2 1 7 sub amnesty_reasons { map { $_->{details} } $_[0]->amnesty }
  6         18  
430              
431 1 100   1 1 22 sub has_todos { &first(sub { uc($_->{tag}) eq 'TODO' }, $_[0]->amnesty) ? 1 : 0 }
  2     2   14  
432 4     4 1 13 sub todos { grep { uc($_->{tag}) eq 'TODO' } $_[0]->amnesty }
  12         36  
433 2 50   2 1 7 sub todo_reasons { map { $_->{details} || 'TODO' } $_[0]->todos }
  2         18  
434              
435 3 100   3 1 14 sub has_skips { &first(sub { uc($_->{tag}) eq 'SKIP' }, $_[0]->amnesty) ? 1 : 0 }
  2     2   12  
436 4     4 1 11 sub skips { grep { uc($_->{tag}) eq 'SKIP' } $_[0]->amnesty }
  12         34  
437 2 50   2 1 8 sub skip_reasons { map { $_->{details} || 'SKIP' } $_[0]->skips }
  2         20  
438              
439             my %TODO_OR_SKIP = (SKIP => 1, TODO => 1);
440 5 100   5 1 17 sub has_other_amnesty { &first( sub { !$TODO_OR_SKIP{uc($_->{tag})} }, $_[0]->amnesty) ? 1 : 0 }
  2     2   12  
441 4     4 1 11 sub other_amnesty { grep { !$TODO_OR_SKIP{uc($_->{tag})} } $_[0]->amnesty }
  12         46  
442 2 0 33 2 1 7 sub other_amnesty_reasons { map { $_->{details} || $_->{tag} || 'AMNESTY' } $_[0]->other_amnesty }
  2         14  
443              
444 50 100   50 1 171 sub has_errors { $_[0]->{+FACET_DATA}->{errors} ? 1 : 0 }
445 0     0 1 0 sub the_errors { $_[0]->the_facet('errors') }
446 26     26 1 67 sub errors { $_[0]->facet('errors') }
447 10 0 33 10 1 29 sub error_messages { map { $_->{details} || $_->{tag} || 'ERROR' } $_[0]->errors }
  6         64  
448              
449             sub error_brief {
450 30     30 1 52 my $self = shift;
451              
452 30 100       92 my $errors = $self->{+FACET_DATA}->{errors} or return;
453              
454 10 100       27 my $base = @$errors > 1 ? "ERRORS" : "ERROR";
455              
456 10 50       27 return $base unless @$errors;
457              
458 10         54 my ($msg, @extra) = split /[\n\r]+/, $errors->[0]->{details};
459              
460 10         28 my $out = "$base: $msg";
461              
462 10 100 100     51 $out .= " [...]" if @extra || @$errors > 1;
463              
464 10         44 return $out;
465             }
466              
467 34 100   34 1 135 sub has_info { $_[0]->{+FACET_DATA}->{info} ? 1 : 0 }
468 0     0 1 0 sub the_info { $_[0]->the_facet('info') }
469 64     64 1 161 sub info { $_[0]->facet('info') }
470 7     7 1 30 sub info_messages { map { $_->{details} } $_[0]->info }
  14         64  
471              
472 3 100   3 1 32 sub has_diags { &first(sub { uc($_->{tag}) eq 'DIAG' }, $_[0]->info) ? 1 : 0 }
  8     8   44  
473 10     10 1 27 sub diags { grep { uc($_->{tag}) eq 'DIAG' } $_[0]->info }
  20         75  
474 8 50   8 1 23 sub diag_messages { map { $_->{details} || 'DIAG' } $_[0]->diags }
  6         58  
475              
476 5 100   5 1 30 sub has_notes { &first(sub { uc($_->{tag}) eq 'NOTE' }, $_[0]->info) ? 1 : 0 }
  8     8   40  
477 10     10 1 27 sub notes { grep { uc($_->{tag}) eq 'NOTE' } $_[0]->info }
  20         80  
478 8 50   8 1 30 sub note_messages { map { $_->{details} || 'NOTE' } $_[0]->notes }
  6         55  
479              
480             my %NOTE_OR_DIAG = (NOTE => 1, DIAG => 1);
481 5 100   5 1 16 sub has_other_info { &first(sub { !$NOTE_OR_DIAG{uc($_->{tag})} }, $_[0]->info) ? 1 : 0 }
  2     2   13  
482 4     4 1 14 sub other_info { grep { !$NOTE_OR_DIAG{uc($_->{tag})} } $_[0]->info }
  12         37  
483 2 0 33 2 1 6 sub other_info_messages { map { $_->{details} || $_->{tag} || 'INFO' } $_[0]->other_info }
  2         15  
484              
485             1;
486              
487             __END__