File Coverage

blib/lib/Test2/AsyncSubtest.pm
Criterion Covered Total %
statement 223 279 79.9
branch 51 102 50.0
condition 26 55 47.2
subroutine 38 42 90.4
pod 14 17 82.3
total 352 495 71.1


line stmt bran cond sub pod time code
1             package Test2::AsyncSubtest;
2 29     29   2850893 use strict;
  29         58  
  29         659  
3 29     29   120 use warnings;
  29         57  
  29         575  
4              
5 29     29   4575 use Test2::IPC;
  29         12278  
  29         126  
6              
7             our $VERSION = '0.000019'; # TRIAL
8              
9             our @CARP_NOT = qw/Test2::Util::HashBase/;
10              
11 29     29   1929 use Carp qw/croak cluck/;
  29         73  
  29         1143  
12 29     29   144 use Test2::Util qw/get_tid CAN_THREAD CAN_FORK/;
  29         49  
  29         1100  
13 29     29   163 use Scalar::Util qw/blessed/;
  29         40  
  29         1700  
14 29     29   157 use List::Util qw/first/;
  29         39  
  29         1339  
15              
16 29     29   6648 use Scope::Guard();
  29         9126  
  29         486  
17 29     29   145 use Test2::API();
  29         129  
  29         315  
18 29     29   163 use Test2::API::Context();
  29         36  
  29         298  
19 29     29   92 use Test2::Util::Trace();
  29         55  
  29         264  
20 29     29   9130 use Time::HiRes();
  29         28987  
  29         615  
21              
22 29     29   6832 use Test2::AsyncSubtest::Hub();
  29         65  
  29         489  
23 29     29   6858 use Test2::AsyncSubtest::Event::Attach();
  29         58  
  29         436  
24 29     29   6710 use Test2::AsyncSubtest::Event::Detach();
  29         82  
  29         1085  
25              
26 29         299 use Test2::Util::HashBase qw{
27             name hub
28             trace send_to
29             events
30             finished
31             active
32             stack
33             id
34             children
35             _in_use
36             _attached pid tid
37 29     29   224 };
  29         93  
