635 lines
18 KiB
Perl
635 lines
18 KiB
Perl
package Test2::API::InterceptResult;
|
|
use strict;
|
|
use warnings;
|
|
|
|
our $VERSION = '1.302194';
|
|
|
|
use Scalar::Util qw/blessed/;
|
|
use Test2::Util qw/pkg_to_file/;
|
|
use Storable qw/dclone/;
|
|
use Carp qw/croak/;
|
|
|
|
use Test2::API::InterceptResult::Squasher;
|
|
use Test2::API::InterceptResult::Event;
|
|
use Test2::API::InterceptResult::Hub;
|
|
|
|
sub new {
|
|
croak "Called a method that creates a new instance in void context" unless defined wantarray;
|
|
my $class = shift;
|
|
bless([@_], $class);
|
|
}
|
|
|
|
sub new_from_ref {
|
|
croak "Called a method that creates a new instance in void context" unless defined wantarray;
|
|
bless($_[1], $_[0]);
|
|
}
|
|
|
|
sub clone { blessed($_[0])->new(@{dclone($_[0])}) }
|
|
|
|
sub event_list { @{$_[0]} }
|
|
|
|
sub _upgrade {
|
|
my $self = shift;
|
|
my ($event, %params) = @_;
|
|
|
|
my $blessed = blessed($event);
|
|
|
|
my $upgrade_class = $params{upgrade_class} ||= 'Test2::API::InterceptResult::Event';
|
|
|
|
return $event if $blessed && $event->isa($upgrade_class) && !$params{_upgrade_clone};
|
|
|
|
my $fd = dclone($blessed ? $event->facet_data : $event);
|
|
|
|
my $class = $params{result_class} ||= blessed($self);
|
|
|
|
if (my $parent = $fd->{parent}) {
|
|
$parent->{children} = $class->new_from_ref($parent->{children} || [])->upgrade(%params);
|
|
}
|
|
|
|
my $uc_file = pkg_to_file($upgrade_class);
|
|
require($uc_file) unless $INC{$uc_file};
|
|
return $upgrade_class->new(facet_data => $fd, result_class => $class);
|
|
}
|
|
|
|
sub hub {
|
|
my $self = shift;
|
|
|
|
my $hub = Test2::API::InterceptResult::Hub->new();
|
|
$hub->process($_) for @$self;
|
|
$hub->set_ended(1);
|
|
|
|
return $hub;
|
|
}
|
|
|
|
sub state {
|
|
my $self = shift;
|
|
my %params = @_;
|
|
|
|
my $hub = $self->hub;
|
|
|
|
my $out = {
|
|
map {($_ => scalar $hub->$_)} qw/count failed is_passing plan bailed_out skip_reason/
|
|
};
|
|
|
|
$out->{bailed_out} = $self->_upgrade($out->{bailed_out}, %params)->bailout_reason || 1
|
|
if $out->{bailed_out};
|
|
|
|
$out->{follows_plan} = $hub->check_plan;
|
|
|
|
return $out;
|
|
}
|
|
|
|
sub upgrade {
|
|
my $self = shift;
|
|
my %params = @_;
|
|
|
|
my @out = map { $self->_upgrade($_, %params, _upgrade_clone => 1) } @$self;
|
|
|
|
return blessed($self)->new_from_ref(\@out)
|
|
unless $params{in_place};
|
|
|
|
@$self = @out;
|
|
return $self;
|
|
}
|
|
|
|
sub squash_info {
|
|
my $self = shift;
|
|
my %params = @_;
|
|
|
|
my @out;
|
|
|
|
{
|
|
my $squasher = Test2::API::InterceptResult::Squasher->new(events => \@out);
|
|
# Clone to make sure we do not indirectly modify an existing one if it
|
|
# is already upgraded
|
|
$squasher->process($self->_upgrade($_, %params)->clone) for @$self;
|
|
$squasher->flush_down();
|
|
}
|
|
|
|
return blessed($self)->new_from_ref(\@out)
|
|
unless $params{in_place};
|
|
|
|
@$self = @out;
|
|
return $self;
|
|
}
|
|
|
|
sub asserts { shift->grep(has_assert => @_) }
|
|
sub subtests { shift->grep(has_subtest => @_) }
|
|
sub diags { shift->grep(has_diags => @_) }
|
|
sub notes { shift->grep(has_notes => @_) }
|
|
sub errors { shift->grep(has_errors => @_) }
|
|
sub plans { shift->grep(has_plan => @_) }
|
|
sub causes_fail { shift->grep(causes_fail => @_) }
|
|
sub causes_failure { shift->grep(causes_failure => @_) }
|
|
|
|
sub flatten { shift->map(flatten => @_) }
|
|
sub briefs { shift->map(brief => @_) }
|
|
sub summaries { shift->map(summary => @_) }
|
|
sub subtest_results { shift->map(subtest_result => @_) }
|
|
sub diag_messages { shift->map(diag_messages => @_) }
|
|
sub note_messages { shift->map(note_messages => @_) }
|
|
sub error_messages { shift->map(error_messages => @_) }
|
|
|
|
no warnings 'once';
|
|
|
|
*map = sub {
|
|
my $self = shift;
|
|
my ($call, %params) = @_;
|
|
|
|
my $args = $params{args} ||= [];
|
|
|
|
return [map { local $_ = $self->_upgrade($_, %params); $_->$call(@$args) } @$self];
|
|
};
|
|
|
|
*grep = sub {
|
|
my $self = shift;
|
|
my ($call, %params) = @_;
|
|
|
|
my $args = $params{args} ||= [];
|
|
|
|
my @out = grep { local $_ = $self->_upgrade($_, %params); $_->$call(@$args) } @$self;
|
|
|
|
return blessed($self)->new_from_ref(\@out)
|
|
unless $params{in_place};
|
|
|
|
@$self = @out;
|
|
return $self;
|
|
};
|
|
|
|
1;
|
|
|
|
__END__
|
|
|
|
=pod
|
|
|
|
=encoding UTF-8
|
|
|
|
=head1 NAME
|
|
|
|
Test2::API::InterceptResult - Representation of a list of events.
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
This class represents a list of events, normally obtained using C<intercept()>
|
|
from L<Test2::API>.
|
|
|
|
This class is intended for people who with to verify the results of test tools
|
|
they write.
|
|
|
|
This class provides methods to normalize, summarize, or map the list of events.
|
|
The output of these operations makes verifying your testing tools and the
|
|
events they generate significantly easier. In most cases this spares you from
|
|
needing a deep understanding of the event/facet model.
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
Usually you get an instance of this class when you use C<intercept()> from
|
|
L<Test2::API>.
|
|
|
|
use Test2::V0;
|
|
use Test2::API qw/intercept/;
|
|
|
|
my $events = intercept {
|
|
ok(1, "pass");
|
|
ok(0, "fail");
|
|
todo "broken" => sub { ok(0, "fixme") };
|
|
plan 3;
|
|
};
|
|
|
|
# This is typically the most useful construct
|
|
# squash_info() merges assertions and diagnostics that are associated
|
|
# (and returns a new instance with the modifications)
|
|
# flatten() condenses the facet data into the key details for each event
|
|
# (and returns those structures in an arrayref)
|
|
is(
|
|
$events->squash_info->flatten(),
|
|
[
|
|
{
|
|
causes_failure => 0,
|
|
|
|
name => 'pass',
|
|
pass => 1,
|
|
|
|
trace_file => 'xxx.t',
|
|
trace_line => 5,
|
|
},
|
|
{
|
|
causes_failure => 1,
|
|
|
|
name => 'fail',
|
|
pass => 0,
|
|
|
|
trace_file => 'xxx.t',
|
|
trace_line => 6,
|
|
|
|
# There can be more than one diagnostics message so this is
|
|
# always an array when present.
|
|
diag => ["Failed test 'fail'\nat xxx.t line 6."],
|
|
},
|
|
{
|
|
causes_failure => 0,
|
|
|
|
name => 'fixme',
|
|
pass => 0,
|
|
|
|
trace_file => 'xxx.t',
|
|
trace_line => 7,
|
|
|
|
# There can be more than one diagnostics message or todo
|
|
# reason, so these are always an array when present.
|
|
todo => ['broken'],
|
|
|
|
# Diag message was turned into a note since the assertion was
|
|
# TODO
|
|
note => ["Failed test 'fixme'\nat xxx.t line 7."],
|
|
},
|
|
{
|
|
causes_failure => 0,
|
|
|
|
plan => 3,
|
|
|
|
trace_file => 'xxx.t',
|
|
trace_line => 8,
|
|
},
|
|
],
|
|
"Flattened events look like we expect"
|
|
);
|
|
|
|
See L<Test2::API::InterceptResult::Event> for a full description of what
|
|
C<flatten()> provides for each event.
|
|
|
|
=head1 METHODS
|
|
|
|
Please note that no methods modify the original instance unless asked to do so.
|
|
|
|
=head2 CONSTRUCTION
|
|
|
|
=over 4
|
|
|
|
=item $events = Test2::API::InterceptResult->new(@EVENTS)
|
|
|
|
=item $events = Test2::API::InterceptResult->new_from_ref(\@EVENTS)
|
|
|
|
These create a new instance of Test2::API::InterceptResult from the given
|
|
events.
|
|
|
|
In the first form a new blessed arrayref is returned. In the 'new_from_ref'
|
|
form the reference you pass in is directly blessed.
|
|
|
|
Both of these will throw an exception if called in void context. This is mainly
|
|
important for the 'filtering' methods listed below which normally return a new
|
|
instance, they throw an exception in such cases as it probably means someone
|
|
meant to filter the original in place.
|
|
|
|
=item $clone = $events->clone()
|
|
|
|
Make a clone of the original events. Note that this is a deep copy, the entire
|
|
structure is duplicated. This uses C<dclone> from L<Storable> to achieve the
|
|
deep clone.
|
|
|
|
=back
|
|
|
|
=head2 NORMALIZATION
|
|
|
|
=over 4
|
|
|
|
=item @events = $events->event_list
|
|
|
|
This returns all the events in list-form.
|
|
|
|
=item $hub = $events->hub
|
|
|
|
This returns a new L<Test2::Hub> instance that has processed all the events
|
|
contained in the instance. This gives you a simple way to inspect the state
|
|
changes your events cause.
|
|
|
|
=item $state = $events->state
|
|
|
|
This returns a summary of the state of a hub after processing all the events.
|
|
|
|
{
|
|
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 => undef, # undef unless there was a bail-out in the
|
|
# events in which case 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 => undef, # If there was a skip_all this will give the
|
|
# reason.
|
|
}
|
|
|
|
|
|
=item $new = $events->upgrade
|
|
|
|
=item $events->upgrade(in_place => $BOOL)
|
|
|
|
B<Note:> This normally returns a new instance, leaving the original unchanged.
|
|
If you call it in void context it will throw an exception. If you want to
|
|
modify the original you must pass in the C<< in_place => 1 >> option. You may
|
|
call this in void context when you ask to modify it in place. The in-place form
|
|
returns the instance that was modified so you can chain methods.
|
|
|
|
This will create a clone of the list where all events have been converted into
|
|
L<Test2::API::InterceptResult::Event> instances. This is extremely helpful as
|
|
L<Test2::API::InterceptResult::Event> provide a much better interface for
|
|
working with events. This allows you to avoid thinking about legacy event
|
|
types.
|
|
|
|
This also means your tests against the list are not fragile if the tool
|
|
you are testing randomly changes what type of events it generates (IE Changing
|
|
from L<Test2::Event::Ok> to L<Test2::Event::Pass>, both make assertions and
|
|
both will normalize to identical (or close enough)
|
|
L<Test2::API::InterceptResult::Event> instances.
|
|
|
|
Really you almost always want this, the only reason it is not done
|
|
automatically is to make sure the C<intercept()> tool is backwards compatible.
|
|
|
|
=item $new = $events->squash_info
|
|
|
|
=item $events->squash_info(in_place => $BOOL)
|
|
|
|
B<Note:> This normally returns a new instance, leaving the original unchanged.
|
|
If you call it in void context it will throw an exception. If you want to
|
|
modify the original you must pass in the C<< in_place => 1 >> option. You may
|
|
call this in void context when you ask to modify it in place. The in-place form
|
|
returns the instance that was modified so you can chain methods.
|
|
|
|
B<Note:> All events in the new or modified instance will be converted to
|
|
L<Test2::API::InterceptResult::Event> instances. There is no way to avoid this,
|
|
the squash operation requires the upgraded event class.
|
|
|
|
L<Test::More> and many other legacy tools would send notes, diags, and
|
|
assertions as seperate events. A subtest in L<Test::More> would send a note
|
|
with the subtest name, the subtest assertion, and finally a diagnostics event
|
|
if the subtest failed. This method will normalize things by squashing the note
|
|
and diag into the same event as the subtest (This is different from putting
|
|
them into the subtest, which is not what happens).
|
|
|
|
=back
|
|
|
|
=head2 FILTERING
|
|
|
|
B<Note:> These normally return new instances, leaving the originals unchanged.
|
|
If you call them in void context they will throw exceptions. If you want to
|
|
modify the originals you must pass in the C<< in_place => 1 >> option. You may
|
|
call these in void context when you ask to modify them in place. The in-place
|
|
forms return the instance that was modified so you can chain methods.
|
|
|
|
=head3 %PARAMS
|
|
|
|
These all accept the same 2 optional parameters:
|
|
|
|
=over 4
|
|
|
|
=item in_place => $BOOL
|
|
|
|
When true the method will modify the instance in place instead of returning a
|
|
new instance.
|
|
|
|
=item args => \@ARGS
|
|
|
|
If you wish to pass parameters into the event method being used for filtering,
|
|
you may do so here.
|
|
|
|
=back
|
|
|
|
=head3 METHODS
|
|
|
|
=over 4
|
|
|
|
=item $events->grep($CALL, %PARAMS)
|
|
|
|
This is essentially:
|
|
|
|
Test2::API::InterceptResult->new(
|
|
grep { $_->$CALL( @{$PARAMS{args}} ) } $self->event_list,
|
|
);
|
|
|
|
B<Note:> that $CALL is called on an upgraded version of the event, though
|
|
the events returned will be the original ones, not the upgraded ones.
|
|
|
|
$CALL may be either the name of a method on
|
|
L<Test2::API::InterceptResult::Event>, or a coderef.
|
|
|
|
=item $events->asserts(%PARAMS)
|
|
|
|
This is essentially:
|
|
|
|
$events->grep(has_assert => @{$PARAMS{args}})
|
|
|
|
It returns a new instance containing only the events that made assertions.
|
|
|
|
=item $events->subtests(%PARAMS)
|
|
|
|
This is essentially:
|
|
|
|
$events->grep(has_subtest => @{$PARAMS{args}})
|
|
|
|
It returns a new instance containing only the events that have subtests.
|
|
|
|
=item $events->diags(%PARAMS)
|
|
|
|
This is essentially:
|
|
|
|
$events->grep(has_diags => @{$PARAMS{args}})
|
|
|
|
It returns a new instance containing only the events that have diags.
|
|
|
|
=item $events->notes(%PARAMS)
|
|
|
|
This is essentially:
|
|
|
|
$events->grep(has_notes => @{$PARAMS{args}})
|
|
|
|
It returns a new instance containing only the events that have notes.
|
|
|
|
=item $events->errors(%PARAMS)
|
|
|
|
B<Note:> Errors are NOT failing assertions. Failing assertions are a different
|
|
thing.
|
|
|
|
This is essentially:
|
|
|
|
$events->grep(has_errors => @{$PARAMS{args}})
|
|
|
|
It returns a new instance containing only the events that have errors.
|
|
|
|
=item $events->plans(%PARAMS)
|
|
|
|
This is essentially:
|
|
|
|
$events->grep(has_plan => @{$PARAMS{args}})
|
|
|
|
It returns a new instance containing only the events that set the plan.
|
|
|
|
=item $events->causes_fail(%PARAMS)
|
|
|
|
=item $events->causes_failure(%PARAMS)
|
|
|
|
These are essentially:
|
|
|
|
$events->grep(causes_fail => @{$PARAMS{args}})
|
|
$events->grep(causes_failure => @{$PARAMS{args}})
|
|
|
|
B<Note:> C<causes_fail()> and C<causes_failure()> are both aliases for
|
|
eachother in events, so these methods are effectively aliases here as well.
|
|
|
|
It returns a new instance containing only the events that cause failure.
|
|
|
|
=back
|
|
|
|
=head2 MAPPING
|
|
|
|
These methods B<ALWAYS> return an arrayref.
|
|
|
|
B<Note:> No methods on L<Test2::API::InterceptResult::Event> alter the event in
|
|
any way.
|
|
|
|
B<Important Notes about Events>:
|
|
|
|
L<Test2::API::InterceptResult::Event> was tailor-made to be used in
|
|
event-lists. Most methods that are not applicable to a given event will return
|
|
an empty list, so you normally do not need to worry about unwanted C<undef>
|
|
values or exceptions being thrown. Mapping over event methods is an entended
|
|
use, so it works well to produce lists.
|
|
|
|
B<Exceptions to the rule:>
|
|
|
|
Some methods such as C<causes_fail> always return a boolean true or false for
|
|
all events. Any method prefixed with C<the_> conveys the intent that the event
|
|
should have exactly 1 of something, so those will throw an exception when that
|
|
condition is not true.
|
|
|
|
=over 4
|
|
|
|
=item $arrayref = $events->map($CALL, %PARAMS)
|
|
|
|
This is essentially:
|
|
|
|
[ map { $_->$CALL(@{ $PARAMS{args} }) } $events->upgrade->event_list ];
|
|
|
|
$CALL may be either the name of a method on
|
|
L<Test2::API::InterceptResult::Event>, or a coderef.
|
|
|
|
=item $arrayref = $events->flatten(%PARAMS)
|
|
|
|
This is essentially:
|
|
|
|
[ map { $_->flatten(@{ $PARAMS{args} }) } $events->upgrade->event_list ];
|
|
|
|
It returns a new list of flattened structures.
|
|
|
|
See L<Test2::API::InterceptResult::Event> for details on what C<flatten()>
|
|
returns.
|
|
|
|
=item $arrayref = $events->briefs(%PARAMS)
|
|
|
|
This is essentially:
|
|
|
|
[ map { $_->briefs(@{ $PARAMS{args} }) } $events->upgrade->event_list ];
|
|
|
|
It returns a new list of event briefs.
|
|
|
|
See L<Test2::API::InterceptResult::Event> for details on what C<brief()>
|
|
returns.
|
|
|
|
=item $arrayref = $events->summaries(%PARAMS)
|
|
|
|
This is essentially:
|
|
|
|
[ map { $_->summaries(@{ $PARAMS{args} }) } $events->upgrade->event_list ];
|
|
|
|
It returns a new list of event summaries.
|
|
|
|
See L<Test2::API::InterceptResult::Event> for details on what C<summary()>
|
|
returns.
|
|
|
|
=item $arrayref = $events->subtest_results(%PARAMS)
|
|
|
|
This is essentially:
|
|
|
|
[ map { $_->subtest_result(@{ $PARAMS{args} }) } $events->upgrade->event_list ];
|
|
|
|
It returns a new list of event summaries.
|
|
|
|
See L<Test2::API::InterceptResult::Event> for details on what
|
|
C<subtest_result()> returns.
|
|
|
|
=item $arrayref = $events->diag_messages(%PARAMS)
|
|
|
|
This is essentially:
|
|
|
|
[ map { $_->diag_messages(@{ $PARAMS{args} }) } $events->upgrade->event_list ];
|
|
|
|
It returns a new list of diagnostic messages (strings).
|
|
|
|
See L<Test2::API::InterceptResult::Event> for details on what
|
|
C<diag_messages()> returns.
|
|
|
|
=item $arrayref = $events->note_messages(%PARAMS)
|
|
|
|
This is essentially:
|
|
|
|
[ map { $_->note_messages(@{ $PARAMS{args} }) } $events->upgrade->event_list ];
|
|
|
|
It returns a new list of notification messages (strings).
|
|
|
|
See L<Test2::API::InterceptResult::Event> for details on what
|
|
C<note_messages()> returns.
|
|
|
|
=item $arrayref = $events->error_messages(%PARAMS)
|
|
|
|
This is essentially:
|
|
|
|
[ map { $_->error_messages(@{ $PARAMS{args} }) } $events->upgrade->event_list ];
|
|
|
|
It returns a new list of error messages (strings).
|
|
|
|
See L<Test2::API::InterceptResult::Event> for details on what
|
|
C<error_messages()> returns.
|
|
|
|
=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>exodist@cpan.orgE<gt>
|
|
|
|
=back
|
|
|
|
=head1 AUTHORS
|
|
|
|
=over 4
|
|
|
|
=item Chad Granum E<lt>exodist@cpan.orgE<gt>
|
|
|
|
=back
|
|
|
|
=head1 COPYRIGHT
|
|
|
|
Copyright 2020 Chad Granum E<lt>exodist@cpan.orgE<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
|