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   10727 use strict;
  76         252  
  76         2358  
3 76     76   398 use warnings;
  76         150  
  76         2783  
4              
5 76     76   28580 use Test2::IPC;
  76         69113  
  76         553  
6              
7             our $VERSION = '0.000155';
8              
9             our @CARP_NOT = qw/Test2::Util::HashBase/;
10              
11 76     76   7397 use Carp qw/croak cluck confess/;
  76         219  
  76         4569  
12 76     76   586 use Test2::Util qw/get_tid CAN_THREAD CAN_FORK/;
  76         212  
  76         4569  
13 76     76   463 use Scalar::Util qw/blessed weaken/;
  76         173  
  76         3865  
14 76     76   454 use List::Util qw/first/;
  76         220  
  76         4555  
15              
16 76     76   546 use Test2::API();
  76         181  
  76         1622  
17 76     76   437 use Test2::API::Context();
  76         169  
  76         1547  
18 76     76   434 use Test2::Util::Trace();
  76         176  
  76         1318  
19 76     76   31902 use Test2::Util::Guard();
  76         218  
  76         1439  
20 76     76   512 use Time::HiRes();
  76         158  
  76         1061  
21              
22 76     76   31268 use Test2::AsyncSubtest::Hub();
  76         225  
  76         1648  
23 76     76   34287 use Test2::AsyncSubtest::Event::Attach();
  76         202  
  76         1646  
24 76     76   31553 use Test2::AsyncSubtest::Event::Detach();
  76         242  
  76         2798  
25              
26 76         514 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   576 };
  76         173  
