File Coverage

blib/lib/Test2/AsyncSubtest.pm
Criterion Covered Total %
statement 234 284 82.3
branch 64 108 59.2
condition 28 51 54.9
subroutine 37 40 92.5
pod 14 17 82.3
total 377 500 75.4


line stmt bran cond sub pod time code
1             package Test2::AsyncSubtest;
2 76     76   11865 use strict;
  76         174  
  76         2365  
3 76     76   372 use warnings;
  76         279  
  76         1755  
4              
5 76     76   29809 use Test2::IPC;
  76         70934  
  76         509  
6              
7             our $VERSION = '0.000156';
8              
9             our @CARP_NOT = qw/Test2::Util::HashBase/;
10              
11 76     76   8154 use Carp qw/croak cluck confess/;
  76         189  
  76         4321  
12 76     76   535 use Test2::Util qw/get_tid CAN_THREAD CAN_FORK/;
  76         166  
  76         3825  
13 76     76   461 use Scalar::Util qw/blessed weaken/;
  76         247  
  76         3264  
14 76     76   848 use List::Util qw/first/;
  76         202  
  76         3834  
15              
16 76     76   437 use Test2::API();
  76         141  
  76         1669  
17 76     76   534 use Test2::API::Context();
  76         225  
  76         1741  
18 76     76   517 use Test2::Util::Trace();
  76         237  
  76         1736  
19 76     76   30791 use Test2::Util::Guard();
  76         202  
  76         1550  
20 76     76   480 use Time::HiRes();
  76         162  
  76         1092  
21              
22 76     76   31848 use Test2::AsyncSubtest::Hub();
  76         225  
  76         1530  
23 76     76   33219 use Test2::AsyncSubtest::Event::Attach();
  76         216  
  76         1532  
24 76     76   30652 use Test2::AsyncSubtest::Event::Detach();
  76         210  
  76         2611  
25              
26 76         358 use Test2::Util::HashBase qw{
27             name hub
28             trace frame send_to
29             events
30             finished
31             active
32             stack
33             id cid uuid
34             children
35             _in_use
36             _attached pid tid
37             start_stamp stop_stamp
38 76     76   485 };
  76         180  
