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   8860 use strict;
  76         153  
  76         2109  
3 76     76   339 use warnings;
  76         191  
  76         1932  
4              
5 76     76   25967 use Test2::IPC;
  76         62875  
  76         557  
6              
7             our $VERSION = '0.000153';
8              
9             our @CARP_NOT = qw/Test2::Util::HashBase/;
10              
11 76     76   6551 use Carp qw/croak cluck confess/;
  76         203  
  76         3904  
12 76     76   543 use Test2::Util qw/get_tid CAN_THREAD CAN_FORK/;
  76         157  
  76         4066  
13 76     76   450 use Scalar::Util qw/blessed weaken/;
  76         161  
  76         3438  
14 76     76   504 use List::Util qw/first/;
  76         372  
  76         3873  
15              
16 76     76   540 use Test2::API();
  76         176  
  76         1650  
17 76     76   445 use Test2::API::Context();
  76         125  
  76         1279  
18 76     76   416 use Test2::Util::Trace();
  76         201  
  76         1510  
19 76     76   28805 use Test2::Util::Guard();
  76         268  
  76         1335  
20 76     76   464 use Time::HiRes();
  76         171  
  76         1039  
21              
22 76     76   28664 use Test2::AsyncSubtest::Hub();
  76         223  
  76         1375  
23 76     76   29355 use Test2::AsyncSubtest::Event::Attach();
  76         228  
  76         1443  
24 76     76   28227 use Test2::AsyncSubtest::Event::Detach();
  76         232  
  76         2477  
25              
26 76         336 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   484 };
  76         180  