39              
40             sub CAN_REALLY_THREAD {
41 77     77 0 281 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 837     837 0 68272 my $self = shift;
55              
56             croak "'name' is a required attribute"
57 837 100       3278 unless $self->{+NAME};
58              
59 836   33     10288 my $to = $self->{+SEND_TO} ||= Test2::API::test2_stack()->top;
60              
61 836         18445 $self->{+STACK} = [@STACK];
62 836         2993 $_->{+_IN_USE}++ for reverse @STACK;
63              
64 836         2398 $self->{+TID} = get_tid;
65 836         2599 $self->{+PID} = $$;
66 836         6349 $self->{+CID} = 'AsyncSubtest-' . $CID++;
67 836         3262 $self->{+ID} = 1;
68 836         3012 $self->{+FINISHED} = 0;
69 836         2543 $self->{+ACTIVE} = 0;
70 836         2424 $self->{+_IN_USE} = 0;
71 836         19022 $self->{+CHILDREN} = [];
72 836 100       3004 $self->{+UUID} = ${$UUID_VIA}->() if defined $$UUID_VIA;
  1         4  
73              
74 836 50       2299 unless($self->{+HUB}) {
75 836         6259 my $ipc = Test2::API::test2_ipc();
76 836         17814 my $formatter = Test2::API::test2_stack->top->format;
77 836   100     21008 my $args = delete $self->{hub_init_args} || {};
78 836         5590 my $hub = Test2::AsyncSubtest::Hub->new(
79             %$args,
80             ipc => $ipc,
81             nested => $to->nested + 1,
82             buffered => 1,
83             formatter => $formatter,
84             );
85 836         17144 weaken($hub->{ast} = $self);
86 836         3679 $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 836   100     15380 uuid => $self->{+UUID},
      33        
95             hid => $to->hid,
96             huuid => $to->uuid,
97             );
98              
99 836         41975 my $hub = $self->{+HUB};
100 836 50       2973 $hub->set_ast_ids({}) unless $hub->ast_ids;
101 836         9021 $hub->listen($self->_listener);
102             }
103              
104             sub _listener {
105 836     836   1707 my $self = shift;
106              
107 836   50     5718 my $events = $self->{+EVENTS} ||= [];
108              
109 836     3831   14709 sub { push @$events => $_[1] };
  3831         934509  
110             }
111              
112             sub context {
113 713     713 1 2313 my $self = shift;
114              
115 713         1876 my $send_to = $self->{+SEND_TO};
116              
117 713 100       3038 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 712         10981 trace => $self->{+TRACE},
122             hub => $send_to,
123             );
124             }
125              
126             sub _gen_event {
127 113     113   1536 my $self = shift;
128 113         13682 my ($type, $id, $hub) = @_;
129              
130 113         2158 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         43076 uuid => $self->{+UUID},
140             hid => $hub->hid,
141             huuid => $hub->uuid,
142             ),
143             );
144             }
145              
146             sub cleave {
147 343     343 1 716 my $self = shift;
148 343         1216 my $id = $self->{+ID}++;
149 343         1339 $self->{+HUB}->ast_ids->{$id} = 0;
150 343         3892 return $id;
151             }
152              
153             sub attach {
154 60     60 1 6150 my $self = shift;
155 60         2847 my ($id) = @_;
156              
157 60 50       3744 croak "An ID is required" unless $id;
158              
159             croak "ID $id is not valid"
160 60 50       7118 unless defined $self->{+HUB}->ast_ids->{$id};
161              
162             croak "ID $id is already attached"
163 60 50       4926 if $self->{+HUB}->ast_ids->{$id};
164              
165             croak "You must attach INSIDE the child process/thread"
166 60 50       7075 if $self->{+HUB}->is_local;
167              
168 60         9102 $self->{+_ATTACHED} = [ $$, get_tid, $id ];
169 60         2842 $self->{+HUB}->send($self->_gen_event('Attach', $id, $self->{+HUB}));
170             }
171              
172             sub detach {
173 53     53 1 475 my $self = shift;
174              
175 53 50 33     954 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       669 my $att = $self->{+_ATTACHED}
181             or croak "Not attached";
182              
183 53 50 33     1606 croak "Attempt to detach from wrong child"
184             unless $att->[0] == $$ && $att->[1] == get_tid;
185              
186 53         526 my $id = $att->[2];
187              
188 53         701 $self->{+HUB}->send($self->_gen_event('Detach', $id, $self->{+HUB}));
189              
190 53         4509 delete $self->{+_ATTACHED};
191             }
192              
193 2792     2792 1 22984 sub ready { return !shift->pending }
194             sub pending {
195 2794     2794 1 5605 my $self = shift;
196 2794         6300 my $hub = $self->{+HUB};
197 2794 100       12251 return -1 unless $hub->is_local;
198              
199 2681         32462 $hub->cull;
200              
201 2681         577317 return $self->{+_IN_USE} + keys %{$self->{+HUB}->ast_ids};
  2681         12199  
202             }
203              
204             sub run {
205 59     59 1 1500 my $self = shift;
206 59         523 my ($code, @args) = @_;
207              
208 59 50 33     3101 croak "AsyncSubtest->run() takes a codeblock as the first argument"
209             unless $code && ref($code) eq 'CODE';
210              
211 59         589 $self->start;
212              
213 59         1484 my ($ok, $err, $finished);
214             T2_SUBTEST_WRAPPER: {
215 59         337 $ok = eval { $code->(@args); 1 };
  59         245  
  59         467  
  39         5068  
216 40         223 $err = $@;
217              
218             # They might have done 'BEGIN { skip_all => "whatever" }'
219 40 50 66     225 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         225 $finished = 1;
225             }
226             }
227              
228 56         19572 $self->stop;
229              
230 56         984 my $hub = $self->{+HUB};
231              
232 56 100       279 if (!$finished) {
233 16 50       634 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         404 my $code = $hub->exit_code;
239 16         217 $ok = !$code;
240 16 50       140 $err = "Subtest ended with exit code $code" if $code;
241             }
242              
243 56 100       224 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         27 uuid => $self->{+UUID},
252             hid => $hub->hid,
253             huuid => $hub->uuid,
254             ),
255             );
256 1         148 $hub->send($e);
257             }
258              
259 56         389 return $hub->is_passing;
260             }
261              
262             sub start {
263 484     484 1 1571 my $self = shift;
264              
265             croak "Subtest is already complete"
266 484 50       1831 if $self->{+FINISHED};
267              
268 484 100       4582 $self->{+START_STAMP} = Time::HiRes::time() unless defined $self->{+START_STAMP};
269              
270 484         1081 $self->{+ACTIVE}++;
271              
272 484         1269 push @STACK => $self;
273 484         986 my $hub = $self->{+HUB};
274 484         2686 my $stack = Test2::API::test2_stack();
275 484         7038 $stack->push($hub);
276              
277 484         4525 return $hub->is_passing;
278             }
279              
280             sub stop {
281 457     457 1 1402 my $self = shift;
282              
283             croak "Subtest is not active"
284 457 50       1558 unless $self->{+ACTIVE}--;
285              
286 457 50 33     3593 croak "AsyncSubtest stack mismatch"
287             unless @STACK && $self == $STACK[-1];
288              
289 457         2613 $self->{+STOP_STAMP} = Time::HiRes::time();
290              
291 457         1048 pop @STACK;
292              
293 457         1066 my $hub = $self->{+HUB};
294 457         1754 my $stack = Test2::API::test2_stack();
295 457         4909 $stack->pop($hub);
296 457         6963 return $hub->is_passing;
297             }
298              
299             sub finish {
300 713     713 1 23750 my $self = shift;
301 713         2487 my %params = @_;
302              
303 713         4759 my $hub = $self->hub;
304              
305             croak "Subtest is already finished"
306 713 50       4921 if $self->{+FINISHED}++;
307              
308 713 50       4596 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 713 50       8817 if $self->{+ACTIVE};
313              
314 713         2973 $self->wait;
315 713 100       12276 $self->{+STOP_STAMP} = Time::HiRes::time() unless defined $self->{+STOP_STAMP};
316 713         2524 my $stop_stamp = $self->{+STOP_STAMP};
317              
318 713         1777 my $todo = $params{todo};
319 713         1812 my $skip = $params{skip};
320 713         1631 my $empty = !@{$self->{+EVENTS}};
  713         2671  
321 713         6923 my $no_asserts = !$hub->count;
322 713         4046 my $collapse = $params{collapse};
323 713   100     8642 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 713         5500 uuid => $self->{+UUID},
331             hid => $hub->hid,
332             huuid => $hub->uuid,
333             );
334              
335 713 50 33     48933 $hub->finalize($trace, !$no_plan)
336             unless $hub->no_ending || $hub->ended;
337              
338 713 50       121419 if ($hub->ipc) {
339 713         4980 $hub->ipc->drop_hub($hub->hid);
340 713         289414 $hub->set_ipc(undef);
341             }
342              
343 713 50       5427 return $hub->is_passing if $params{silent};
344              
345 713         3806 my $ctx = $self->context;
346              
347 712         36401 my $pass = 1;
348 712 100       3349 if ($skip) {
349 75         348 $ctx->skip($self->{+NAME}, $skip);
350             }
351             else {
352 637 100 100     2582 if ($collapse && $empty) {
353 3         18 $ctx->ok($hub->is_passing, $self->{+NAME});
354 3         629 return $hub->is_passing;
355             }
356              
357 634 100 100     2079 if ($collapse && $no_asserts) {
358 2         6 push @{$self->{+EVENTS}} => Test2::Event::Plan->new(trace => $trace, max => 0, directive => 'SKIP', reason => "No assertions");
  2         20  
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 634 50       2866 stop_stamp => $self->{+STOP_STAMP},
371             $todo ? (
372             todo => $todo,
373             effective_pass => 1,
374             ) : (),
375             );
376              
377 634         138204 $ctx->hub->send($e);
378              
379 634 100       555894 unless ($e->effective_pass) {
380 5         60 $ctx->failure_diag($e);
381              
382             $ctx->diag("Bad subtest plan, expected " . $hub->plan . " but ran " . $hub->count)
383 5 50 66     880 if $hub->plan && !$hub->check_plan && !grep {$_->causes_fail} @{$self->{+EVENTS}};
  0   33     0  
  0         0  
384             }
385              
386 634         6763 $pass = $e->pass;
387             }
388              
389 709         14426 $_->{+_IN_USE}-- for reverse @{$self->{+STACK}};
  709         3386  
