1package Test2::API::InterceptResult::Event;
2use strict;
3use warnings;
4
5our $VERSION = '1.302199';
6
7use List::Util   qw/first/;
8use Test2::Util  qw/pkg_to_file/;
9use Scalar::Util qw/reftype blessed/;
10
11use Storable qw/dclone/;
12use Carp     qw/confess croak/;
13
14use Test2::API::InterceptResult::Facet;
15use Test2::API::InterceptResult::Hub;
16
17use Test2::Util::HashBase qw{
18    +causes_failure
19    <facet_data
20    <result_class
21};
22
23my %FACETS;
24BEGIN {
25    local $@;
26    local *plugins;
27    if (eval { require Module::Pluggable; 1 }) {
28        Module::Pluggable->import(
29            # We will replace the sub later
30            require          => 1,
31            on_require_error => sub { 1 },
32            search_path      => ['Test2::EventFacet'],
33            max_depth        => 3,
34            min_depth        => 3,
35        );
36
37        for my $facet_type (__PACKAGE__->plugins) {
38            my ($key, $list);
39            eval {
40                $key  = $facet_type->facet_key;
41                $list = $facet_type->is_list;
42            };
43            next unless $key && defined($list);
44
45            $FACETS{$key} = {list => $list, class => $facet_type, loaded => 1};
46        }
47    }
48
49    $FACETS{__GENERIC__} = {class => 'Test2::API::InterceptResult::Facet', loaded => 1};
50}
51
52sub facet_map { \%FACETS }
53
54sub facet_info {
55    my $facet = pop;
56
57    return $FACETS{$facet} if exists $FACETS{$facet};
58
59    my $mname = ucfirst(lc($facet));
60    $mname =~ s/s$//;
61
62    for my $name ($mname, "${mname}s") {
63        my $file  = "Test2/EventFacet/$name.pm";
64        my $class = "Test2::EventFacet::$name";
65
66        local $@;
67        my $ok = eval {
68            require $file;
69
70            my $key = $class->facet_key;
71            my $list = $class->is_list;
72
73            $FACETS{$key} = {list => $list, class => $class, loaded => 1};
74            $FACETS{$facet} = $FACETS{$key} if $facet ne $key;
75
76            1;
77        };
78
79        return $FACETS{$facet} if $ok && $FACETS{$facet};
80    }
81
82    return $FACETS{$facet} = $FACETS{__GENERIC__};
83}
84
85sub init {
86    my $self = shift;
87
88    my $rc = $self->{+RESULT_CLASS} ||= 'Test2::API::InterceptResult';
89    my $rc_file = pkg_to_file($rc);
90    require($rc_file) unless $INC{$rc_file};
91
92    my $fd = $self->{+FACET_DATA} ||= {};
93
94    for my $facet (keys %$fd) {
95        my $finfo = $self->facet_info($facet);
96        my $is_list = $finfo->{list};
97        next unless defined $is_list;
98
99        my $type = reftype($fd->{$facet});
100
101        if ($is_list) {
102            confess "Facet '$facet' is a list facet, but got '$type' instead of an arrayref"
103                unless $type eq 'ARRAY';
104
105            for my $item (@{$fd->{$facet}}) {
106                my $itype = reftype($item);
107                next if $itype eq 'HASH';
108
109                confess "Got item type '$itype' in list-facet '$facet', all items must be hashrefs";
110            }
111        }
112        else {
113            confess "Facet '$facet' is an only-one facet, but got '$type' instead of a hashref"
114                unless $type eq 'HASH';
115        }
116    }
117}
118
119sub clone {
120    my $self = shift;
121    my $class = blessed($self);
122
123    my %data = %$self;
124
125    $data{+FACET_DATA} = dclone($data{+FACET_DATA});
126
127    return bless(\%data, $class);
128}
129
130sub _facet_class {
131    my $self = shift;
132    my ($name) = @_;
133
134    my $spec  = $self->facet_info($name);
135    my $class = $spec->{class};
136    unless ($spec->{loaded}) {
137        my $file = pkg_to_file($class);
138        require $file unless $INC{$file};
139        $spec->{loaded} = 1;
140    }
141
142    return $class;
143}
144
145sub the_facet {
146    my $self = shift;
147    my ($name) = @_;
148
149    return undef unless defined $self->{+FACET_DATA}->{$name};
150
151    my $data = $self->{+FACET_DATA}->{$name};
152
153    my $type = reftype($data) or confess "Facet '$name' has a value that is not a reference, this should not happen";
154
155    return $self->_facet_class($name)->new(%{dclone($data)})
156        if $type eq 'HASH';
157
158    if ($type eq 'ARRAY') {
159        return undef unless @$data;
160        croak "'the_facet' called for facet '$name', but '$name' has '" . @$data . "' items" if @$data != 1;
161        return $self->_facet_class($name)->new(%{dclone($data->[0])});
162    }
163
164    die "Invalid facet data type: $type";
165}
166
167sub facet {
168    my $self = shift;
169    my ($name) = @_;
170
171    return () unless exists $self->{+FACET_DATA}->{$name};
172
173    my $data = $self->{+FACET_DATA}->{$name};
174
175    my $type = reftype($data) or confess "Facet '$name' has a value that is not a reference, this should not happen";
176
177    my @out;
178    @out = ($data)  if $type eq 'HASH';
179    @out = (@$data) if $type eq 'ARRAY';
180
181    my $class = $self->_facet_class($name);
182
183    return map { $class->new(%{dclone($_)}) } @out;
184}
185
186sub causes_failure {
187    my $self = shift;
188
189    return $self->{+CAUSES_FAILURE}
190        if exists $self->{+CAUSES_FAILURE};
191
192    my $hub = Test2::API::InterceptResult::Hub->new();
193    $hub->process($self);
194
195    return $self->{+CAUSES_FAILURE} = ($hub->is_passing ? 0 : 1);
196}
197
198sub causes_fail { shift->causes_failure }
199
200sub trace         { $_[0]->facet('trace') }
201sub the_trace     { $_[0]->the_facet('trace') }
202sub frame         { my $t = $_[0]->the_trace or return undef; $t->{frame}   || undef }
203sub trace_details { my $t = $_[0]->the_trace or return undef; $t->{details} || undef }
204sub trace_stamp   { my $f = $_[0]->the_trace or return undef; $f->{stamp}   || undef }
205sub trace_package { my $f = $_[0]->frame     or return undef; $f->[0]       || undef }
206sub trace_file    { my $f = $_[0]->frame     or return undef; $f->[1]       || undef }
207sub trace_line    { my $f = $_[0]->frame     or return undef; $f->[2]       || undef }
208sub trace_subname { my $f = $_[0]->frame     or return undef; $f->[3]       || undef }
209sub trace_tool    { my $f = $_[0]->frame     or return undef; $f->[3]       || undef }
210
211sub trace_signature { my $t = $_[0]->the_trace or return undef; Test2::EventFacet::Trace::signature($t) || undef }
212
213sub brief {
214    my $self = shift;
215
216    my @try = qw{
217        bailout_brief
218        error_brief
219        assert_brief
220        plan_brief
221    };
222
223    for my $meth (@try) {
224        my $got = $self->$meth or next;
225        return $got;
226    }
227
228    return;
229}
230
231sub flatten {
232    my $self = shift;
233    my %params = @_;
234
235    my $todo = {%{$self->{+FACET_DATA}}};
236    delete $todo->{hubs};
237    delete $todo->{meta};
238    delete $todo->{trace};
239
240    my $out = $self->summary;
241    delete $out->{brief};
242    delete $out->{facets};
243    delete $out->{trace_tool};
244    delete $out->{trace_details} unless defined($out->{trace_details});
245
246    for my $tagged (grep { my $finfo = $self->facet_info($_); $finfo->{list} && $finfo->{class}->can('tag') } keys %FACETS, keys %$todo) {
247        my $set = delete $todo->{$tagged} or next;
248
249        my $fd = $self->{+FACET_DATA};
250        my $has_assert = $self->has_assert;
251        my $has_parent = $self->has_subtest;
252        my $has_fatal_error = $self->has_errors && grep { $_->{fail} } $self->errors;
253
254        next if $tagged eq 'amnesty' && !($has_assert || $has_parent || $has_fatal_error);
255
256        for my $item (@$set) {
257            push @{$out->{lc($item->{tag})}} => $item->{fail} ? "FATAL: $item->{details}" : $item->{details};
258        }
259    }
260
261    if (my $assert = delete $todo->{assert}) {
262        $out->{pass} = $assert->{pass};
263        $out->{name} = $assert->{details};
264    }
265
266    if (my $parent = delete $todo->{parent}) {
267        delete $out->{subtest}->{bailed_out}  unless defined $out->{subtest}->{bailed_out};
268        delete $out->{subtest}->{skip_reason} unless defined $out->{subtest}->{skip_reason};
269
270        if (my $res = $self->subtest_result) {
271            my $state = $res->state;
272            delete $state->{$_} for grep { !defined($state->{$_}) } keys %$state;
273            $out->{subtest} = $state;
274            $out->{subevents} = $res->flatten(%params)
275                if $params{include_subevents};
276        }
277    }
278
279    if (my $control = delete $todo->{control}) {
280        if ($control->{halt}) {
281            $out->{bailed_out} = $control->{details} || 1;
282        }
283        elsif(defined $control->{details}) {
284            $out->{control} = $control->{details};
285        }
286    }
287
288    if (my $plan = delete $todo->{plan}) {
289        $out->{plan} = $self->plan_brief;
290        $out->{plan} =~ s/^PLAN\s*//;
291    }
292
293    for my $other (keys %$todo) {
294        my $data = $todo->{$other} or next;
295
296        if (reftype($data) eq 'ARRAY') {
297            if (!$out->{$other} || reftype($out->{$other}) eq 'ARRAY') {
298                for my $item (@$data) {
299                    push @{$out->{$other}} => $item->{details} if defined $item->{details};
300                }
301            }
302        }
303        else {
304            $out->{$other} = $data->{details} if defined($data->{details}) && !defined($out->{$other});
305        }
306    }
307
308    if (my $fields = $params{fields}) {
309        $out = { map {exists($out->{$_}) ? ($_ => $out->{$_}) : ()} @$fields };
310    }
311
312    if (my $remove = $params{remove}) {
313        delete $out->{$_} for @$remove;
314    }
315
316    return $out;
317}
318
319sub summary {
320    my $self = shift;
321    my %params = @_;
322
323    my $out = {
324        brief => $self->brief || '',
325
326        causes_failure => $self->causes_failure,
327
328        trace_line    => $self->trace_line,
329        trace_file    => $self->trace_file,
330        trace_tool    => $self->trace_subname,
331        trace_details => $self->trace_details,
332
333        facets => [ sort keys(%{$self->{+FACET_DATA}}) ],
334    };
335
336    if (my $fields = $params{fields}) {
337        $out = { map {exists($out->{$_}) ? ($_ => $out->{$_}) : ()} @$fields };
338    }
339
340    if (my $remove = $params{remove}) {
341        delete $out->{$_} for @$remove;
342    }
343
344    return $out;
345}
346
347sub has_assert { $_[0]->{+FACET_DATA}->{assert} ? 1 : 0 }
348sub the_assert { $_[0]->the_facet('assert') }
349sub assert     { $_[0]->facet('assert') }
350
351sub assert_brief {
352    my $self = shift;
353
354    my $fd = $self->{+FACET_DATA};
355    my $as = $fd->{assert} or return;
356    my $am = $fd->{amnesty};
357
358    my $out = $as->{pass} ? "PASS" : "FAIL";
359    $out .= " with amnesty" if $am;
360    return $out;
361}
362
363sub has_subtest { $_[0]->{+FACET_DATA}->{parent} ? 1 : 0 }
364sub the_subtest { $_[0]->the_facet('parent') }
365sub subtest     { $_[0]->facet('parent') }
366
367sub subtest_result {
368    my $self = shift;
369
370    my $parent = $self->{+FACET_DATA}->{parent} or return;
371    my $children = $parent->{children} || [];
372
373    $children = $self->{+RESULT_CLASS}->new(@$children)->upgrade
374        unless blessed($children) && $children->isa($self->{+RESULT_CLASS});
375
376    return $children;
377}
378
379sub has_bailout { $_[0]->bailout ? 1 : 0 }
380sub the_bailout { my ($b) = $_[0]->bailout; $b }
381
382sub bailout {
383    my $self = shift;
384    my $control = $self->{+FACET_DATA}->{control} or return;
385    return $control if $control->{halt};
386    return;
387}
388
389sub bailout_brief {
390    my $self = shift;
391    my $bo = $self->bailout or return;
392
393    my $reason = $bo->{details} or return "BAILED OUT";
394    return "BAILED OUT: $reason";
395}
396
397sub bailout_reason {
398    my $self = shift;
399    my $bo = $self->bailout or return;
400    return $bo->{details} || '';
401}
402
403sub has_plan { $_[0]->{+FACET_DATA}->{plan} ? 1 : 0 }
404sub the_plan { $_[0]->the_facet('plan') }
405sub plan     { $_[0]->facet('plan') }
406
407sub plan_brief {
408    my $self = shift;
409
410    my $plan = $self->{+FACET_DATA}->{plan} or return;
411
412    my $base = $self->_plan_brief($plan);
413
414    my $reason = $plan->{details} or return $base;
415    return "$base: $reason";
416}
417
418sub _plan_brief {
419    my $self = shift;
420    my ($plan) = @_;
421
422    return 'NO PLAN' if $plan->{none};
423    return "SKIP ALL" if $plan->{skip} || !$plan->{count};
424    return "PLAN $plan->{count}";
425}
426
427sub has_amnesty     { $_[0]->{+FACET_DATA}->{amnesty} ? 1 : 0 }
428sub the_amnesty     { $_[0]->the_facet('amnesty') }
429sub amnesty         { $_[0]->facet('amnesty') }
430sub amnesty_reasons { map { $_->{details} } $_[0]->amnesty }
431
432sub has_todos    { &first(sub { uc($_->{tag}) eq 'TODO' }, $_[0]->amnesty) ? 1 : 0 }
433sub todos        {       grep { uc($_->{tag}) eq 'TODO' }  $_[0]->amnesty          }
434sub todo_reasons {       map  { $_->{details} || 'TODO' }  $_[0]->todos            }
435
436sub has_skips    { &first(sub { uc($_->{tag}) eq 'SKIP' }, $_[0]->amnesty) ? 1 : 0 }
437sub skips        {       grep { uc($_->{tag}) eq 'SKIP' }  $_[0]->amnesty          }
438sub skip_reasons {       map  { $_->{details} || 'SKIP' }  $_[0]->skips            }
439
440my %TODO_OR_SKIP = (SKIP => 1, TODO => 1);
441sub has_other_amnesty     { &first( sub { !$TODO_OR_SKIP{uc($_->{tag})}            }, $_[0]->amnesty) ? 1 : 0 }
442sub other_amnesty         {        grep { !$TODO_OR_SKIP{uc($_->{tag})}            }  $_[0]->amnesty          }
443sub other_amnesty_reasons {        map  { $_->{details} ||  $_->{tag} || 'AMNESTY' }  $_[0]->other_amnesty    }
444
445sub has_errors     { $_[0]->{+FACET_DATA}->{errors} ? 1 : 0 }
446sub the_errors     { $_[0]->the_facet('errors') }
447sub errors         { $_[0]->facet('errors') }
448sub error_messages { map { $_->{details} || $_->{tag} || 'ERROR' } $_[0]->errors }
449
450sub error_brief {
451    my $self = shift;
452
453    my $errors = $self->{+FACET_DATA}->{errors} or return;
454
455    my $base = @$errors > 1 ? "ERRORS" : "ERROR";
456
457    return $base unless @$errors;
458
459    my ($msg, @extra) = split /[\n\r]+/, $errors->[0]->{details};
460
461    my $out = "$base: $msg";
462
463    $out .= " [...]" if @extra || @$errors > 1;
464
465    return $out;
466}
467
468sub has_info      { $_[0]->{+FACET_DATA}->{info} ? 1 : 0 }
469sub the_info      { $_[0]->the_facet('info') }
470sub info          { $_[0]->facet('info') }
471sub info_messages { map { $_->{details} } $_[0]->info }
472
473sub has_diags { &first(sub { uc($_->{tag}) eq 'DIAG' }, $_[0]->info) ? 1 : 0 }
474sub diags         {   grep { uc($_->{tag}) eq 'DIAG' }  $_[0]->info          }
475sub diag_messages {   map  { $_->{details} || 'DIAG' }  $_[0]->diags         }
476
477sub has_notes { &first(sub { uc($_->{tag}) eq 'NOTE' }, $_[0]->info) ? 1 : 0 }
478sub notes         {   grep { uc($_->{tag}) eq 'NOTE' }  $_[0]->info          }
479sub note_messages {   map  { $_->{details} || 'NOTE' }  $_[0]->notes         }
480
481my %NOTE_OR_DIAG = (NOTE => 1, DIAG => 1);
482sub has_other_info { &first(sub { !$NOTE_OR_DIAG{uc($_->{tag})}         }, $_[0]->info) ? 1 : 0 }
483sub other_info          {  grep { !$NOTE_OR_DIAG{uc($_->{tag})}         }  $_[0]->info          }
484sub other_info_messages {  map  { $_->{details} ||  $_->{tag} || 'INFO' }  $_[0]->other_info    }
485
4861;
487
488__END__
489
490=pod
491
492=encoding UTF-8
493
494=head1 NAME
495
496Test2::API::InterceptResult::Event - Representation of an event for use in
497testing other test tools.
498
499=head1 DESCRIPTION
500
501C<intercept { ... }> from L<Test2::API> returns an instance of
502L<Test2::API::InterceptResult> which is a blessed arrayref of
503L<Test2::API::InterceptResult::Event> objects.
504
505This POD documents the methods of these events, which are mainly provided for
506you to use when testing your test tools.
507
508=head1 SYNOPSIS
509
510    use Test2::V0;
511    use Test2::API qw/intercept/;
512
513    my $events = intercept {
514        ok(1, "A passing assertion");
515        plan(1);
516    };
517
518    # This will convert all events into instances of
519    # Test2::API::InterceptResult::Event. Until we do this they are the
520    # original Test::Event::* instances
521    $events->upgrade(in_place => 1);
522
523    # Now we can get individual events in this form
524    my $assert = $events->[0];
525    my $plan   = $events->[1];
526
527    # Or we can operate on all events at once:
528    my $flattened = $events->flatten;
529    is(
530        $flattened,
531        [
532          {
533            causes_failure => 0,
534
535            name => 'A passing assertion',
536            pass => 1,
537
538            trace_file => 'xxx.t',
539            trace_line => 5,
540          },
541          {
542            causes_failure => 0,
543
544            plan => 1,
545
546            trace_file => 'xxx.t',
547            trace_line => 6,
548          },
549        ],
550        "Flattened both events and returned an arrayref of the results
551    );
552
553=head1 METHODS
554
555=head2 !!! IMPORTANT NOTES ON DESIGN !!!
556
557Please pay attention to what these return, many return a scalar when
558applicable or an empty list when not (as opposed to undef). Many also always
559return a list of 0 or more items. Some always return a scalar. Note that none
560of the methods care about context, their behavior is consistent regardless of
561scalar, list, or void context.
562
563This was done because this class was specifically designed to be used in a list
564and generate more lists in bulk operations. Sometimes in a map you want nothing
565to show up for the event, and you do not want an undef in its place. In general
566single event instances are not going to be used alone, though that is allowed.
567
568As a general rule any method prefixed with C<the_> implies the event should
569have exactly 1 of the specified item, and and exception will be thrown if there
570are 0, or more than 1 of the item.
571
572=head2 ATTRIBUTES
573
574=over 4
575
576=item $hashref = $event->facet_data
577
578This will return the facet data hashref, which is all Test2 cares about for any
579given event.
580
581=item $class = $event->result_class
582
583This is normally L<Test2::API::InterceptResult>. This is set at construction so
584that subtest results can be turned into instances of it on demand.
585
586=back
587
588=head2 DUPLICATION
589
590=over 4
591
592=item $copy = $event->clone
593
594Create a deep copy of the event. Modifying either event will not effect the
595other.
596
597=back
598
599=head2 CONDENSED MULTI-FACET DATA
600
601=over 4
602
603=item $bool = $event->causes_failure
604
605=item $bool = $event->causes_fail
606
607These are both aliases of the same functionality.
608
609This will always return either a true value, or a false value. This never
610returns a list.
611
612This method may be relatively slow (still super fast) because it determines
613pass or fail by creating an instance of L<Test2::Hub> and asking it to process
614the event, and then asks the hub for its pass/fail state. This is slower than
615building in logic to do the check, but it is more reliable as it will always
616tell you what the hub thinks, so the logic will never be out of date relative
617to the Test2 logic that actually cares.
618
619=item STRING_OR_EMPTY_LIST = $event->brief
620
621Not all events have a brief, some events are not rendered by the formatter,
622others have no "brief" data worth seeing. When this is the case an empty list
623is returned. This is done intentionally so it can be used in a map operation
624without having C<undef> being included in the result.
625
626When a brief can be generated it is always a single 1-line string, and is
627returned as-is, not in a list.
628
629Possible briefs:
630
631    # From control facets
632    "BAILED OUT"
633    "BAILED OUT: $why"
634
635    # From error facets
636    "ERROR"
637    "ERROR: $message"
638    "ERROR: $partial_message [...]"
639    "ERRORS: $first_error_message [...]"
640
641    # From assert facets
642    "PASS"
643    "FAIL"
644    "PASS with amnesty"
645    "FAIL with amnesty"
646
647    # From plan facets
648    "PLAN $count"
649    "NO PLAN"
650    "SKIP ALL"
651    "SKIP ALL: $why"
652
653Note that only the first applicable brief is returned. This is essnetially a
654poor-mans TAP that only includes facets that could (but not necessarily do)
655cause a failure.
656
657=item $hashref = $event->flatten
658
659=item $hashref = $event->flatten(include_subevents => 1)
660
661This ALWAYS returns a hashref. This puts all the most useful data for the most
662interesting facets into a single hashref for easy validation.
663
664If there are no meaningful facets this will return an empty hashref.
665
666If given the 'include_subevents' parameter it will also include subtest data:
667
668Here is a list of EVERY possible field. If a field is not applicable it will
669not be present.
670
671=over 4
672
673=item always present
674
675        causes_failure => 1,    # Always present
676
677=item Present if the event has a trace facet
678
679        trace_line    => 42,
680        trace_file    => 'Foo/Bar.pm',
681        trace_details => 'Extra trace details',    # usually not present
682
683=item If an assertion is present
684
685        pass => 0,
686        name => "1 + 1 = 2, so math works",
687
688=item If a plan is present:
689
690        plan => $count_or_SKIP_ALL_or_NO_PLAN,
691
692=item If amnesty facets are present
693
694You get an array for each type that is present.
695
696        todo => [    # Yes you could be under multiple todos, this will list them all.
697            "I will fix this later",
698            "I promise to fix these",
699        ],
700
701        skip => ["This will format the main drive, do not run"],
702
703        ... => ["Other amnesty"]
704
705=item If Info (note/diag) facets are present
706
707You get an arrayref for any that are present, the key is not defined if they are not present.
708
709        diag => [
710            "Test failed at Foo/Bar.pm line 42",
711            "You forgot to tie your boots",
712        ],
713
714        note => ["Your boots are red"],
715
716        ...  => ["Other info"],
717
718=item If error facets are present
719
720Always an arrayref
721
722        error => [
723            "non fatal error (does not cause test failure, just an FYI",
724            "FATAL: This is a fatal error (causes failure)",
725        ],
726
727        # Errors can have alternative tags, but in practice are always 'error',
728        # listing this for completeness.
729        ... => [ ... ]
730
731=item Present if the event is a subtest
732
733        subtest => {
734            count      => 2,    # Number of assertions made
735            failed     => 1,    # Number of test failures seen
736            is_passing => 0,    # Boolean, true if the test would be passing
737                                # after the events are processed.
738
739            plan         => 2,  # Plan, either a number, undef, 'SKIP', or 'NO PLAN'
740            follows_plan => 1,  # True if there is a plan and it was followed.
741                                # False if the plan and assertions did not
742                                # match, undef if no plan was present in the
743                                # event list.
744
745            bailed_out => "foo",    # if there was a bail-out in the
746                                    # events in this will be a string explaining
747                                    # why there was a bailout, if no reason was
748                                    # given this will simply be set to true (1).
749
750            skip_reason => "foo",   # If there was a skip_all this will give the
751                                    # reason.
752        },
753
754if C<< (include_subtest => 1) >> was provided as a parameter then the following
755will be included. This is the result of turning all subtest child events into
756an L<Test2::API::InterceptResult> instance and calling the C<flatten> method on
757it.
758
759        subevents => Test2::API::InterceptResult->new(@child_events)->flatten(...),
760
761=item If a bail-out is being requested
762
763If no reason was given this will be set to 1.
764
765        bailed_out => "reason",
766
767=back
768
769=item $hashref = $event->summary()
770
771This returns a limited summary. See C<flatten()>, which is usually a better
772option.
773
774    {
775        brief => $event->brief || '',
776
777        causes_failure => $event->causes_failure,
778
779        trace_line    => $event->trace_line,
780        trace_file    => $event->trace_file,
781        trace_tool    => $event->trace_subname,
782        trace_details => $event->trace_details,
783
784        facets => [ sort keys(%{$event->{+FACET_DATA}}) ],
785    }
786
787=back
788
789=head2 DIRECT ARBITRARY FACET ACCESS
790
791=over 4
792
793=item @list_of_facets = $event->facet($name)
794
795This always returns a list of 0 or more items. This fetches the facet instances
796from the event. For facets like 'assert' this will always return 0 or 1
797item. For events like 'info' (diags, notes) this will return 0 or more
798instances, once for each instance of the facet.
799
800These will be blessed into the proper L<Test2::EventFacet> subclass. If no
801subclass can be found it will be blessed as an
802L<Test2::API::InterceptResult::Facet> generic facet class.
803
804=item $undef_or_facet = $event->the_facet($name)
805
806If you know you will have exactly 1 instance of a facet you can call this.
807
808If you are correct and there is exactly one instance of the facet it will
809always return the hashref.
810
811If there are 0 instances of the facet this will return undef, not an empty
812list.
813
814If there are more than 1 instance this will throw an exception because your
815assumption was incorrect.
816
817=back
818
819=head2 TRACE FACET
820
821=over 4
822
823=item @list_of_facets = $event->trace
824
825TODO
826
827=item $undef_or_hashref = $event->the_trace
828
829This returns the trace hashref, or undef if it is not present.
830
831=item $undef_or_arrayref = $event->frame
832
833If a trace is present, and has a caller frame, this will be an arrayref:
834
835    [$package, $file, $line, $subname]
836
837If the trace is not present, or has no caller frame this will return undef.
838
839=item $undef_or_string = $event->trace_details
840
841This is usually undef, but occasionally has a string that overrides the
842file/line number debugging a trace usually provides on test failure.
843
844=item $undef_or_string = $event->trace_package
845
846Same as C<(caller())[0]>, the first element of the trace frame.
847
848Will be undef if not present.
849
850=item $undef_or_string = $event->trace_file
851
852Same as C<(caller())[1]>, the second element of the trace frame.
853
854Will be undef if not present.
855
856=item $undef_or_integer = $event->trace_line
857
858Same as C<(caller())[2]>, the third element of the trace frame.
859
860Will be undef if not present.
861
862=item $undef_or_string = $event->trace_subname
863
864=item $undef_or_string = $event->trace_tool
865
866Aliases for the same thing
867
868Same as C<(caller($level))[4]>, the fourth element of the trace frame.
869
870Will be undef if not present.
871
872=item $undef_or_string = $event->trace_signature
873
874A string that is a unique signature for the trace. If a single context
875generates multiple events they will all have the same signature. This can be
876used to tie assertions and diagnostics sent as separate events together after
877the fact.
878
879=back
880
881=head2 ASSERT FACET
882
883=over 4
884
885=item $bool = $event->has_assert
886
887Returns true if the event has an assert facet, false if it does not.
888
889=item $undef_or_hashref = $event->the_assert
890
891Returns the assert facet if present, undef if it is not.
892
893=item @list_of_facets = $event->assert
894
895TODO
896
897=item EMPTY_LIST_OR_STRING = $event->assert_brief
898
899Returns a string giving a brief of the assertion if an assertion is present.
900Returns an empty list if no assertion is present.
901
902=back
903
904=head2 SUBTESTS (PARENT FACET)
905
906=over 4
907
908=item $bool = $event->has_subtest
909
910True if a subetest is present in this event.
911
912=item $undef_or_hashref = $event->the_subtest
913
914Get the one subtest if present, otherwise undef.
915
916=item @list_of_facets = $event->subtest
917
918TODO
919
920=item EMPTY_LIST_OR_OBJECT = $event->subtest_result
921
922Returns an empty list if there is no subtest.
923
924Get an instance of L<Test2::API::InterceptResult> representing the subtest.
925
926=back
927
928=head2 CONTROL FACET (BAILOUT, ENCODING)
929
930=over 4
931
932=item $bool = $event->has_bailout
933
934True if there was a bailout
935
936=item $undef_hashref = $event->the_bailout
937
938Return the control facet if it requested a bailout.
939
940=item EMPTY_LIST_OR_HASHREF = $event->bailout
941
942Get a list of 0 or 1 hashrefs. The hashref will be the control facet if a
943bail-out was requested.
944
945=item EMPTY_LIST_OR_STRING = $event->bailout_brief
946
947Get the brief of the balout if present.
948
949=item EMPTY_LIST_OR_STRING = $event->bailout_reason
950
951Get the reason for the bailout, an empty string if no reason was provided, or
952an empty list if there was no bailout.
953
954=back
955
956=head2 PLAN FACET
957
958TODO
959
960=over 4
961
962=item $bool = $event->has_plan
963
964=item $undef_or_hashref = $event->the_plan
965
966=item @list_if_hashrefs = $event->plan
967
968=item EMPTY_LIST_OR_STRING $event->plan_brief
969
970=back
971
972=head2 AMNESTY FACET (TODO AND SKIP)
973
974TODO
975
976=over 4
977
978=item $event->has_amnesty
979
980=item $event->the_amnesty
981
982=item $event->amnesty
983
984=item $event->amnesty_reasons
985
986=item $event->has_todos
987
988=item $event->todos
989
990=item $event->todo_reasons
991
992=item $event->has_skips
993
994=item $event->skips
995
996=item $event->skip_reasons
997
998=item $event->has_other_amnesty
999
1000=item $event->other_amnesty
1001
1002=item $event->other_amnesty_reasons
1003
1004=back
1005
1006=head2 ERROR FACET (CAPTURED EXCEPTIONS)
1007
1008TODO
1009
1010=over 4
1011
1012=item $event->has_errors
1013
1014=item $event->the_errors
1015
1016=item $event->errors
1017
1018=item $event->error_messages
1019
1020=item $event->error_brief
1021
1022=back
1023
1024=head2 INFO FACET (DIAG, NOTE)
1025
1026TODO
1027
1028=over 4
1029
1030=item $event->has_info
1031
1032=item $event->the_info
1033
1034=item $event->info
1035
1036=item $event->info_messages
1037
1038=item $event->has_diags
1039
1040=item $event->diags
1041
1042=item $event->diag_messages
1043
1044=item $event->has_notes
1045
1046=item $event->notes
1047
1048=item $event->note_messages
1049
1050=item $event->has_other_info
1051
1052=item $event->other_info
1053
1054=item $event->other_info_messages
1055
1056=back
1057
1058=head1 SOURCE
1059
1060The source code repository for Test2 can be found at
1061L<https://github.com/Test-More/test-more/>.
1062
1063=head1 MAINTAINERS
1064
1065=over 4
1066
1067=item Chad Granum E<lt>exodist@cpan.orgE<gt>
1068
1069=back
1070
1071=head1 AUTHORS
1072
1073=over 4
1074
1075=item Chad Granum E<lt>exodist@cpan.orgE<gt>
1076
1077=back
1078
1079=head1 COPYRIGHT
1080
1081Copyright 2020 Chad Granum E<lt>exodist@cpan.orgE<gt>.
1082
1083This program is free software; you can redistribute it and/or
1084modify it under the same terms as Perl itself.
1085
1086See L<https://dev.perl.org/licenses/>
1087
1088=cut
1089