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