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