File Coverage

blib/lib/Test2/API/InterceptResult.pm
Criterion Covered Total %
statement 109 111 98.2
branch 17 20 85.0
condition 13 19 68.4
subroutine 34 36 94.4
pod 23 23 100.0
total 196 209 93.7


line stmt bran cond sub pod time code
1             package Test2::API::InterceptResult;
2 35     35   886 use strict;
  35         87  
  35         1230  
3 35     35   200 use warnings;
  35         66  
  35         1800  
4              
5             our $VERSION = '1.302182';
6              
7 35     35   215 use Scalar::Util qw/blessed/;
  35         83  
  35         2372  
8 35     35   228 use Test2::Util qw/pkg_to_file/;
  35         93  
  35         1921  
9 35     35   20360 use Storable qw/dclone/;
  35         99972  
  35         2905  
10 35     35   277 use Carp qw/croak/;
  35         82  
  35         1866  
11              
12 35     35   17287 use Test2::API::InterceptResult::Squasher;
  35         105  
  35         968  
13 35     35   18284 use Test2::API::InterceptResult::Event;
  35         119  
  35         1384  
14 35     35   302 use Test2::API::InterceptResult::Hub;
  35         89  
  35         36426  
15              
16             sub new {
17 7 50   7 1 36 croak "Called a method that creates a new instance in void context" unless defined wantarray;
18 7         13 my $class = shift;
19 7         33 bless([@_], $class);
20             }
21              
22             sub new_from_ref {
23 84 100   84 1 493 croak "Called a method that creates a new instance in void context" unless defined wantarray;
24 83         780 bless($_[1], $_[0]);
25             }
26              
27 1     1 1 11 sub clone { blessed($_[0])->new(@{dclone($_[0])}) }
  1         168  
28              
29 1     1 1 7 sub event_list { @{$_[0]} }
  1         7  
30              
31             sub _upgrade {
32 122     122   201 my $self = shift;
33 122         289 my ($event, %params) = @_;
34              
35 122         333 my $blessed = blessed($event);
36              
37 122   100     449 my $upgrade_class = $params{upgrade_class} ||= 'Test2::API::InterceptResult::Event';
38              
39 122 100 100     802 return $event if $blessed && $event->isa($upgrade_class) && !$params{_upgrade_clone};
      100        
40              
41 68 100       423 my $fd = dclone($blessed ? $event->facet_data : $event);
42              
43 68   66     592 my $class = $params{result_class} ||= blessed($self);
44              
45 68 100       206 if (my $parent = $fd->{parent}) {
46 4   50     25 $parent->{children} = $class->new_from_ref($parent->{children} || [])->upgrade(%params);
47             }
48              
49 68         230 my $uc_file = pkg_to_file($upgrade_class);
50 68 50       189 require($uc_file) unless $INC{$uc_file};
51 68         221 return $upgrade_class->new(facet_data => $fd, result_class => $class);
52             }
53              
54             sub hub {
55 4     4 1 16 my $self = shift;
56              
57 4         169 my $hub = Test2::API::InterceptResult::Hub->new();
58 4         23 $hub->process($_) for @$self;
59 4         149 $hub->set_ended(1);
60              
61 4         11 return $hub;
62             }
63              
64             sub state {
65 3     3 1 13 my $self = shift;
66 3         7 my %params = @_;
67              
68 3         9 my $hub = $self->hub;
69              
70             my $out = {
71 3         32 map {($_ => scalar $hub->$_)} qw/count failed is_passing plan bailed_out skip_reason/
  18         72  
72             };
73              
74             $out->{bailed_out} = $self->_upgrade($out->{bailed_out}, %params)->bailout_reason || 1
75 3 50 0     17 if $out->{bailed_out};
76              
77 3         21 $out->{follows_plan} = $hub->check_plan;
78              
79 3         13 return $out;
80             }
81              
82             sub upgrade {
83 14     14 1 50 my $self = shift;
84 14         36 my %params = @_;
85              
86 14         34 my @out = map { $self->_upgrade($_, %params, _upgrade_clone => 1) } @$self;
  30         95  
87              
88             return blessed($self)->new_from_ref(\@out)
89 14 100       71 unless $params{in_place};
90              
91 4         69 @$self = @out;
92 4         14 return $self;
93             }
94              
95             sub squash_info {
96 2     2 1 15 my $self = shift;
97 2         7 my %params = @_;
98              
99 2         5 my @out;
100              
101             {
102 2         3 my $squasher = Test2::API::InterceptResult::Squasher->new(events => \@out);
  2         14  
103             # Clone to make sure we do not indirectly modify an existing one if it
104             # is already upgraded
105 2         13 $squasher->process($self->_upgrade($_, %params)->clone) for @$self;
106 2         10 $squasher->flush_down();
107             }
108              
109             return blessed($self)->new_from_ref(\@out)
110 2 100       15 unless $params{in_place};
111              
112 1         33 @$self = @out;
113 1         4 return $self;
114             }
115              
116 2     2 1 13 sub asserts { shift->grep(has_assert => @_) }
117 1     1 1 4 sub subtests { shift->grep(has_subtest => @_) }
118 1     1 1 6 sub diags { shift->grep(has_diags => @_) }
119 1     1 1 6 sub notes { shift->grep(has_notes => @_) }
120 1     1 1 5 sub errors { shift->grep(has_errors => @_) }
121 1     1 1 6 sub plans { shift->grep(has_plan => @_) }
122 0     0 1 0 sub causes_fail { shift->grep(causes_fail => @_) }
123 0     0 1 0 sub causes_failure { shift->grep(causes_failure => @_) }
124              
125 2     2 1 16 sub flatten { shift->map(flatten => @_) }
126 1     1 1 4 sub briefs { shift->map(brief => @_) }
127 1     1 1 5 sub summaries { shift->map(summary => @_) }
128 1     1 1 41 sub subtest_results { shift->map(subtest_result => @_) }
129 1     1 1 35 sub diag_messages { shift->map(diag_messages => @_) }
130 1     1 1 4 sub note_messages { shift->map(note_messages => @_) }
131 1     1 1 5 sub error_messages { shift->map(error_messages => @_) }
132              
133 35     35   367 no warnings 'once';
  35         125  
  35         9417  
