File Coverage

blib/lib/Test2/API/Context.pm
Criterion Covered Total %
statement 219 231 94.8
branch 63 74 85.1
condition 28 43 65.1
subroutine 36 39 92.3
pod 22 25 88.0
total 368 412 89.3


line stmt bran cond sub pod time code
1             package Test2::API::Context;
2 246     246   1826 use strict;
  246         590  
  246         7891  
3 246     246   1331 use warnings;
  246         508  
  246         11495  
4              
5             our $VERSION = '1.302180';
6              
7              
8 246     246   1555 use Carp qw/confess croak/;
  246         626  
  246         15491  
9 246     246   1875 use Scalar::Util qw/weaken blessed/;
  246         715  
  246         14481  
10 246     246   1684 use Test2::Util qw/get_tid try pkg_to_file get_tid/;
  246         724  
  246         13917  
11              
12 246     246   1711 use Test2::EventFacet::Trace();
  246         654  
  246         5067  
13 246     246   1428 use Test2::API();
  246         594  
  246         25430  
14              
15             # Preload some key event types
16             my %LOADED = (
17             map {
18             my $pkg = "Test2::Event::$_";
19             my $file = "Test2/Event/$_.pm";
20             require $file unless $INC{$file};
21             ( $pkg => $pkg, $_ => $pkg )
22             } qw/Ok Diag Note Plan Bail Exception Waiting Skip Subtest Pass Fail V2/
23             );
24              
25 246     246   1993 use Test2::Util::ExternalMeta qw/meta get_meta set_meta delete_meta/;
  246         686  
  246         20517  
26 246         1946 use Test2::Util::HashBase qw{
27             stack hub trace _on_release _depth _is_canon _is_spawn _aborted
28             errno eval_error child_error thrown
29 246     246   1868 };
  246         558  
30              
31             # Private, not package vars
32             # It is safe to cache these.
33             my $ON_RELEASE = Test2::API::_context_release_callbacks_ref();
34             my $CONTEXTS = Test2::API::_contexts_ref();
35              
36             sub init {
37 465     465 0 1090 my $self = shift;
38              
39             confess "The 'trace' attribute is required"
40 465 100       1948 unless $self->{+TRACE};
41              
42             confess "The 'hub' attribute is required"
43 464 100       1590 unless $self->{+HUB};
44              
45 463 100       1782 $self->{+_DEPTH} = 0 unless defined $self->{+_DEPTH};
46              
47 463 50       5002 $self->{+ERRNO} = $! unless exists $self->{+ERRNO};
48 463 50       2180 $self->{+EVAL_ERROR} = $@ unless exists $self->{+EVAL_ERROR};
49 463 50       2377 $self->{+CHILD_ERROR} = $? unless exists $self->{+CHILD_ERROR};
50             }
51              
52 259     259 1 480 sub snapshot { bless {%{$_[0]}, _is_canon => undef, _is_spawn => undef, _aborted => undef}, __PACKAGE__ }
  259         2673  
