File Coverage

blib/lib/Test2/API/Context.pm
Criterion Covered Total %
statement 162 166 97.5
branch 53 66 80.3
condition 24 38 63.1
subroutine 29 30 96.6
pod 15 17 88.2
total 283 317 89.2


line stmt bran cond sub pod time code
1             package Test2::API::Context;
2 57     57   236 use strict;
  57         67  
  57         1344  
3 57     57   161 use warnings;
  57         57  
  57         2317  
4              
5             our $VERSION = '0.000043';
6             $VERSION = eval $VERSION; ## no critic (BuiltinFunctions::ProhibitStringyEval)
7              
8 57     57   181 use Carp qw/confess croak longmess/;
  57         63  
  57         2977  
9 57     57   209 use Scalar::Util qw/weaken/;
  57         52  
  57         2103  
10 57     57   180 use Test2::Util qw/get_tid try pkg_to_file get_tid/;
  57         67  
  57         2282  
11              
12 57     57   208 use Test2::Util::Trace();
  57         54  
  57         684  
13 57     57   155 use Test2::API();
  57         49  
  57         4087  
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/
23             );
24              
25 57     57   453 use Test2::Util::ExternalMeta qw/meta get_meta set_meta delete_meta/;
  57         73  
  57         3138  
26 57         270 use Test2::Util::HashBase qw{
27             stack hub trace _on_release _depth _is_canon _is_spawn _aborted
28             errno eval_error child_error
29 57     57   208 };
  57         56  
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 72     72 0 117 my $self = shift;
38              
39             confess "The 'trace' attribute is required"
40 72 100       349 unless $self->{+TRACE};
41              
42             confess "The 'hub' attribute is required"
43 71 100       245 unless $self->{+HUB};
44              
45 70 100       220 $self->{+_DEPTH} = 0 unless defined $self->{+_DEPTH};
46              
47 70 50       953 $self->{+ERRNO} = $! unless exists $self->{+ERRNO};
48 70 50       220 $self->{+EVAL_ERROR} = $@ unless exists $self->{+EVAL_ERROR};
49 70 50       289 $self->{+CHILD_ERROR} = $? unless exists $self->{+CHILD_ERROR};
50             }
51              
52 18     18 1 62 sub snapshot { bless {%{$_[0]}, _is_canon => undef, _is_spawn => undef, _aborted => undef}, __PACKAGE__ }
  18         169  
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 1290 100 66 1290   12979 return unless $_[0]->{+_IS_CANON} || $_[0]->{+_IS_SPAWN};
61 14 100 66     44 return if $_[0]->{+_ABORTED} && ${$_[0]->{+_ABORTED}};
  14         187  
