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   191 use strict;
  57         53  
  57         1373  
3 57     57   163 use warnings;
  57         49  
  57         1877  
4              
5             our $VERSION = '0.000044';
6              
7 57     57   177 use Carp qw/confess croak longmess/;
  57         66  
  57         2946  
8 57     57   208 use Scalar::Util qw/weaken/;
  57         47  
  57         2113  
9 57     57   201 use Test2::Util qw/get_tid try pkg_to_file get_tid/;
  57         62  
  57         2282  
10              
11 57     57   197 use Test2::Util::Trace();
  57         63  
  57         623  
12 57     57   160 use Test2::API();
  57         104  
  57         3910  
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 57     57   197 use Test2::Util::ExternalMeta qw/meta get_meta set_meta delete_meta/;
  57         55  
  57         3088  
25 57         302 use Test2::Util::HashBase qw{
26             stack hub trace _on_release _depth _is_canon _is_spawn _aborted
27             errno eval_error child_error
28 57     57   207 };
  57         62  
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 72     72 0 98 my $self = shift;
37              
38             confess "The 'trace' attribute is required"
39 72 100       366 unless $self->{+TRACE};
40              
41             confess "The 'hub' attribute is required"
42 71 100       244 unless $self->{+HUB};
43              
44 70 100       304 $self->{+_DEPTH} = 0 unless defined $self->{+_DEPTH};
45              
46 70 50       950 $self->{+ERRNO} = $! unless exists $self->{+ERRNO};
47 70 50       238 $self->{+EVAL_ERROR} = $@ unless exists $self->{+EVAL_ERROR};
48 70 50       267 $self->{+CHILD_ERROR} = $? unless exists $self->{+CHILD_ERROR};
49             }
50              
51 18     18 1 42 sub snapshot { bless {%{$_[0]}, _is_canon => undef, _is_spawn => undef, _aborted => undef}, __PACKAGE__ }
  18         167  
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 1287 100 66 1287   12853 return unless $_[0]->{+_IS_CANON} || $_[0]->{+_IS_SPAWN};
60 14 100 66     48 return if $_[0]->{+_ABORTED} && ${$_[0]->{+_ABORTED}};
  14         183  