39              
40             sub CAN_REALLY_THREAD {
41 77     77 0 266 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 57489 my $self = shift;
55              
56             croak "'name' is a required attribute"
57 837 100       2871 unless $self->{+NAME};
58              
59 836   33     8827 my $to = $self->{+SEND_TO} ||= Test2::API::test2_stack()->top;
60              
61 836         15976 $self->{+STACK} = [@STACK];
62 836         2806 $_->{+_IN_USE}++ for reverse @STACK;
63              
64 836         2133 $self->{+TID} = get_tid;
65 836         2514 $self->{+PID} = $$;
66 836         4537 $self->{+CID} = 'AsyncSubtest-' . $CID++;
67 836         19079 $self->{+ID} = 1;
68 836         1962 $self->{+FINISHED} = 0;
69 836         2033 $self->{+ACTIVE} = 0;
70 836         1823 $self->{+_IN_USE} = 0;
71 836         3087 $self->{+CHILDREN} = [];
72 836 100       2942 $self->{+UUID} = ${$UUID_VIA}->() if defined $$UUID_VIA;
  1         3  
73              
74 836 50       2345 unless($self->{+HUB}) {
75 836         5324 my $ipc = Test2::API::test2_ipc();
76 836         15490 my $formatter = Test2::API::test2_stack->top->format;
77 836   100     20610 my $args = delete $self->{hub_init_args} || {};
78 836         5137 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         15922 weaken($hub->{ast} = $self);
86 836         3241 $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     13111 uuid => $self->{+UUID},
      33        
95             hid => $to->hid,
96             huuid => $to->uuid,
97             );
98              
99 836         37647 my $hub = $self->{+HUB};
100 836 50       2831 $hub->set_ast_ids({}) unless $hub->ast_ids;
101 836         8727 $hub->listen($self->_listener);
102             }
103              
104             sub _listener {
105 836     836   1611 my $self = shift;
106              
107 836   50     5103 my $events = $self->{+EVENTS} ||= [];
108              
109 836     3846   9704 sub { push @$events => $_[1] };
  3846         898186  
110             }
111              
112             sub context {
113 720     720 1 1740 my $self = shift;
114              
115 720         1753 my $send_to = $self->{+SEND_TO};
116              
117 720 100       3138 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 719         10344 trace => $self->{+TRACE},
122             hub => $send_to,
123             );
124             }
125              
126             sub _gen_event {
127 113     113   893 my $self = shift;
128 113         1819 my ($type, $id, $hub) = @_;
129              
130 113         1094 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         38899 uuid => $self->{+UUID},
140             hid => $hub->hid,
141             huuid => $hub->uuid,
142             ),
143             );
144             }
145              
146             sub cleave {
147 343     343 1 688 my $self = shift;
148 343         957 my $id = $self->{+ID}++;
149 343         1113 $self->{+HUB}->ast_ids->{$id} = 0;
150 343         3361 return $id;
151             }
152              
153             sub attach {
154 60     60 1 4789 my $self = shift;
155 60         2298 my ($id) = @_;
156              
157 60 50       2790 croak "An ID is required" unless $id;
158              
159             croak "ID $id is not valid"
160 60 50       4678 unless defined $self->{+HUB}->ast_ids->{$id};
161              
162             croak "ID $id is already attached"
163 60 50       3534 if $self->{+HUB}->ast_ids->{$id};
164              
165             croak "You must attach INSIDE the child process/thread"
166 60 50       6125 if $self->{+HUB}->is_local;
167              
168 60         6127 $self->{+_ATTACHED} = [ $$, get_tid, $id ];
169 60         2020 $self->{+HUB}->send($self->_gen_event('Attach', $id, $self->{+HUB}));
170             }
171              
172             sub detach {
173 53     53 1 402 my $self = shift;
174              
175 53 50 33     800 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       706 my $att = $self->{+_ATTACHED}
181             or croak "Not attached";
182              
183 53 50 33     1449 croak "Attempt to detach from wrong child"
184             unless $att->[0] == $$ && $att->[1] == get_tid;
185              
186 53         353 my $id = $att->[2];
187              
188 53         794 $self->{+HUB}->send($self->_gen_event('Detach', $id, $self->{+HUB}));
189              
190 53         4109 delete $self->{+_ATTACHED};
191             }
192              
193 3036     3036 1 23147 sub ready { return !shift->pending }
194             sub pending {
195 3038     3038 1 5257 my $self = shift;
196 3038         5711 my $hub = $self->{+HUB};
197 3038 100       11590 return -1 unless $hub->is_local;
198              
199 2996         34272 $hub->cull;
200              
201 2996         616467 return $self->{+_IN_USE} + keys %{$self->{+HUB}->ast_ids};
  2996         12564  
202             }
203              
204             sub run {
205 59     59 1 1186 my $self = shift;
206 59         215 my ($code, @args) = @_;
207              
208 59 50 33     830 croak "AsyncSubtest->run() takes a codeblock as the first argument"
209             unless $code && ref($code) eq 'CODE';
210              
211 59         348 $self->start;
212              
213 59         1090 my ($ok, $err, $finished);
214             T2_SUBTEST_WRAPPER: {
215 59         226 $ok = eval { $code->(@args); 1 };
  59         121  
  59         367  
  39         4893  
216 40         168 $err = $@;
217              
218             # They might have done 'BEGIN { skip_all => "whatever" }'
219 40 50 66     194 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         91 $finished = 1;
225             }
226             }
227              
228 56         13767 $self->stop;
229              
230 56         825 my $hub = $self->{+HUB};
231              
232 56 100       432 if (!$finished) {
233 16 50       504 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         357 my $code = $hub->exit_code;
239 16         84 $ok = !$code;
240 16 50       83 $err = "Subtest ended with exit code $code" if $code;
241             }
242              
243 56 100       166 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         16 uuid => $self->{+UUID},
252             hid => $hub->hid,
253             huuid => $hub->uuid,
254             ),
255             );
256 1         91 $hub->send($e);
257             }
258              
259 56         279 return $hub->is_passing;
260             }
261              
262             sub start {
263 484     484 1 1182 my $self = shift;
264              
265             croak "Subtest is already complete"
266 484 50       1468 if $self->{+FINISHED};
267              
268 484 100       4262 $self->{+START_STAMP} = Time::HiRes::time() unless defined $self->{+START_STAMP};
269              
270 484         1426 $self->{+ACTIVE}++;
271              
272 484         1247 push @STACK => $self;
273 484         903 my $hub = $self->{+HUB};
274 484         2165 my $stack = Test2::API::test2_stack();
275 484         6364 $stack->push($hub);
276              
277 484         4137 return $hub->is_passing;
278             }
279              
280             sub stop {
281 457     457 1 970 my $self = shift;
282              
283             croak "Subtest is not active"
284 457 50       1545 unless $self->{+ACTIVE}--;
285              
286 457 50 33     3441 croak "AsyncSubtest stack mismatch"
287             unless @STACK && $self == $STACK[-1];
288              
289 457         2215 $self->{+STOP_STAMP} = Time::HiRes::time();
290              
291 457         1076 pop @STACK;
292              
293 457         1047 my $hub = $self->{+HUB};
294 457         1674 my $stack = Test2::API::test2_stack();
295 457         4983 $stack->pop($hub);
296 457         6456 return $hub->is_passing;
297             }
298              
299             sub finish {
300 720     720 1 19359 my $self = shift;
301 720         2187 my %params = @_;
302              
303 720         4247 my $hub = $self->hub;
304              
305             croak "Subtest is already finished"
306 720 50       5402 if $self->{+FINISHED}++;
307              
308 720 50       4084 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 720 50       7987 if $self->{+ACTIVE};
313              
314 720         2995 $self->wait;
315 720 100       10683 $self->{+STOP_STAMP} = Time::HiRes::time() unless defined $self->{+STOP_STAMP};
316 720         1801 my $stop_stamp = $self->{+STOP_STAMP};
317              
318 720         1728 my $todo = $params{todo};
319 720         1471 my $skip = $params{skip};
320 720         1252 my $empty = !@{$self->{+EVENTS}};
  720         1934  
321 720         5657 my $no_asserts = !$hub->count;
322 720         3527 my $collapse = $params{collapse};
323 720   100     6247 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 720         3857 uuid => $self->{+UUID},
331             hid => $hub->hid,
332             huuid => $hub->uuid,
333             );
334              
335 720 50 33     37487 $hub->finalize($trace, !$no_plan)
336             unless $hub->no_ending || $hub->ended;
337              
338 720 50       100650 if ($hub->ipc) {
339 720         4758 $hub->ipc->drop_hub($hub->hid);
340 720         237179 $hub->set_ipc(undef);
341             }
342              
343 720 50       4884 return $hub->is_passing if $params{silent};
344              
345 720         3412 my $ctx = $self->context;
346              
347 719         29537 my $pass = 1;
348 719 100       2119 if ($skip) {
349 75         367 $ctx->skip($self->{+NAME}, $skip);
350             }
351             else {
352 644 100 100     2421 if ($collapse && $empty) {
353 3         20 $ctx->ok($hub->is_passing, $self->{+NAME});
354 3         597 return $hub->is_passing;
355             }
356              
357 641 100 100     2003 if ($collapse && $no_asserts) {
358 2         6 push @{$self->{+EVENTS}} => Test2::Event::Plan->new(trace => $trace, max => 0, directive => 'SKIP', reason => "No assertions");
  2         18  
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 641 50       2743 stop_stamp => $self->{+STOP_STAMP},
371             $todo ? (
372             todo => $todo,
373             effective_pass => 1,
374             ) : (),
375             );
376              
377 641         118649 $ctx->hub->send($e);
378              
379 641 100       740289 unless ($e->effective_pass) {
380 5         35 $ctx->failure_diag($e);
381              
382             $ctx->diag("Bad subtest plan, expected " . $hub->plan . " but ran " . $hub->count)
383 5 50 66     784 if $hub->plan && !$hub->check_plan && !grep {$_->causes_fail} @{$self->{+EVENTS}};
  0   33     0  
  0         0  
384             }
385              
386 641         5657 $pass = $e->pass;
387             }
388              
389 716         16512 $_->{+_IN_USE}-- for reverse @{$self->{+STACK}};
  716         3200  