390              
391 709         4273 return $pass;
392             }
393              
394             sub wait {
395 713     713 1 1746 my $self = shift;
396              
397 713         1700 my $hub = $self->{+HUB};
398 713         1773 my $children = $self->{+CHILDREN};
399              
400 713         2622 while (@$children) {
401 243         2734 $hub->cull;
402 243 50       46447 if (my $child = pop @$children) {
403 243 50       2608 if (blessed($child)) {
404 0         0 $child->join;
405             }
406             else {
407 243         538315840 waitpid($child, 0);
408             }
409             }
410             else {
411 0         0 Time::HiRes::sleep('0.01');
412             }
413             }
414              
415 713         9386 $hub->cull;
416              
417             cluck "Subtest '$self->{+NAME}': All children have completed, but we still appear to be pending"
418 713 50 33     122336 if $hub->is_local && keys %{$self->{+HUB}->ast_ids};
  713         12378  
419             }
420              
421             sub fork {
422 338 50   338 1 4586 croak "Forking is not supported" unless CAN_FORK;
423 338         5175 my $self = shift;
424 338         1418 my $id = $self->cleave;
425 338         467399 my $pid = CORE::fork();
426              
427 338 50       13693 unless (defined $pid) {
428 0         0 delete $self->{+HUB}->ast_ids->{$id};
429 0         0 croak "Failed to fork";
430             }
431              
432 338 100       10565 if($pid) {
433 280         3484 push @{$self->{+CHILDREN}} => $pid;
  280         12347  
434 280         11756 return $pid;
435             }
436              
437 58         10783 $self->attach($id);
438              
439 58         15680 return $self->_guard;
440             }
441              
442             sub run_fork {
443 180     180 1 1086 my $self = shift;
444 180         699 my ($code, @args) = @_;
445              
446 180         773 my $f = $self->fork;
447 180 100       7904 return $f unless blessed($f);
448              
449 22         744 $self->run($code, @args);
450              
451 21         543 $self->detach();
452 21         357 $f->dismiss();
453 21         193 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   1172 my $self = shift;
480              
481 58         1135 my ($pid, $tid) = ($$, get_tid);
482              
483             return Test2::Util::Guard->new(sub {
484 1 50 33 1   27 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         36833 });
502             }
503              
504             sub DESTROY {
505 743     743   19952 my $self = shift;
506 743 100       3215 return unless $self->{+NAME};
507              
508 742 50       2655 if (my $att = $self->{+_ATTACHED}) {
509 0 0       0 return unless $self->{+HUB};
510 0         0 eval { $self->detach() };
  0         0  
511             }
512              
513 742 100       10587 return if $self->{+FINISHED};
514 49 50       2012 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__