134              
135             *map = sub {
136 8     8   18 my $self = shift;
137 8         21 my ($call, %params) = @_;
138              
139 8   50     39 my $args = $params{args} ||= [];
140              
141 8         21 return [map { local $_ = $self->_upgrade($_, %params); $_->$call(@$args) } @$self];
  29         98  
  29         141  
142             };
143              
144             *grep = sub {
145 7     7   12 my $self = shift;
146 7         18 my ($call, %params) = @_;
147              
148 7   50     31 my $args = $params{args} ||= [];
149              
150 7         19 my @out = grep { local $_ = $self->_upgrade($_, %params); $_->$call(@$args) } @$self;
  42         98  
  42         137  
151              
152             return blessed($self)->new_from_ref(\@out)
153 7 100       38 unless $params{in_place};
154              
155 1         22 @$self = @out;
156 1         4 return $self;
157             };
158              
159             1;
160              
161             __END__
162              
163             =pod
164              
165             =encoding UTF-8
166              
167             =head1 NAME
168              
169             Test2::API::InterceptResult - Representation of a list of events.
170              
171             =head1 DESCRIPTION
172              
173             This class represents a list of events, normally obtained using C<intercept()>
174             from L<Test2::API>.
175              
176             This class is intended for people who with to verify the results of test tools
177             they write.
178              
179             This class provides methods to normalize, summarize, or map the list of events.
180             The output of these operations makes verifying your testing tools and the
181             events they generate significantly easier. In most cases this spares you from
182             needing a deep understanding of the event/facet model.
183              
184             =head1 SYNOPSIS
185              
186             Usually you get an instance of this class when you use C<intercept()> from
187             L<Test2::API>.
188              
189             use Test2::V0;
190             use Test2::API qw/intercept/;
191              
192             my $events = intercept {
193             ok(1, "pass");
194             ok(0, "fail");
195             todo "broken" => sub { ok(0, "fixme") };
196             plan 3;
197             };
198              
199             # This is typically the most useful construct
200             # squash_info() merges assertions and diagnostics that are associated
201             # (and returns a new instance with the modifications)
202             # flatten() condenses the facet data into the key details for each event
203             # (and returns those structures in an arrayref)
204             is(
205             $events->squash_info->flatten(),
206             [
207             {
208             causes_failure => 0,
209              
210             name => 'pass',
211             pass => 1,
212              
213             trace_file => 'xxx.t',
214             trace_line => 5,
215             },
216             {
217             causes_failure => 1,
218              
219             name => 'fail',
220             pass => 0,
221              
222             trace_file => 'xxx.t',
223             trace_line => 6,
224              
225             # There can be more than one diagnostics message so this is
226             # always an array when present.
227             diag => ["Failed test 'fail'\nat xxx.t line 6."],
228             },
229             {
230             causes_failure => 0,
231              
232             name => 'fixme',
233             pass => 0,
234              
235             trace_file => 'xxx.t',
236             trace_line => 7,
237              
238             # There can be more than one diagnostics message or todo
239             # reason, so these are always an array when present.
240             todo => ['broken'],
241              
242             # Diag message was turned into a note since the assertion was
243             # TODO
244             note => ["Failed test 'fixme'\nat xxx.t line 7."],
245             },
246             {
247             causes_failure => 0,
248              
249             plan => 3,
250              
251             trace_file => 'xxx.t',
252             trace_line => 8,
253             },
254             ],
255             "Flattened events look like we expect"
256             );
257              
258             See L<Test2::API::InterceptResult::Event> for a full description of what
259             C<flatten()> provides for each event.
260              
261             =head1 METHODS
262              
263             Please note that no methods modify the original instance unless asked to do so.
264              
265             =head2 CONSTRUCTION
266              
267             =over 4
268              
269             =item $events = Test2::API::InterceptResult->new(@EVENTS)
270              
271             =item $events = Test2::API::InterceptResult->new_from_ref(\@EVENTS)
272              
273             These create a new instance of Test2::API::InterceptResult from the given
274             events.
275              
276             In the first form a new blessed arrayref is returned. In the 'new_from_ref'
277             form the reference you pass in is directly blessed.
278              
279             Both of these will throw an exception if called in void context. This is mainly
280             important for the 'filtering' methods listed below which normally return a new
281             instance, they throw an exception in such cases as it probably means someone
282             meant to filter the original in place.
283              
284             =item $clone = $events->clone()
285              
286             Make a clone of the original events. Note that this is a deep copy, the entire
287             structure is duplicated. This uses C<dclone> from L<Storable> to achieve the
288             deep clone.
289              
290             =back
291              
292             =head2 NORMALIZATION
293              
294             =over 4
295              
296             =item @events = $events->event_list
297              
298             This returns all the events in list-form.
299              
300             =item $hub = $events->hub
301              
302             This returns a new L<Test2::Hub> instance that has processed all the events
303             contained in the instance. This gives you a simple way to inspect the state
304             changes your events cause.
305              
306             =item $state = $events->state
307              
308             This returns a summary of the state of a hub after processing all the events.
309              
310             {
311             count => 2, # Number of assertions made
312             failed => 1, # Number of test failures seen
313             is_passing => 0, # Boolean, true if the test would be passing
314             # after the events are processed.
315              
316             plan => 2, # Plan, either a number, undef, 'SKIP', or 'NO PLAN'
317             follows_plan => 1, # True if there is a plan and it was followed.
318             # False if the plan and assertions did not
319             # match, undef if no plan was present in the
320             # event list.
321              
322             bailed_out => undef, # undef unless there was a bail-out in the
323             # events in which case this will be a string
324             # explaining why there was a bailout, if no
325             # reason was given this will simply be set to
326             # true (1).
327              
328             skip_reason => undef, # If there was a skip_all this will give the
329             # reason.
330             }
331              
332              
333             =item $new = $events->upgrade
334              
335             =item $events->upgrade(in_place => $BOOL)
336              
337             B<Note:> This normally returns a new instance, leaving the original unchanged.
338             If you call it in void context it will throw an exception. If you want to
339             modify the original you must pass in the C<< in_place => 1 >> option. You may
340             call this in void context when you ask to modify it in place. The in-place form
341             returns the instance that was modified so you can chain methods.
342              
343             This will create a clone of the list where all events have been converted into
344             L<Test2::API::InterceptResult::Event> instances. This is extremely helpful as
345             L<Test2::API::InterceptResult::Event> provide a much better interface for
346             working with events. This allows you to avoid thinking about legacy event
347             types.
348              
349             This also means your tests against the list are not fragile if the tool
350             you are testing randomly changes what type of events it generates (IE Changing
351             from L<Test2::Event::Ok> to L<Test2::Event::Pass>, both make assertions and
352             both will normalize to identical (or close enough)
353             L<Test2::API::InterceptResult::Event> instances.
354              
355             Really you almost always want this, the only reason it is not done
356             automatically is to make sure the C<intercept()> tool is backwards compatible.
357              
358             =item $new = $events->squash_info
359              
360             =item $events->squash_info(in_place => $BOOL)
361              
362             B<Note:> This normally returns a new instance, leaving the original unchanged.
363             If you call it in void context it will throw an exception. If you want to
364             modify the original you must pass in the C<< in_place => 1 >> option. You may
365             call this in void context when you ask to modify it in place. The in-place form
366             returns the instance that was modified so you can chain methods.
367              
368             B<Note:> All events in the new or modified instance will be converted to
369             L<Test2::API::InterceptResult::Event> instances. There is no way to avoid this,
370             the squash operation requires the upgraded event class.
371              
372             L<Test::More> and many other legacy tools would send notes, diags, and
373             assertions as seperate events. A subtest in L<Test::More> would send a note
374             with the subtest name, the subtest assertion, and finally a diagnostics event
375             if the subtest failed. This method will normalize things by squashing the note
376             and diag into the same event as the subtest (This is different from putting
377             them into the subtest, which is not what happens).
378              
379             =back
380              
381             =head2 FILTERING
382              
383             B<Note:> These normally return new instances, leaving the originals unchanged.
384             If you call them in void context they will throw exceptions. If you want to
385             modify the originals you must pass in the C<< in_place => 1 >> option. You may
386             call these in void context when you ask to modify them in place. The in-place
387             forms return the instance that was modified so you can chain methods.
388              
389             =head3 %PARAMS
390              
391             These all accept the same 2 optional parameters:
392              
393             =over 4
394              
395             =item in_place => $BOOL
396              
397             When true the method will modify the instance in place instead of returning a
398             new instance.
399              
400             =item args => \@ARGS
401              
402             If you wish to pass parameters into the event method being used for filtering,
403             you may do so here.
404              
405             =back
406              
407             =head3 METHODS
408              
409             =over 4
410              
411             =item $events->grep($CALL, %PARAMS)
412              
413             This is essentially:
414              
415             Test2::API::InterceptResult->new(
416             grep { $_->$CALL( @{$PARAMS{args}} ) } $self->event_list,
417             );
418              
419             B<Note:> that $CALL is called on an upgraded version of the event, though
420             the events returned will be the original ones, not the upgraded ones.
421              
422             $CALL may be either the name of a method on
423             L<Test2::API::InterceptResult::Event>, or a coderef.
424              
425             =item $events->asserts(%PARAMS)
426              
427             This is essentially:
428              
429             $events->grep(has_assert => @{$PARAMS{args}})
430              
431             It returns a new instance containing only the events that made assertions.
432              
433             =item $events->subtests(%PARAMS)
434              
435             This is essentially:
436              
437             $events->grep(has_subtest => @{$PARAMS{args}})
438              
439             It returns a new instance containing only the events that have subtests.
440              
441             =item $events->diags(%PARAMS)
442              
443             This is essentially:
444              
445             $events->grep(has_diags => @{$PARAMS{args}})
446              
447             It returns a new instance containing only the events that have diags.
448              
449             =item $events->notes(%PARAMS)
450              
451             This is essentially:
452              
453             $events->grep(has_notes => @{$PARAMS{args}})
454              
455             It returns a new instance containing only the events that have notes.
456              
457             =item $events->errors(%PARAMS)
458              
459             B<Note:> Errors are NOT failing assertions. Failing assertions are a different
460             thing.
461              
462             This is essentially:
463              
464             $events->grep(has_errors => @{$PARAMS{args}})
465              
466             It returns a new instance containing only the events that have errors.
467              
468             =item $events->plans(%PARAMS)
469              
470             This is essentially:
471              
472             $events->grep(has_plan => @{$PARAMS{args}})
473              
474             It returns a new instance containing only the events that set the plan.
475              
476             =item $events->causes_fail(%PARAMS)
477              
478             =item $events->causes_failure(%PARAMS)
479              
480             These are essentially:
481              
482             $events->grep(causes_fail => @{$PARAMS{args}})
483             $events->grep(causes_failure => @{$PARAMS{args}})
484              
485             B<Note:> C<causes_fail()> and C<causes_failure()> are both aliases for
486             eachother in events, so these methods are effectively aliases here as well.
487              
488             It returns a new instance containing only the events that cause failure.
489              
490             =back
491              
492             =head2 MAPPING
493              
494             These methods B<ALWAYS> return an arrayref.
495              
496             B<Note:> No methods on L<Test2::API::InterceptResult::Event> alter the event in
497             any way.
498              
499             B<Important Notes about Events>:
500              
501             L<Test2::API::InterceptResult::Event> was tailor-made to be used in
502             event-lists. Most methods that are not applicable to a given event will return
503             an empty list, so you normally do not need to worry about unwanted C<undef>
504             values or exceptions being thrown. Mapping over event methods is an entended
505             use, so it works well to produce lists.
506              
507             B<Exceptions to the rule:>
508              
509             Some methods such as C<causes_fail> always return a boolean true or false for
510             all events. Any method prefixed with C<the_> conveys the intent that the event
511             should have exactly 1 of something, so those will throw an exception when that
512             condition is not true.
513              
514             =over 4
515              
516             =item $arrayref = $events->map($CALL, %PARAMS)
517              
518             This is essentially:
519              
520             [ map { $_->$CALL(@{ $PARAMS{args} }) } $events->upgrade->event_list ];
521              
522             $CALL may be either the name of a method on
523             L<Test2::API::InterceptResult::Event>, or a coderef.
524              
525             =item $arrayref = $events->flatten(%PARAMS)
526              
527             This is essentially:
528              
529             [ map { $_->flatten(@{ $PARAMS{args} }) } $events->upgrade->event_list ];
530              
531             It returns a new list of flattened structures.
532              
533             See L<Test2::API::InterceptResult::Event> for details on what C<flatten()>
534             returns.
535              
536             =item $arrayref = $events->briefs(%PARAMS)
537              
538             This is essentially:
539              
540             [ map { $_->briefs(@{ $PARAMS{args} }) } $events->upgrade->event_list ];
541              
542             It returns a new list of event briefs.
543              
544             See L<Test2::API::InterceptResult::Event> for details on what C<brief()>
545             returns.
546              
547             =item $arrayref = $events->summaries(%PARAMS)
548              
549             This is essentially:
550              
551             [ map { $_->summaries(@{ $PARAMS{args} }) } $events->upgrade->event_list ];
552              
553             It returns a new list of event summaries.
554              
555             See L<Test2::API::InterceptResult::Event> for details on what C<summary()>
556             returns.
557              
558             =item $arrayref = $events->subtest_results(%PARAMS)
559              
560             This is essentially:
561              
562             [ map { $_->subtest_result(@{ $PARAMS{args} }) } $events->upgrade->event_list ];
563              
564             It returns a new list of event summaries.
565              
566             See L<Test2::API::InterceptResult::Event> for details on what
567             C<subtest_result()> returns.
568              
569             =item $arrayref = $events->diag_messages(%PARAMS)
570              
571             This is essentially:
572              
573             [ map { $_->diag_messages(@{ $PARAMS{args} }) } $events->upgrade->event_list ];
574              
575             It returns a new list of diagnostic messages (strings).
576              
577             See L<Test2::API::InterceptResult::Event> for details on what
578             C<diag_messages()> returns.
579              
580             =item $arrayref = $events->note_messages(%PARAMS)
581              
582             This is essentially:
583              
584             [ map { $_->note_messages(@{ $PARAMS{args} }) } $events->upgrade->event_list ];
585              
586             It returns a new list of notification messages (strings).
587              
588             See L<Test2::API::InterceptResult::Event> for details on what
589             C<note_messages()> returns.
590              
591             =item $arrayref = $events->error_messages(%PARAMS)
592              
593             This is essentially:
594              
595             [ map { $_->error_messages(@{ $PARAMS{args} }) } $events->upgrade->event_list ];
596              
597             It returns a new list of error messages (strings).
598              
599             See L<Test2::API::InterceptResult::Event> for details on what
600             C<error_messages()> returns.
601              
602             =back
603              
604             =head1 SOURCE
605              
606             The source code repository for Test2 can be found at
607             F<http://github.com/Test-More/test-more/>.
608              
609             =head1 MAINTAINERS
610              
611             =over 4
612              
613             =item Chad Granum E<lt>exodist@cpan.orgE<gt>
614              
615             =back
616              
617             =head1 AUTHORS
618              
619             =over 4
620              
621             =item Chad Granum E<lt>exodist@cpan.orgE<gt>
622              
623             =back
624              
625             =head1 COPYRIGHT
626              
627             Copyright 2020 Chad Granum E<lt>exodist@cpan.orgE<gt>.
628              
629             This program is free software; you can redistribute it and/or
630             modify it under the same terms as Perl itself.
631              
632             See F<http://dev.perl.org/licenses/>
633              
634             =cut