62 10         11 my ($self) = @_;
63              
64 10         15 my $hub = $self->{+HUB};
65 10         18 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 10 100 100     55 if($self->{+EVAL_ERROR} eq $@ && $hub->is_local) {
70 4   66     16 my $frame = $self->{+_IS_SPAWN} || $self->{+TRACE}->frame;
71 4         60 warn <<" EOT";
72             A context appears to have been destroyed without first calling release().
73             Based on \$@ it does not look like an exception was thrown (this is not always
74             a reliable test)
75              
76             This is a problem because the global error variables (\$!, \$@, and \$?) will
77             not be restored. In addition some release callbacks will not work properly from
78             inside a DESTROY method.
79              
80             Here are the context creation details, just in case a tool forgot to call
81             release():
82             File: $frame->[1]
83             Line: $frame->[2]
84             Tool: $frame->[3]
85              
86             Cleaning up the CONTEXT stack...
87             EOT
88             }
89              
90 10 100       89 return if $self->{+_IS_SPAWN};
91              
92             # Remove the key itself to avoid a slow memory leak
93 5         13 delete $CONTEXTS->{$hid};
94 5         8 $self->{+_IS_CANON} = undef;
95              
96 5 50       16 if (my $cbk = $self->{+_ON_RELEASE}) {
97 0         0 $_->($self) for reverse @$cbk;
98             }
99 5 100       16 if (my $hcbk = $hub->{_context_release}) {
100 1         3 $_->($self) for reverse @$hcbk;
101             }
102 5         150 $_->($self) for reverse @$ON_RELEASE;
103             }
104              
105             # release exists to implement behaviors like die-on-fail. In die-on-fail you
106             # want to die after a failure, but only after diagnostics have been reported.
107             # The ideal time for the die to happen is when the context is released.
108             # Unfortunately die does not work in a DESTROY block.
109             sub release {
110 1188     1188 1 4599 my ($self) = @_;
111              
112             ($!, $@, $?) = @$self{+ERRNO, +EVAL_ERROR, +CHILD_ERROR} and return $self->{+_IS_SPAWN} = undef
113 1188 100 50     2482 if $self->{+_IS_SPAWN};
114              
115             croak "release() should not be called on context that is neither canon nor a child"
116 1103 100       1701 unless $self->{+_IS_CANON};
117              
118 1102         905 my $hub = $self->{+HUB};
119 1102         1020 my $hid = $hub->{hid};
120              
121             croak "context thinks it is canon, but it is not"
122 1102 50 33     4391 unless $CONTEXTS->{$hid} && $CONTEXTS->{$hid} == $self;
123              
124             # Remove the key itself to avoid a slow memory leak
125 1102         1125 $self->{+_IS_CANON} = undef;
126 1102         1544 delete $CONTEXTS->{$hid};
127              
128 1102 100       1706 if (my $cbk = $self->{+_ON_RELEASE}) {
129 2         4 $_->($self) for reverse @$cbk;
130             }
131 1102 100       1585 if (my $hcbk = $hub->{_context_release}) {
132 38         61 $_->($self) for reverse @$hcbk;
133             }
134 1102         1757 $_->($self) for reverse @$ON_RELEASE;
135              
136             # Do this last so that nothing else changes them.
137             # If one of the hooks dies then these do not get restored, this is
138             # intentional
139 1102         3588 ($!, $@, $?) = @$self{+ERRNO, +EVAL_ERROR, +CHILD_ERROR};
140              
141 1102         1701 return;
142             }
143              
144             sub do_in_context {
145 2     2 1 11 my $self = shift;
146 2         3 my ($sub, @args) = @_;
147              
148             # We need to update the pid/tid and error vars.
149 2         3 my $clone = $self->snapshot;
150 2         8 @$clone{+ERRNO, +EVAL_ERROR, +CHILD_ERROR} = ($!, $@, $?);
151 2         5 $clone->{+TRACE} = $clone->{+TRACE}->snapshot;
152 2         7 $clone->{+TRACE}->set_pid($$);
153 2         4 $clone->{+TRACE}->set_tid(get_tid());
154              
155 2         2 my $hub = $clone->{+HUB};
156 2         6 my $hid = $hub->hid;
157              
158 2         3 my $old = $CONTEXTS->{$hid};
159              
160 2         3 $clone->{+_IS_CANON} = 1;
161 2         3 $CONTEXTS->{$hid} = $clone;
162 2         6 weaken($CONTEXTS->{$hid});
163 2         6 my ($ok, $err) = &try($sub, @args);
164 2     2   8 my ($rok, $rerr) = try { $clone->release };
  2         3  
165 2         5 delete $clone->{+_IS_CANON};
166              
167 2 100       4 if ($old) {
168 1         2 $CONTEXTS->{$hid} = $old;
169 1         2 weaken($CONTEXTS->{$hid});
170             }
171             else {
172 1         2 delete $CONTEXTS->{$hid};
173             }
174              
175 2 100       6 die $err unless $ok;
176 1 50       3 die $rerr unless $rok;
177             }
178              
179             sub done_testing {
180 38     38 1 196 my $self = shift;
181 38         139 $self->hub->finalize($self->trace, 1);
182 38         81 return;
183             }
184              
185             sub throw {
186 2     2 1 13 my ($self, $msg) = @_;
187 2 50       5 ${$self->{+_ABORTED}}++ if $self->{+_ABORTED};
  2         3  
188 2 50 33     8 $self->release if $self->{+_IS_CANON} || $self->{+_IS_SPAWN};
189 2         5 $self->trace->throw($msg);
190             }
191              
192             sub alert {
193 1     1 1 11 my ($self, $msg) = @_;
194 1         3 $self->trace->alert($msg);
195             }
196              
197             sub send_event {
198 120     120 1 92 my $self = shift;
199 120         99 my $event = shift;
200 120         184 my %args = @_;
201              
202 120   33     242 my $pkg = $LOADED{$event} || $self->_parse_event($event);
203              
204             $self->{+HUB}->send(
205             $pkg->new(
206 120         291 trace => $self->{+TRACE}->snapshot,
207             %args,
208             )
209             );
210             }
211              
212             sub build_event {
213 41     41 1 46 my $self = shift;
214 41         41 my $event = shift;
215 41         141 my %args = @_;
216              
217 41   33     108 my $pkg = $LOADED{$event} || $self->_parse_event($event);
218              
219             $pkg->new(
220 41         112 trace => $self->{+TRACE}->snapshot,
221             %args,
222             );
223             }
224              
225             sub ok {
226 970     970 1 263120 my $self = shift;
227 970         1167 my ($pass, $name, $diag) = @_;
228              
229 970         1109 my $hub = $self->{+HUB};
230              
231             my $e = bless {
232 970         818 trace => bless( {%{$self->{+TRACE}}}, 'Test2::Util::Trace'),
  970         5524  
233             pass => $pass,
234             name => $name,
235             }, 'Test2::Event::Ok';
236 970         2765 $e->init;
237              
238 970         2383 $hub->send($e);
239 969 100       4040 return $e if $pass;
240              
241 15         26 $self->failure_diag($e);
242              
243 15 100 100     61 if ($diag && @$diag) {
244 11         19 $self->diag($_) for @$diag
245             }
246              
247 15         23 return $e;
248             }
249              
250             sub failure_diag {
251 23     23 0 21 my $self = shift;
252 23         21 my ($e) = @_;
253              
254             # This behavior is inherited from Test::Builder which injected a newline at
255             # the start of the first diagnostics when the harness is active, but not
256             # verbose. This is important to keep the diagnostics from showing up
257             # appended to the existing line, which is hard to read. In a verbose
258             # harness there is no need for this.
259 23 50 33     81 my $prefix = $ENV{HARNESS_ACTIVE} && !$ENV{HARNESS_IS_VERBOSE} ? "\n" : "";
260              
261             # Figure out the debug info, this is typically the file name and line
262             # number, but can also be a custom message. If no trace object is provided
263             # then we have nothing useful to display.
264 23         42 my $name = $e->name;
265 23         65 my $trace = $e->trace;
266 23 50       61 my $debug = $trace ? $trace->debug : "[No trace info available]";
267              
268             # Create the initial diagnostics. If the test has a name we put the debug
269             # info on a second line, this behavior is inherited from Test::Builder.
270 23 100       68 my $msg = defined($name)
271             ? qq[${prefix}Failed test '$name'\n$debug.\n]
272             : qq[${prefix}Failed test $debug.\n];
273              
274 23         38 $self->diag($msg);
275             }
276              
277             sub skip {
278 2     2 1 2 my $self = shift;
279 2         5 my ($name, $reason, @extra) = @_;
280 2         9 $self->send_event(
281             'Skip',
282             name => $name,
283             reason => $reason,
284             pass => 1,
285             @extra,
286             );
287             }
288              
289             sub note {
290 25     25 1 66 my $self = shift;
291 25         22 my ($message) = @_;
292 25         41 $self->send_event('Note', message => $message);
293             }
294              
295             sub diag {
296 77     77 1 138 my $self = shift;
297 77         66 my ($message) = @_;
298 77         75 my $hub = $self->{+HUB};
299 77         118 $self->send_event(
300             'Diag',
301             message => $message,
302             );
303             }
304              
305             sub plan {
306 14     14 1 67 my ($self, $max, $directive, $reason) = @_;
307 14 100 100     122 ${$self->{+_ABORTED}}++ if $self->{+_ABORTED} && $directive && $directive =~ m/^(SKIP|skip_all)$/;
  3   100     8  
308 14         54 $self->send_event('Plan', max => $max, directive => $directive, reason => $reason);
309             }
310              
311             sub bail {
312 1     1 1 2 my ($self, $reason) = @_;
313 1 50       3 ${$self->{+_ABORTED}}++ if $self->{+_ABORTED};
  1         2  
314 1         3 $self->send_event('Bail', reason => $reason);
315             }
316              
317             sub _parse_event {
318 3     3   14 my $self = shift;
319 3         2 my $event = shift;
320              
321 3         4 my $pkg;
322 3 100       10 if ($event =~ m/^\+(.*)/) {
323 2         4 $pkg = $1;
324             }
325             else {
326 1         2 $pkg = "Test2::Event::$event";
327             }
328              
329 3 100       8 unless ($LOADED{$pkg}) {
330 1         4 my $file = pkg_to_file($pkg);
331 1     1   4 my ($ok, $err) = try { require $file };
  1         328  
332 1 50       9 $self->throw("Could not load event module '$pkg': $err")
333             unless $ok;
334              
335 0         0 $LOADED{$pkg} = $pkg;
336             }
337              
338 2 50       10 confess "'$pkg' is not a subclass of 'Test2::Event'"
339             unless $pkg->isa('Test2::Event');
340              
341 2         4 $LOADED{$event} = $pkg;
342              
343 2         5 return $pkg;
344             }
345              
346             1;
347              
348             __END__