53              
54             sub restore_error_vars {
55 0     0 1 0 my $self = shift;
56 0         0 ($!, $@, $?) = @$self{+ERRNO, +EVAL_ERROR, +CHILD_ERROR};
57             }
58              
59             sub DESTROY {
60 14081 100 100 14081   151404 return unless $_[0]->{+_IS_CANON} || $_[0]->{+_IS_SPAWN};
61 98 100 66     422 return if $_[0]->{+_ABORTED} && ${$_[0]->{+_ABORTED}};
  98         7936  
62 16         80 my ($self) = @_;
63              
64 16         80 my $hub = $self->{+HUB};
65 16         77 my $hid = $hub->{hid};
66              
67             # Do not show the warning if it looks like an exception has been thrown, or
68             # if the context is not local to this process or thread.
69             {
70             # Sometimes $@ is uninitialized, not a problem in this case so do not
71             # show the warning about using eq.
72 246     246   2075 no warnings 'uninitialized';
  246         637  
  246         649355  
  16         47  
73 16 100 100     150 if($self->{+EVAL_ERROR} eq $@ && $hub->is_local) {
74 5         35 require Carp;
75 5         441 my $mess = Carp::longmess("Context destroyed");
76 5   66     686 my $frame = $self->{+_IS_SPAWN} || $self->{+TRACE}->frame;
77 5         79 warn <<" EOT";
78             A context appears to have been destroyed without first calling release().
79             Based on \$@ it does not look like an exception was thrown (this is not always
80             a reliable test)
81              
82             This is a problem because the global error variables (\$!, \$@, and \$?) will
83             not be restored. In addition some release callbacks will not work properly from
84             inside a DESTROY method.
85              
86             Here are the context creation details, just in case a tool forgot to call
87             release():
88             File: $frame->[1]
89             Line: $frame->[2]
90             Tool: $frame->[3]
91              
92             Here is a trace to the code that caused the context to be destroyed, this could
93             be an exit(), a goto, or simply the end of a scope:
94             $mess
95              
96             Cleaning up the CONTEXT stack...
97             EOT
98             }
99             }
100              
101 16 100       266 return if $self->{+_IS_SPAWN};
102              
103             # Remove the key itself to avoid a slow memory leak
104 10         80 delete $CONTEXTS->{$hid};
105 10         38 $self->{+_IS_CANON} = undef;
106              
107 10 50       62 if (my $cbk = $self->{+_ON_RELEASE}) {
108 0         0 $_->($self) for reverse @$cbk;
109             }
110 10 100       63 if (my $hcbk = $hub->{_context_release}) {
111 2         9 $_->($self) for reverse @$hcbk;
112             }
113 10         990 $_->($self) for reverse @$ON_RELEASE;
114             }
115              
116             # release exists to implement behaviors like die-on-fail. In die-on-fail you
117             # want to die after a failure, but only after diagnostics have been reported.
118             # The ideal time for the die to happen is when the context is released.
119             # Unfortunately die does not work in a DESTROY block.
120             sub release {
121 13293     13293 1 26716 my ($self) = @_;
122              
123 13293 100 50     28596 ($!, $@, $?) = @$self{+ERRNO, +EVAL_ERROR, +CHILD_ERROR} and return if $self->{+THROWN};
124              
125             ($!, $@, $?) = @$self{+ERRNO, +EVAL_ERROR, +CHILD_ERROR} and return $self->{+_IS_SPAWN} = undef
126 13266 100 50     49785 if $self->{+_IS_SPAWN};
127              
128             croak "release() should not be called on context that is neither canon nor a child"
129 8280 100       17601 unless $self->{+_IS_CANON};
130              
131 8279         12338 my $hub = $self->{+HUB};
132 8279         13909 my $hid = $hub->{hid};
133              
134             croak "context thinks it is canon, but it is not"
135 8279 50 33     39022 unless $CONTEXTS->{$hid} && $CONTEXTS->{$hid} == $self;
136              
137             # Remove the key itself to avoid a slow memory leak
138 8279         15230 $self->{+_IS_CANON} = undef;
139 8279         20786 delete $CONTEXTS->{$hid};
140              
141 8279 100       18032 if (my $cbk = $self->{+_ON_RELEASE}) {
142 2         8 $_->($self) for reverse @$cbk;
143             }
144 8279 100       17258 if (my $hcbk = $hub->{_context_release}) {
145 47         115 $_->($self) for reverse @$hcbk;
146             }
147 8279         17949 $_->($self) for reverse @$ON_RELEASE;
148              
149             # Do this last so that nothing else changes them.
150             # If one of the hooks dies then these do not get restored, this is
151             # intentional
152 8279         30806 ($!, $@, $?) = @$self{+ERRNO, +EVAL_ERROR, +CHILD_ERROR};
153              
154 8279         18839 return;
155             }
156              
157             sub do_in_context {
158 7     7 1 17 my $self = shift;
159 7         16 my ($sub, @args) = @_;
160              
161             # We need to update the pid/tid and error vars.
162 7         21 my $clone = $self->snapshot;
163 7         28 @$clone{+ERRNO, +EVAL_ERROR, +CHILD_ERROR} = ($!, $@, $?);
164 7         26 $clone->{+TRACE} = $clone->{+TRACE}->snapshot(pid => $$, tid => get_tid());
165              
166 7         14 my $hub = $clone->{+HUB};
167 7         27 my $hid = $hub->hid;
168              
169 7         12 my $old = $CONTEXTS->{$hid};
170              
171 7         12 $clone->{+_IS_CANON} = 1;
172 7         15 $CONTEXTS->{$hid} = $clone;
173 7         25 weaken($CONTEXTS->{$hid});
174 7         24 my ($ok, $err) = &try($sub, @args);
175 7     7   43 my ($rok, $rerr) = try { $clone->release };
  7         19  
176 7         25 delete $clone->{+_IS_CANON};
177              
178 7 100       18 if ($old) {
179 1         3 $CONTEXTS->{$hid} = $old;
180 1         5 weaken($CONTEXTS->{$hid});
181             }
182             else {
183 6         12 delete $CONTEXTS->{$hid};
184             }
185              
186 7 100       18 die $err unless $ok;
187 6 50       20 die $rerr unless $rok;
188             }
189              
190             sub done_testing {
191 83     83 1 510 my $self = shift;
192 83         462 $self->hub->finalize($self->trace, 1);
193 83         252 return;
194             }
195              
196             sub throw {
197 23     23 1 76 my ($self, $msg) = @_;
198 23         63 $self->{+THROWN} = 1;
199 23 50       90 ${$self->{+_ABORTED}}++ if $self->{+_ABORTED};
  23         63  
200 23 50 66     161 $self->release if $self->{+_IS_CANON} || $self->{+_IS_SPAWN};
201 23         87 $self->trace->throw($msg);
202             }
203              
204             sub alert {
205 3     3 1 13 my ($self, $msg) = @_;
206 3         13 $self->trace->alert($msg);
207             }
208              
209             sub send_ev2_and_release {
210 0     0 1 0 my $self = shift;
211 0         0 my $out = $self->send_ev2(@_);
212 0         0 $self->release;
213 0         0 return $out;
214             }
215              
216             sub send_ev2 {
217 214     214 1 650 my $self = shift;
218              
219 214         1034 my $e;
220             {
221 214         640 local $Carp::CarpLevel = $Carp::CarpLevel + 1;
  214         1140  
222             $e = Test2::Event::V2->new(
223 214         1765 trace => $self->{+TRACE}->snapshot,
224             @_,
225             );
226             }
227              
228 214 100       1351 if ($self->{+_ABORTED}) {
229 3         11 my $f = $e->facet_data;
230 3 50 33     42 ${$self->{+_ABORTED}}++ if $f->{control}->{halt} || defined($f->{control}->{terminate}) || defined($e->terminate);
  0   33     0  
231             }
232 214         1517 $self->{+HUB}->send($e);
233             }
234              
235             sub build_ev2 {
236 3     3 0 19 my $self = shift;
237              
238 3         5 local $Carp::CarpLevel = $Carp::CarpLevel + 1;
239             Test2::Event::V2->new(
240 3         13 trace => $self->{+TRACE}->snapshot,
241             @_,
242             );
243             }
244              
245             sub send_event_and_release {
246 0     0 1 0 my $self = shift;
247 0         0 my $out = $self->send_event(@_);
248 0         0 $self->release;
249 0         0 return $out;
250             }
251              
252             sub send_event {
253 1253     1253 1 2209 my $self = shift;
254 1253         2132 my $event = shift;
255 1253         3593 my %args = @_;
256              
257 1253   66     4200 my $pkg = $LOADED{$event} || $self->_parse_event($event);
258              
259 1253         2089 my $e;
260             {
261 1253         1971 local $Carp::CarpLevel = $Carp::CarpLevel + 1;
  1253         2446  
262             $e = $pkg->new(
263 1253         4989 trace => $self->{+TRACE}->snapshot,
264             %args,
265             );
266             }
267              
268 1253 100       3429 if ($self->{+_ABORTED}) {
269 1138         3387 my $f = $e->facet_data;
270 1138 100 100     7582 ${$self->{+_ABORTED}}++ if $f->{control}->{halt} || defined($f->{control}->{terminate}) || defined($e->terminate);
  29   66     176  
271             }
272 1253         5068 $self->{+HUB}->send($e);
273             }
274              
275             sub build_event {
276 120     120 1 234 my $self = shift;
277 120         204 my $event = shift;
278 120         649 my %args = @_;
279              
280 120   33     467 my $pkg = $LOADED{$event} || $self->_parse_event($event);
281              
282 120         318 local $Carp::CarpLevel = $Carp::CarpLevel + 1;
283             $pkg->new(
284 120         466 trace => $self->{+TRACE}->snapshot,
285             %args,
286             );
287             }
288              
289             sub pass {
290 2     2 1 10 my $self = shift;
291 2         4 my ($name) = @_;
292              
293             my $e = bless(
294             {
295 2         4 trace => bless({%{$self->{+TRACE}}}, 'Test2::EventFacet::Trace'),
  2         14  
296             name => $name,
297             },
298             "Test2::Event::Pass"
299             );
300              
301 2         8 $self->{+HUB}->send($e);
302 2         5 return $e;
303             }
304              
305             sub pass_and_release {
306 1903     1903 1 3796 my $self = shift;
307 1903         3821 my ($name) = @_;
308              
309             my $e = bless(
310             {
311 1903         3075 trace => bless({%{$self->{+TRACE}}}, 'Test2::EventFacet::Trace'),
  1903         19690  
312             name => $name,
313             },
314             "Test2::Event::Pass"
315             );
316              
317 1903         8887 $self->{+HUB}->send($e);
318 1903         6623 $self->release;
319 1903         14870 return 1;
320             }
321              
322             sub fail {
323 2     2 1 11 my $self = shift;
324 2         8 my ($name, @diag) = @_;
325              
326             my $e = bless(
327             {
328 2         6 trace => bless({%{$self->{+TRACE}}}, 'Test2::EventFacet::Trace'),
  2         23  
329             name => $name,
330             },
331             "Test2::Event::Fail"
332             );
333              
334 2         8 for my $msg (@diag) {
335 2 100       8 if (ref($msg) eq 'Test2::EventFacet::Info::Table') {
336 1         5 $e->add_info({tag => 'DIAG', debug => 1, $msg->info_args});
337             }
338             else {
339 1         8 $e->add_info({tag => 'DIAG', debug => 1, details => $msg});
340             }
341             }
342              
343 2         13 $self->{+HUB}->send($e);
344 2         10 return $e;
345             }
346              
347             sub fail_and_release {
348 27     27 1 62 my $self = shift;
349 27         76 my ($name, @diag) = @_;
350              
351             my $e = bless(
352             {
353 27         55 trace => bless({%{$self->{+TRACE}}}, 'Test2::EventFacet::Trace'),
  27         323  
354             name => $name,
355             },
356             "Test2::Event::Fail"
357             );
358              
359 27         86 for my $msg (@diag) {
360 21 100       66 if (ref($msg) eq 'Test2::EventFacet::Info::Table') {
361 1         5 $e->add_info({tag => 'DIAG', debug => 1, $msg->info_args});
362             }
363             else {
364 20         89 $e->add_info({tag => 'DIAG', debug => 1, details => $msg});
365             }
366             }
367              
368 27         130 $self->{+HUB}->send($e);
369 27         96 $self->release;
370 27         282 return 0;
371             }
372              
373             sub ok {
374 27     27 1 310 my $self = shift;
375 27         106 my ($pass, $name, $on_fail) = @_;
376              
377 27         129 my $hub = $self->{+HUB};
378              
379             my $e = bless {
380 27         73 trace => bless( {%{$self->{+TRACE}}}, 'Test2::EventFacet::Trace'),
  27         431  
381             pass => $pass,
382             name => $name,
383             }, 'Test2::Event::Ok';
384 27         266 $e->init;
385              
386 27         261 $hub->send($e);
387 27 100       313 return $e if $pass;
388              
389 6         33 $self->failure_diag($e);
390              
391 6 100 100     49 if ($on_fail && @$on_fail) {
392 1         7 $self->diag($_) for @$on_fail;
393             }
394              
395 6         31 return $e;
396             }
397              
398             sub failure_diag {
399 18     18 0 36 my $self = shift;
400 18         39 my ($e) = @_;
401              
402             # Figure out the debug info, this is typically the file name and line
403             # number, but can also be a custom message. If no trace object is provided
404             # then we have nothing useful to display.
405 18         60 my $name = $e->name;
406 18         62 my $trace = $e->trace;
407 18 50       79 my $debug = $trace ? $trace->debug : "[No trace info available]";
408              
409             # Create the initial diagnostics. If the test has a name we put the debug
410             # info on a second line, this behavior is inherited from Test::Builder.
411 18 100       85 my $msg = defined($name)
412             ? qq[Failed test '$name'\n$debug.\n]
413             : qq[Failed test $debug.\n];
414              
415 18         62 $self->diag($msg);
416             }
417              
418             sub skip {
419 33     33 1 56 my $self = shift;
420 33         86 my ($name, $reason, @extra) = @_;
421 33         102 $self->send_event(
422             'Skip',
423             name => $name,
424             reason => $reason,
425             pass => 1,
426             @extra,
427             );
428             }
429              
430             sub note {
431 198     198 1 456 my $self = shift;
432 198         438 my ($message) = @_;
433 198         597 $self->send_event('Note', message => $message);
434             }
435              
436             sub diag {
437 775     775 1 1380 my $self = shift;
438 775         1856 my ($message) = @_;
439 775         1323 my $hub = $self->{+HUB};
440 775         1909 $self->send_event(
441             'Diag',
442             message => $message,
443             );
444             }
445              
446             sub plan {
447 228     228 1 899 my ($self, $max, $directive, $reason) = @_;
448 228         1098 $self->send_event('Plan', max => $max, directive => $directive, reason => $reason);
449             }
450              
451             sub bail {
452 8     8 1 37 my ($self, $reason) = @_;
453 8         29 $self->send_event('Bail', reason => $reason);
454             }
455              
456             sub _parse_event {
457 5     5   30 my $self = shift;
458 5         18 my $event = shift;
459              
460 5         8 my $pkg;
461 5 100       35 if ($event =~ m/^\+(.*)/) {
462 3         10 $pkg = $1;
463             }
464             else {
465 2         5 $pkg = "Test2::Event::$event";
466             }
467              
468 5 100       38 unless ($LOADED{$pkg}) {
469 3         17 my $file = pkg_to_file($pkg);
470 3     3   23 my ($ok, $err) = try { require $file };
  3         980  
471 3 100       22 $self->throw("Could not load event module '$pkg': $err")
472             unless $ok;
473              
474 2         9 $LOADED{$pkg} = $pkg;
475             }
476              
477 4 50       39 confess "'$pkg' is not a subclass of 'Test2::Event'"
478             unless $pkg->isa('Test2::Event');
479              
480 4         13 $LOADED{$event} = $pkg;
481              
482 4         22 return $pkg;
483             }
484              
485             1;
486              
487             __END__