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