38              
39             sub CAN_REALLY_THREAD {
40 2     2 0 3195 return 0 unless CAN_THREAD;
41 0 0       0 return 0 unless eval { require threads; threads->VERSION('1.34'); 1 };
  0         0  
  0         0  
  0         0  
42 0         0 return 1;
43             }
44              
45             my @STACK;
46              
47 0 0   0 0 0 sub TOP { @STACK ? $STACK[-1] : undef }
48              
49             sub init {
50 216     216 0 139472 my $self = shift;
51              
52             croak "'name' is a required attribute"
53 216 100       856 unless $self->{+NAME};
54              
55 215   33     3090 $self->{+SEND_TO} ||= Test2::API::test2_stack()->top;
56              
57 215         103895 $self->{+STACK} = [@STACK];
58 215         604 $_->{+_IN_USE}++ for reverse @STACK;
59              
60 215         716 $self->{+TID} = get_tid;
61 215         609 $self->{+PID} = $$;
62 215         784 $self->{+ID} = 1;
63 215         445 $self->{+FINISHED} = 0;
64 215         780 $self->{+ACTIVE} = 0;
65 215         669 $self->{+_IN_USE} = 0;
66 215         857 $self->{+CHILDREN} = [];
67              
68 215 50       580 unless($self->{+HUB}) {
69 215         912 my $ipc = Test2::API::test2_ipc();
70 215         3802 my $formatter = Test2::API::test2_stack->top->format;
71 215   100     6605 my $args = delete $self->{hub_init_args} || {};
72 215         2571 my $hub = Test2::AsyncSubtest::Hub->new(
73             %$args,
74             ipc => $ipc,
75             nested => 1,
76             formatter => $formatter,
77             );
78 215         2941 $self->{+HUB} = $hub;
79             }
80              
81 215   33     4445 $self->{+TRACE} ||= Test2::Util::Trace->new(
82             frame => [caller(1)],
83             );
84              
85 215         5353 my $hub = $self->{+HUB};
86 215 50       588 $hub->set_ast_ids({}) unless $hub->ast_ids;
87 215         2510 $hub->listen($self->_listener);
88 215         4161 $hub->pre_filter($self->_pre_filter);
89             }
90              
91             sub _listener {
92 215     215   417 my $self = shift;
93              
94 215   50     1087 my $events = $self->{+EVENTS} ||= [];
95              
96 215     498   3129 sub { push @$events => $_[1] };
  498         34935  
97             }
98              
99             sub _pre_filter {
100 215     215   423 my $self = shift;
101              
102             sub {
103 134     134   33301 my ($hub, $e) = @_;
104 134 100       524 return $e if $hub->is_local;
105              
106 73         780 my $attached = $self->{+_ATTACHED};
107 73 50 33     1367 return $e if $attached && @$attached && $attached->[0] == $$ && $attached->[1] == get_tid;
      33        
      33        
108 0         0 $e->trace->throw("You must attach to an AsyncSubtest before you can send events to it from another process or thread");
109 0         0 return;
110 215         2297 };
111             }
112              
113             sub context {
114 167     167 1 408 my $self = shift;
115             return Test2::API::Context->new(
116             trace => $self->{+TRACE},
117 167         1694 hub => $self->{+SEND_TO},
118             );
119             }
120              
121             sub _gen_event {
122 47     47   182 my $self = shift;
123 47         379 my ($type, $id) = @_;
124              
125 47         248 my $class = "Test2::AsyncSubtest::Event::$type";
126              
127 47         1992 return $class->new(id => $id, trace => Test2::Util::Trace->new(frame => [caller(1)]));
128             }
129              
130             sub cleave {
131 185     185 1 353 my $self = shift;
132 185         427 my $id = $self->{+ID}++;
133 185         562 $self->{+HUB}->ast_ids->{$id} = 0;
134 185         1560 return $id;
135             }
136              
137             sub attach {
138 24     24 1 5939 my $self = shift;
139 24         267 my ($id) = @_;
140              
141 24 50       337 croak "An ID is required" unless $id;
142              
143             croak "ID $id is not valid"
144 24 50       993 unless defined $self->{+HUB}->ast_ids->{$id};
145              
146             croak "ID $id is already attached"
147 24 50       755 if $self->{+HUB}->ast_ids->{$id};
148              
149             croak "You must attach INSIDE the child process/thread"
150 24 50       1108 if $self->{+HUB}->is_local;
151              
152 24         1186 $self->{+_ATTACHED} = [ $$, get_tid, $id ];
153 24         382 $self->{+HUB}->send($self->_gen_event('Attach', $id));
154             }
155              
156             sub detach {
157 23     23 1 1235 my $self = shift;
158              
159 23 50 33     259 if ($self->{+PID} == $$ && $self->{+TID} == get_tid) {
160 0         0 cluck "You must detach INSIDE the child process/thread";
161 0         0 return;
162             }
163              
164 23 50       113 my $att = $self->{+_ATTACHED}
165             or croak "Not attached";
166              
167 23 50 33     12737 croak "Attempt to detach from wrong child"
168             unless $att->[0] == $$ && $att->[1] == get_tid;
169              
170 23         101 my $id = $att->[2];
171              
172 23         129 $self->{+HUB}->send($self->_gen_event('Detach', $id));
173              
174 23         8911 delete $self->{+_ATTACHED};
175             }
176              
177 0     0 1 0 sub ready { return !shift->pending }
178             sub pending {
179 2     2 1 648 my $self = shift;
180 2         8 my $hub = $self->{+HUB};
181 2 50       60 return -1 unless $hub->is_local;
182              
183 2         30 $hub->cull;
184              
185 2         471 return $self->{+_IN_USE} + keys %{$self->{+HUB}->ast_ids};
  2         36  
186             }
187              
188             sub run {
189 56     56 1 563627 my $self = shift;
190 56         181 my ($code, @args) = @_;
191              
192 56 50 33     522 croak "AsyncSubtest->run() takes a codeblock as the first argument"
193             unless $code && ref($code) eq 'CODE';
194              
195 56         364 $self->start;
196              
197 56         972 my ($ok, $err, $finished);
198             T2_SUBTEST_WRAPPER: {
199 56         99 $ok = eval { $code->(@args); 1 };
  56         150  
  56         393  
  36         6837  
200 37         125 $err = $@;
201              
202             # They might have done 'BEGIN { skip_all => "whatever" }'
203 37 50 66     267 if (!$ok && $err =~ m/Label not found for "last T2_SUBTEST_WRAPPER"/) {
204 0         0 $ok = undef;
205 0         0 $err = undef;
206             }
207             else {
208 37         86 $finished = 1;
209             }
210             }
211              
212 53         7296 $self->stop;
213              
214 53         774 my $hub = $self->{+HUB};
215              
216 53 100       179 if (!$finished) {
217 16 50       252 if(my $bailed = $hub->bailed_out) {
218 0         0 my $ctx = $self->context;
219 0         0 $ctx->bail($bailed->reason);
220 0         0 return;
221             }
222 16         300 my $code = $hub->exit_code;
223 16         117 $ok = !$code;
224 16 50       139 $err = "Subtest ended with exit code $code" if $code;
225             }
226              
227 53 100       149 unless ($ok) {
228 1         10 my $e = Test2::Event::Exception->new(
229             error => $err,
230             trace => Test2::Util::Trace->new(frame => [caller(0)]),
231             );
232 1         42 $hub->send($e);
233             }
234              
235 53         170 return $hub->is_passing;
236             }
237              
238             sub start {
239 59     59 1 313 my $self = shift;
240              
241             croak "Subtest is already complete"
242 59 50       266 if $self->{+FINISHED};
243              
244 59         135 $self->{+ACTIVE}++;
245              
246 59         176 push @STACK => $self;
247 59         187 my $hub = $self->{+HUB};
248 59         354 my $stack = Test2::API::test2_stack();
249 59         1009 $stack->push($hub);
250              
251 59         788 return $hub->is_passing;
252             }
253              
254             sub stop {
255 56     56 1 203 my $self = shift;
256              
257             croak "Subtest is not active"
258 56 50       437 unless $self->{+ACTIVE}--;
259              
260 56 50 33     513 croak "AsyncSubtest stack mismatch"
261             unless @STACK && $self == $STACK[-1];
262              
263 56         138 pop @STACK;
264              
265 56         136 my $hub = $self->{+HUB};
266 56         215 my $stack = Test2::API::test2_stack();
267 56         638 $stack->pop($hub);
268 56         892 return $hub->is_passing;
269             }
270              
271             sub finish {
272 167     167 1 18328 my $self = shift;
273 167         505 my %params = @_;
274              
275 167         1477 my $hub = $self->hub;
276              
277             croak "Subtest is already finished"
278 167 50       1381 if $self->{+FINISHED}++;
279              
280 167 50       1869 croak "Subtest can only be finished in the process/thread that created it"
281             unless $hub->is_local;
282              
283             croak "Subtest is still active"
284 167 50       2231 if $self->{+ACTIVE};
285              
286 167         879 $self->wait;
287              
288 167         1761 my $todo = $params{todo};
289 167         386 my $skip = $params{skip};
290 167         275 my $empty = !@{$self->{+EVENTS}};
  167         595  
291 167         884 my $no_asserts = !$hub->count;
292 167         724 my $collapse = $params{collapse};
293 167   100     1242 my $no_plan = $params{no_plan} || ($collapse && $no_asserts) || $skip;
294              
295 167 50 33     921 $hub->finalize($self->trace, !$no_plan)
296             unless $hub->no_ending || $hub->ended;
297              
298 167 50       41731 if ($hub->ipc) {
299 167         1005 $hub->ipc->drop_hub($hub->hid);
300 167         41660 $hub->set_ipc(undef);
301             }
302              
303 167 50       1351 return $hub->is_passing if $params{silent};
304              
305 167         815 my $ctx = $self->context;
306              
307 167         6115 my $pass = 1;
308 167 100       546 if ($skip) {
309 1         4 $ctx->skip($self->{+NAME}, $skip);
310             }
311             else {
312 166 100 100     537 if ($collapse && $empty) {
313 3         11 $ctx->ok($hub->is_passing, $self->{+NAME});
314 3         367 return $hub->is_passing;
315             }
316              
317             my $e = $ctx->build_event(
318             'Subtest',
319             pass => $hub->is_passing,
320             subtest_id => $hub->id,
321             name => $self->{+NAME},
322             buffered => 1,
323 163 50       537 subevents => $self->{+EVENTS},
324             $todo ? (
325             todo => $todo,
326             effective_pass => 1,
327             ) : (),
328             );
329              
330 163         17615 $ctx->hub->send($e);
331              
332 163 100       19004 unless ($e->effective_pass) {
333 4         25 $ctx->failure_diag($e);
334              
335             $ctx->diag("Bad subtest plan, expected " . $hub->plan . " but ran " . $hub->count)
336 4 50 66     224 if $hub->plan && !$hub->check_plan && !grep {$_->causes_fail} @{$self->{+EVENTS}};
  0   33     0  
  0         0  
337             }
338              
339 163         1037 $pass = $e->pass;
340             }
341              
342 164         811 $_->{+_IN_USE}-- for reverse @{$self->{+STACK}};
  164         628  
343              
344 164         835 return $pass;
345             }
346              
347             sub wait {
348 167     167 1 381 my $self = shift;
349              
350 167         339 my $hub = $self->{+HUB};
351 167         338 my $children = $self->{+CHILDREN};
352              
353 167         738 while (@$children) {
354 138         2548 $hub->cull;
355 138 50       18453 if (my $child = pop @$children) {
356 138 50       502 if (blessed($child)) {
357 0         0 $child->join;
358             }
359             else {
360 138         34466977 waitpid($child, 0);
361             }
362             }
363             else {
364 0         0 Time::HiRes::sleep('0.01');
365             }
366             }
367              
368 167         2095 $hub->cull;
369              
370             cluck "Subtest '$self->{+NAME}': All children have completed, but we still appear to be pending"
371 167 50 33     27568 if $hub->is_local && keys %{$self->{+HUB}->ast_ids};
  167         2226  
372             }
373              
374             sub fork {
375 180 50   180 1 1237 croak "Forking is not supported" unless CAN_FORK;
376 180         1805 my $self = shift;
377 180         864 my $id = $self->cleave;
378 180         178221 my $pid = CORE::fork();
379              
380 180 50       4327 unless (defined $pid) {
381 0         0 delete $self->{+HUB}->ast_ids->{$id};
382 0         0 croak "Failed to fork";
383             }
384              
385 180 100       2216 if($pid) {
386 158         817 push @{$self->{+CHILDREN}} => $pid;
  158         3380  
387 158         2467 return $pid;
388             }
389              
390 22         1268 $self->attach($id);
391              
392 22         20153 return $self->_guard;
393             }
394              
395             sub run_fork {
396 180     180 1 749 my $self = shift;
397 180         650 my ($code, @args) = @_;
398              
399 180         545 my $f = $self->fork;
400 180 100       5431 return $f unless blessed($f);
401              
402 22         342 $self->run($code, @args);
403              
404 21         401 $self->detach();
405 21         134 $f->dismiss();
406 21         527 exit 0;
407             }
408              
409             sub run_thread {
410 0 0   0 1 0 croak "Threading is not supported"
411             unless CAN_REALLY_THREAD;
412              
413 0         0 my $self = shift;
414 0         0 my ($code, @args) = @_;
415              
416 0         0 my $id = $self->cleave;
417             my $thr = threads->create(sub {
418 0     0   0 $self->attach($id);
419              
420 0         0 $self->run($code, @args);
421              
422 0         0 $self->detach(get_tid);
423 0         0 return 0;
424 0         0 });
425              
426 0         0 push @{$self->{+CHILDREN}} => $thr;
  0         0  
427              
428 0         0 return $thr;
429             }
430              
431             sub _guard {
432 22     22   159 my $self = shift;
433              
434 22         129 my ($pid, $tid) = ($$, get_tid);
435              
436             return Scope::Guard->new(sub {
437 1 50 33 1   85 return unless $$ == $pid && get_tid == $tid;
438              
439 0         0 my $error = "Scope Leak";
440 0 0       0 if (my $ex = $@) {
441 0         0 chomp($ex);
442 0         0 $error .= " ($ex)";
443             }
444              
445 0         0 cluck $error;
446              
447 0         0 my $e = $self->context->build_event(
448             'Exception',
449             error => "$error\n",
450             );
451 0         0 $self->{+HUB}->send($e);
452 0         0 $self->detach();
453 0         0 exit 255;
454 22         943 });
455             }
456              
457             sub DESTROY {
458 1     1   3 my $self = shift;
459 1 50       7 return unless $self->{+NAME};
460              
461 0 0         if (my $att = $self->{+_ATTACHED}) {
462 0 0         return unless $self->{+HUB};
463 0           eval { $self->detach() };
  0            
464             }
465              
466 0 0         return if $self->{+FINISHED};
467 0 0         return unless $self->{+PID} == $$;
468 0 0         return unless $self->{+TID} == get_tid;
469              
470 0           local $@;
471 0           eval { $_->{+_IN_USE}-- for reverse @{$self->{+STACK}} };
  0            
  0            
472              
473 0           warn "Subtest $self->{+NAME} did not finish!";
474 0           exit 255;
475             }
476              
477             1;
478              
479             __END__