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