61 10         14 my ($self) = @_;
62              
63 10         13 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     53 if($self->{+EVAL_ERROR} eq $@ && $hub->is_local) {
69 4   66     13 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       96 return if $self->{+_IS_SPAWN};
90              
91             # Remove the key itself to avoid a slow memory leak
92 5         11 delete $CONTEXTS->{$hid};
93 5         9 $self->{+_IS_CANON} = undef;
94              
95 5 50       20 if (my $cbk = $self->{+_ON_RELEASE}) {
96 0         0 $_->($self) for reverse @$cbk;
97             }
98 5 100       10 if (my $hcbk = $hub->{_context_release}) {
99 1         3 $_->($self) for reverse @$hcbk;
100             }
101 5         159 $_->($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 1185     1185 1 3673 my ($self) = @_;
110              
111             ($!, $@, $?) = @$self{+ERRNO, +EVAL_ERROR, +CHILD_ERROR} and return $self->{+_IS_SPAWN} = undef
112 1185 100 50     2368 if $self->{+_IS_SPAWN};
113              
114             croak "release() should not be called on context that is neither canon nor a child"
115 1100 100       1718 unless $self->{+_IS_CANON};
116              
117 1099         927 my $hub = $self->{+HUB};
118 1099         1052 my $hid = $hub->{hid};
119              
120             croak "context thinks it is canon, but it is not"
121 1099 50 33     4407 unless $CONTEXTS->{$hid} && $CONTEXTS->{$hid} == $self;
122              
123             # Remove the key itself to avoid a slow memory leak
124 1099         1162 $self->{+_IS_CANON} = undef;
125 1099         1559 delete $CONTEXTS->{$hid};
126              
127 1099 100       1615 if (my $cbk = $self->{+_ON_RELEASE}) {
128 2         6 $_->($self) for reverse @$cbk;
129             }
130 1099 100       1541 if (my $hcbk = $hub->{_context_release}) {
131 38         64 $_->($self) for reverse @$hcbk;
132             }
133 1099         1774 $_->($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 1099         3607 ($!, $@, $?) = @$self{+ERRNO, +EVAL_ERROR, +CHILD_ERROR};
139              
140 1099         1777 return;
141             }
142              
143             sub do_in_context {
144 2     2 1 109 my $self = shift;
145 2         4 my ($sub, @args) = @_;
146              
147             # We need to update the pid/tid and error vars.
148 2         4 my $clone = $self->snapshot;
149 2         6 @$clone{+ERRNO, +EVAL_ERROR, +CHILD_ERROR} = ($!, $@, $?);
150 2         7 $clone->{+TRACE} = $clone->{+TRACE}->snapshot;
151 2         6 $clone->{+TRACE}->set_pid($$);
152 2         5 $clone->{+TRACE}->set_tid(get_tid());
153              
154 2         1 my $hub = $clone->{+HUB};
155 2         6 my $hid = $hub->hid;
156              
157 2         2 my $old = $CONTEXTS->{$hid};
158              
159 2         1 $clone->{+_IS_CANON} = 1;
160 2         3 $CONTEXTS->{$hid} = $clone;
161 2         5 weaken($CONTEXTS->{$hid});
162 2         5 my ($ok, $err) = &try($sub, @args);
163 2     2   7 my ($rok, $rerr) = try { $clone->release };
  2         3  
164 2         5 delete $clone->{+_IS_CANON};
165              
166 2 100       5 if ($old) {
167 1         2 $CONTEXTS->{$hid} = $old;
168 1         3 weaken($CONTEXTS->{$hid});
169             }
170             else {
171 1         2 delete $CONTEXTS->{$hid};
172             }
173              
174 2 100       5 die $err unless $ok;
175 1 50       4 die $rerr unless $rok;
176             }
177              
178             sub done_testing {
179 38     38 1 187 my $self = shift;
180 38         149 $self->hub->finalize($self->trace, 1);
181 38         83 return;
182             }
183              
184             sub throw {
185 2     2 1 13 my ($self, $msg) = @_;
186 2 50       5 ${$self->{+_ABORTED}}++ if $self->{+_ABORTED};
  2         2  
187 2 50 33     8 $self->release if $self->{+_IS_CANON} || $self->{+_IS_SPAWN};
188 2         7 $self->trace->throw($msg);
189             }
190              
191             sub alert {
192 1     1 1 10 my ($self, $msg) = @_;
193 1         4 $self->trace->alert($msg);
194             }
195              
196             sub send_event {
197 120     120 1 123 my $self = shift;
198 120         113 my $event = shift;
199 120         193 my %args = @_;
200              
201 120   33     252 my $pkg = $LOADED{$event} || $self->_parse_event($event);
202              
203             $self->{+HUB}->send(
204             $pkg->new(
205 120         314 trace => $self->{+TRACE}->snapshot,
206             %args,
207             )
208             );
209             }
210              
211             sub build_event {
212 41     41 1 37 my $self = shift;
213 41         39 my $event = shift;
214 41         110 my %args = @_;
215              
216 41   33     92 my $pkg = $LOADED{$event} || $self->_parse_event($event);
217              
218             $pkg->new(
219 41         108 trace => $self->{+TRACE}->snapshot,
220             %args,
221             );
222             }
223              
224             sub ok {
225 967     967 1 240688 my $self = shift;
226 967         1198 my ($pass, $name, $diag) = @_;
227              
228 967         1180 my $hub = $self->{+HUB};
229              
230             my $e = bless {
231 967         849 trace => bless( {%{$self->{+TRACE}}}, 'Test2::Util::Trace'),
  967         5402  
232             pass => $pass,
233             name => $name,
234             }, 'Test2::Event::Ok';
235 967         2621 $e->init;
236              
237 967         2252 $hub->send($e);
238 966 100       4327 return $e if $pass;
239              
240 15         25 $self->failure_diag($e);
241              
242 15 100 100     59 if ($diag && @$diag) {
243 11         24 $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         23 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     87 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       59 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       58 my $msg = defined($name)
270             ? qq[${prefix}Failed test '$name'\n$debug.\n]
271             : qq[${prefix}Failed test $debug.\n];
272              
273 23         35 $self->diag($msg);
274             }
275              
276             sub skip {
277 2     2 1 3 my $self = shift;
278 2         4 my ($name, $reason, @extra) = @_;
279 2         5 $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 69 my $self = shift;
290 25         26 my ($message) = @_;
291 25         37 $self->send_event('Note', message => $message);
292             }
293              
294             sub diag {
295 77     77 1 129 my $self = shift;
296 77         70 my ($message) = @_;
297 77         70 my $hub = $self->{+HUB};
298 77         119 $self->send_event(
299             'Diag',
300             message => $message,
301             );
302             }
303              
304             sub plan {
305 14     14 1 84 my ($self, $max, $directive, $reason) = @_;
306 14 100 100     137 ${$self->{+_ABORTED}}++ if $self->{+_ABORTED} && $directive && $directive =~ m/^(SKIP|skip_all)$/;
  3   100     8  
307 14         61 $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         3 my $event = shift;
319              
320 3         3 my $pkg;
321 3 100       10 if ($event =~ m/^\+(.*)/) {
322 2         5 $pkg = $1;
323             }
324             else {
325 1         2 $pkg = "Test2::Event::$event";
326             }
327              
328 3 100       7 unless ($LOADED{$pkg}) {
329 1         2 my $file = pkg_to_file($pkg);
330 1     1   5 my ($ok, $err) = try { require $file };
  1         294  
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         8 $LOADED{$event} = $pkg;
341              
342 2         5 return $pkg;
343             }
344              
345             1;
346              
347             __END__