diff options
Diffstat (limited to 'cpan/Test-Simple/lib/Test2/API/InterceptResult.pm')
-rw-r--r-- | cpan/Test-Simple/lib/Test2/API/InterceptResult.pm | 634 |
1 files changed, 634 insertions, 0 deletions
diff --git a/cpan/Test-Simple/lib/Test2/API/InterceptResult.pm b/cpan/Test-Simple/lib/Test2/API/InterceptResult.pm new file mode 100644 index 0000000000..c22e877b2f --- /dev/null +++ b/cpan/Test-Simple/lib/Test2/API/InterceptResult.pm @@ -0,0 +1,634 @@ +package Test2::API::InterceptResult; +use strict; +use warnings; + +our $VERSION = '1.302181'; + +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 |