39              
40             sub CAN_REALLY_THREAD {
41 77     77 0 264 return 0 unless CAN_THREAD;
42 0 0       0 return 0 unless eval { require threads; threads->VERSION('1.34'); 1 };
  0         0  
  0         0  
  0         0  
43 0         0 return 1;
44             }
45              
46              
47             my $UUID_VIA = Test2::API::_add_uuid_via_ref();
48             my $CID = 1;
49             my @STACK;
50              
51 0 0   0 0 0 sub TOP { @STACK ? $STACK[-1] : undef }
52              
53             sub init {
54 846     846 0 64154 my $self = shift;
55              
56             croak "'name' is a required attribute"
57 846 100       3007 unless $self->{+NAME};
58              
59 845   33     9822 my $to = $self->{+SEND_TO} ||= Test2::API::test2_stack()->top;
60              
61 845         20188 $self->{+STACK} = [@STACK];
62 845         3041 $_->{+_IN_USE}++ for reverse @STACK;
63              
64 845         2239 $self->{+TID} = get_tid;
65 845         3270 $self->{+PID} = $$;
66 845         6096 $self->{+CID} = 'AsyncSubtest-' . $CID++;
67 845         3396 $self->{+ID} = 1;
68 845         2514 $self->{+FINISHED} = 0;
69 845         2099 $self->{+ACTIVE} = 0;
70 845         2349 $self->{+_IN_USE} = 0;
71 845         3403 $self->{+CHILDREN} = [];
72 845 100       2978 $self->{+UUID} = ${$UUID_VIA}->() if defined $$UUID_VIA;
  1         4  
73              
74 845 50       2484 unless($self->{+HUB}) {
75 845         5437 my $ipc = Test2::API::test2_ipc();
76 845         18563 my $formatter = Test2::API::test2_stack->top->format;
77 845   100     22679 my $args = delete $self->{hub_init_args} || {};
78 845         5520 my $hub = Test2::AsyncSubtest::Hub->new(
79             %$args,
80             ipc => $ipc,
81             nested => $to->nested + 1,
82             buffered => 1,
83             formatter => $formatter,
84             );
85 845         16445 weaken($hub->{ast} = $self);
86 845         3495 $self->{+HUB} = $hub;
87             }
88              
89             $self->{+TRACE} ||= Test2::Util::Trace->new(
90             frame => $self->{+FRAME} || [caller(1)],
91             buffered => $to->buffered,
92             nested => $to->nested,
93             cid => $self->{+CID},
94 845   100     13625 uuid => $self->{+UUID},
      33        
95             hid => $to->hid,
96             huuid => $to->uuid,
97             );
98              
99 845         39556 my $hub = $self->{+HUB};
100 845 50       3146 $hub->set_ast_ids({}) unless $hub->ast_ids;
101 845         9182 $hub->listen($self->_listener);
102             }
103              
104             sub _listener {
105 845     845   1652 my $self = shift;
106              
107 845   50     5531 my $events = $self->{+EVENTS} ||= [];
108              
109 845     3862   11219 sub { push @$events => $_[1] };
  3862         873892  
110             }
111              
112             sub context {
113 722     722 1 1780 my $self = shift;
114              
115 722         1635 my $send_to = $self->{+SEND_TO};
116              
117 722 100       2872 confess "Attempt to close AsyncSubtest when original parent hub (a non async-subtest?) has ended"
118             if $send_to->ended;
119              
120             return Test2::API::Context->new(
121 721         9175 trace => $self->{+TRACE},
122             hub => $send_to,
123             );
124             }
125              
126             sub _gen_event {
127 113     113   767 my $self = shift;
128 113         1951 my ($type, $id, $hub) = @_;
129              
130 113         1525 my $class = "Test2::AsyncSubtest::Event::$type";
131              
132             return $class->new(
133             id => $id,
134             trace => Test2::Util::Trace->new(
135             frame => [caller(1)],
136             buffered => $hub->buffered,
137             nested => $hub->nested,
138             cid => $self->{+CID},
139 113         56769 uuid => $self->{+UUID},
140             hid => $hub->hid,
141             huuid => $hub->uuid,
142             ),
143             );
144             }
145              
146             sub cleave {
147 343     343 1 924 my $self = shift;
148 343         1160 my $id = $self->{+ID}++;
149 343         1372 $self->{+HUB}->ast_ids->{$id} = 0;
150 343         4081 return $id;
151             }
152              
153             sub attach {
154 60     60 1 5359 my $self = shift;
155 60         2791 my ($id) = @_;
156              
157 60 50       33579 croak "An ID is required" unless $id;
158              
159             croak "ID $id is not valid"
160 60 50       4923 unless defined $self->{+HUB}->ast_ids->{$id};
161              
162             croak "ID $id is already attached"
163 60 50       3981 if $self->{+HUB}->ast_ids->{$id};
164              
165             croak "You must attach INSIDE the child process/thread"
166 60 50       53912 if $self->{+HUB}->is_local;
167              
168 60         6956 $self->{+_ATTACHED} = [ $$, get_tid, $id ];
169 60         1902 $self->{+HUB}->send($self->_gen_event('Attach', $id, $self->{+HUB}));
170             }
171              
172             sub detach {
173 53     53 1 542 my $self = shift;
174              
175 53 50 33     807 if ($self->{+PID} == $$ && $self->{+TID} == get_tid) {
176 0         0 cluck "You must detach INSIDE the child process/thread ($$, " . get_tid . " instead of $self->{+PID}, $self->{+TID})";
177 0         0 return;
178             }
179              
180 53 50       647 my $att = $self->{+_ATTACHED}
181             or croak "Not attached";
182              
183 53 50 33     1648 croak "Attempt to detach from wrong child"
184             unless $att->[0] == $$ && $att->[1] == get_tid;
185              
186 53         425 my $id = $att->[2];
187              
188 53         666 $self->{+HUB}->send($self->_gen_event('Detach', $id, $self->{+HUB}));
189              
190 53         4273 delete $self->{+_ATTACHED};
191             }
192              
193 2712     2712 1 20296 sub ready { return !shift->pending }
194             sub pending {
195 2714     2714 1 5062 my $self = shift;
196 2714         4878 my $hub = $self->{+HUB};
197 2714 100       9948 return -1 unless $hub->is_local;
198              
199 2601         28995 $hub->cull;
200              
201 2601         506086 return $self->{+_IN_USE} + keys %{$self->{+HUB}->ast_ids};
  2601         10797  
202             }
203              
204             sub run {
205 59     59 1 1451 my $self = shift;
206 59         375 my ($code, @args) = @_;
207              
208 59 50 33     2040 croak "AsyncSubtest->run() takes a codeblock as the first argument"
209             unless $code && ref($code) eq 'CODE';
210              
211 59         387 $self->start;
212              
213 59         1421 my ($ok, $err, $finished);
214             T2_SUBTEST_WRAPPER: {
215 59         272 $ok = eval { $code->(@args); 1 };
  59         159  
  59         543  
  39         5606  
216 40         169 $err = $@;
217              
218             # They might have done 'BEGIN { skip_all => "whatever" }'
219 40 50 66     233 if (!$ok && $err =~ m/Label not found for "last T2_SUBTEST_WRAPPER"/) {
220 0         0 $ok = undef;
221 0         0 $err = undef;
222             }
223             else {
224 40         128 $finished = 1;
225             }
226             }
227              
228 56         18008 $self->stop;
229              
230 56         1006 my $hub = $self->{+HUB};
231              
232 56 100       370 if (!$finished) {
233 16 50       528 if(my $bailed = $hub->bailed_out) {
234 0         0 my $ctx = $self->context;
235 0         0 $ctx->bail($bailed->reason);
236 0         0 return;
237             }
238 16         365 my $code = $hub->exit_code;
239 16         92 $ok = !$code;
240 16 50       76 $err = "Subtest ended with exit code $code" if $code;
241             }
242              
243 56 100       290 unless ($ok) {
244             my $e = Test2::Event::Exception->new(
245             error => $err,
246             trace => Test2::Util::Trace->new(
247             frame => [caller(0)],
248             buffered => $hub->buffered,
249             nested => $hub->nested,
250             cid => $self->{+CID},
251 1         19 uuid => $self->{+UUID},
252             hid => $hub->hid,
253             huuid => $hub->uuid,
254             ),
255             );
256 1         108 $hub->send($e);
257             }
258              
259 56         401 return $hub->is_passing;
260             }
261              
262             sub start {
263 493     493 1 1221 my $self = shift;
264              
265             croak "Subtest is already complete"
266 493 50       3756 if $self->{+FINISHED};
267              
268 493 100       4228 $self->{+START_STAMP} = Time::HiRes::time() unless defined $self->{+START_STAMP};
269              
270 493         1099 $self->{+ACTIVE}++;
271              
272 493         1267 push @STACK => $self;
273 493         1083 my $hub = $self->{+HUB};
274 493         2484 my $stack = Test2::API::test2_stack();
275 493         6666 $stack->push($hub);
276              
277 493         3711 return $hub->is_passing;
278             }
279              
280             sub stop {
281 466     466 1 1350 my $self = shift;
282              
283             croak "Subtest is not active"
284 466 50       1597 unless $self->{+ACTIVE}--;
285              
286 466 50 33     3382 croak "AsyncSubtest stack mismatch"
287             unless @STACK && $self == $STACK[-1];
288              
289 466         2123 $self->{+STOP_STAMP} = Time::HiRes::time();
290              
291 466         1067 pop @STACK;
292              
293 466         908 my $hub = $self->{+HUB};
294 466         1890 my $stack = Test2::API::test2_stack();
295 466         4671 $stack->pop($hub);
296 466         6637 return $hub->is_passing;
297             }
298              
299             sub finish {
300 722     722 1 23586 my $self = shift;
301 722         2077 my %params = @_;
302              
303 722         4202 my $hub = $self->hub;
304              
305             croak "Subtest is already finished"
306 722 50       4862 if $self->{+FINISHED}++;
307              
308 722 50       3960 croak "Subtest can only be finished in the process/thread that created it"
309             unless $hub->is_local;
310              
311             croak "Subtest is still active"
312 722 50       8477 if $self->{+ACTIVE};
313              
314 722         3059 $self->wait;
315 722 100       11558 $self->{+STOP_STAMP} = Time::HiRes::time() unless defined $self->{+STOP_STAMP};
316 722         1696 my $stop_stamp = $self->{+STOP_STAMP};
317              
318 722         1395 my $todo = $params{todo};
319 722         1548 my $skip = $params{skip};
320 722         1254 my $empty = !@{$self->{+EVENTS}};
  722         2004  
321 722         4147 my $no_asserts = !$hub->count;
322 722         3572 my $collapse = $params{collapse};
323 722   100     6518 my $no_plan = $params{no_plan} || ($collapse && $no_asserts) || $skip;
324              
325             my $trace = Test2::Util::Trace->new(
326             frame => $self->{+TRACE}->{frame},
327             buffered => $hub->buffered,
328             nested => $hub->nested,
329             cid => $self->{+CID},
330 722         4283 uuid => $self->{+UUID},
331             hid => $hub->hid,
332             huuid => $hub->uuid,
333             );
334              
335 722 50 33     38868 $hub->finalize($trace, !$no_plan)
336             unless $hub->no_ending || $hub->ended;
337              
338 722 50       106645 if ($hub->ipc) {
339 722         4367 $hub->ipc->drop_hub($hub->hid);
340 722         286594 $hub->set_ipc(undef);
341             }
342              
343 722 50       5060 return $hub->is_passing if $params{silent};
344              
345 722         3404 my $ctx = $self->context;
346              
347 721         30987 my $pass = 1;
348 721 100       2060 if ($skip) {
349 75         282 $ctx->skip($self->{+NAME}, $skip);
350             }
351             else {
352 646 100 100     2041 if ($collapse && $empty) {
353 3         30 $ctx->ok($hub->is_passing, $self->{+NAME});
354 3         811 return $hub->is_passing;
355             }
356              
357 643 100 100     1977 if ($collapse && $no_asserts) {
358 2         5 push @{$self->{+EVENTS}} => Test2::Event::Plan->new(trace => $trace, max => 0, directive => 'SKIP', reason => "No assertions");
  2         14  
359             }
360              
361             my $e = $ctx->build_event(
362             'Subtest',
363             pass => $hub->is_passing,
364             subtest_id => $hub->id,
365             subtest_uuid => $hub->uuid,
366             name => $self->{+NAME},
367             buffered => 1,
368             subevents => $self->{+EVENTS},
369             start_stamp => $self->{+START_STAMP},
370 643 50       2550 stop_stamp => $self->{+STOP_STAMP},
371             $todo ? (
372             todo => $todo,
373             effective_pass => 1,
374             ) : (),
375             );
376              
377 643         119600 $ctx->hub->send($e);
378              
379 643 100       615527 unless ($e->effective_pass) {
380 5         40 $ctx->failure_diag($e);
381              
382             $ctx->diag("Bad subtest plan, expected " . $hub->plan . " but ran " . $hub->count)
383 5 50 66     831 if $hub->plan && !$hub->check_plan && !grep {$_->causes_fail} @{$self->{+EVENTS}};
  0   33     0  
  0         0  
384             }
385              
386 643         5227 $pass = $e->pass;
387             }
388              
389 718         14519 $_->{+_IN_USE}-- for reverse @{$self->{+STACK}};
  718         2943  
390              
391 718         3581 return $pass;
392             }
393              
394             sub wait {
395 722     722 1 1441 my $self = shift;
396              
397 722         1569 my $hub = $self->{+HUB};
398 722         1642 my $children = $self->{+CHILDREN};
399              
400 722         2221 while (@$children) {
401 243         2895 $hub->cull;
402 243 50       35416 if (my $child = pop @$children) {
403 243 50       1760 if (blessed($child)) {
404 0         0 $child->join;
405             }
406             else {
407 243         537422622 waitpid($child, 0);
408             }
409             }
410             else {
411 0         0 Time::HiRes::sleep('0.01');
412             }
413             }
414              
415 722         6064 $hub->cull;
416              
417             cluck "Subtest '$self->{+NAME}': All children have completed, but we still appear to be pending"
418 722 50 33     107242 if $hub->is_local && keys %{$self->{+HUB}->ast_ids};
  722         10083  
419             }
420              
421             sub fork {
422 338 50   338 1 3932 croak "Forking is not supported" unless CAN_FORK;
423 338         5065 my $self = shift;
424 338         2004 my $id = $self->cleave;
425 338         535628 my $pid = CORE::fork();
426              
427 338 50       11443 unless (defined $pid) {
428 0         0 delete $self->{+HUB}->ast_ids->{$id};
429 0         0 croak "Failed to fork";
430             }
431              
432 338 100       7826 if($pid) {
433 280         3179 push @{$self->{+CHILDREN}} => $pid;
  280         12456  
434 280         12125 return $pid;
435             }
436              
437 58         7248 $self->attach($id);
438              
439 58         13914 return $self->_guard;
440             }
441              
442             sub run_fork {
443 180     180 1 1117 my $self = shift;
444 180         538 my ($code, @args) = @_;
445              
446 180         717 my $f = $self->fork;
447 180 100       8989 return $f unless blessed($f);
448              
449 22         916 $self->run($code, @args);
450              
451 21         496 $self->detach();
452 21         244 $f->dismiss();
453 21         158 exit 0;
454             }
455              
456             sub run_thread {
457 0 0   0 1 0 croak "Threading is not supported"
458             unless CAN_REALLY_THREAD;
459              
460 0         0 my $self = shift;
461 0         0 my ($code, @args) = @_;
462              
463 0         0 my $id = $self->cleave;
464             my $thr = threads->create(sub {
465 0     0   0 $self->attach($id);
466              
467 0         0 $self->run($code, @args);
468              
469 0         0 $self->detach(get_tid);
470 0         0 return 0;
471 0         0 });
472              
473 0         0 push @{$self->{+CHILDREN}} => $thr;
  0         0  
474              
475 0         0 return $thr;
476             }
477              
478             sub _guard {
479 58     58   849 my $self = shift;
480              
481 58         911 my ($pid, $tid) = ($$, get_tid);
482              
483             return Test2::Util::Guard->new(sub {
484 1 50 33 1   14 return unless $$ == $pid && get_tid == $tid;
485              
486 0         0 my $error = "Scope Leak";
487 0 0       0 if (my $ex = $@) {
488 0         0 chomp($ex);
489 0         0 $error .= " ($ex)";
490             }
491              
492 0         0 cluck $error;
493              
494 0         0 my $e = $self->context->build_event(
495             'Exception',
496             error => "$error\n",
497             );
498 0         0 $self->{+HUB}->send($e);
499 0         0 $self->detach();
500 0         0 exit 255;
501 58         4480 });
502             }
503              
504             sub DESTROY {
505 752     752   19350 my $self = shift;
506 752 100       2701 return unless $self->{+NAME};
507              
508 751 50       2391 if (my $att = $self->{+_ATTACHED}) {
509 0 0       0 return unless $self->{+HUB};
510 0         0 eval { $self->detach() };
  0         0  
511             }
512              
513 751 100       9155 return if $self->{+FINISHED};
514 49 50       1458 return unless $self->{+PID} == $$;
515 0 0         return unless $self->{+TID} == get_tid;
516              
517 0           local $@;
518 0           eval { $_->{+_IN_USE}-- for reverse @{$self->{+STACK}} };
  0            
  0            
519              
520 0           warn "Subtest $self->{+NAME} did not finish!";
521 0           exit 255;
522             }
523              
524             1;
525              
526             __END__