390              
391 716         4364 return $pass;
392             }
393              
394             sub wait {
395 720     720 1 1497 my $self = shift;
396              
397 720         1579 my $hub = $self->{+HUB};
398 720         2083 my $children = $self->{+CHILDREN};
399              
400 720         2403 while (@$children) {
401 246         2485 $hub->cull;
402 246 50       34729 if (my $child = pop @$children) {
403 246 50       2637 if (blessed($child)) {
404 0         0 $child->join;
405             }
406             else {
407 246         477185646 waitpid($child, 0);
408             }
409             }
410             else {
411 0         0 Time::HiRes::sleep('0.01');
412             }
413             }
414              
415 720         6098 $hub->cull;
416              
417             cluck "Subtest '$self->{+NAME}': All children have completed, but we still appear to be pending"
418 720 50 33     103948 if $hub->is_local && keys %{$self->{+HUB}->ast_ids};
  720         9927  
419             }
420              
421             sub fork {
422 338 50   338 1 3867 croak "Forking is not supported" unless CAN_FORK;
423 338         3990 my $self = shift;
424 338         1283 my $id = $self->cleave;
425 338         410851 my $pid = CORE::fork();
426              
427 338 50       10824 unless (defined $pid) {
428 0         0 delete $self->{+HUB}->ast_ids->{$id};
429 0         0 croak "Failed to fork";
430             }
431              
432 338 100       7263 if($pid) {
433 280         2738 push @{$self->{+CHILDREN}} => $pid;
  280         10178  
434 280         12114 return $pid;
435             }
436              
437 58         6904 $self->attach($id);
438              
439 58         28880 return $self->_guard;
440             }
441              
442             sub run_fork {
443 180     180 1 942 my $self = shift;
444 180         522 my ($code, @args) = @_;
445              
446 180         683 my $f = $self->fork;
447 180 100       7187 return $f unless blessed($f);
448              
449 22         502 $self->run($code, @args);
450              
451 21         586 $self->detach();
452 21         244 $f->dismiss();
453 21         239 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   572 my $self = shift;
480              
481 58         617 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         19912 });
502             }
503              
504             sub DESTROY {
505 742     742   15417 my $self = shift;
506 742 100       2775 return unless $self->{+NAME};
507              
508 741 50       2313 if (my $att = $self->{+_ATTACHED}) {
509 0 0       0 return unless $self->{+HUB};
510 0         0 eval { $self->detach() };
  0         0  
511             }
512              
513 741 100       8540 return if $self->{+FINISHED};
514 49 50       885 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__