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   4204243 use strict;
  29         63  
  29         721  
3 29     29   144 use warnings;
  29         59  
  29         619  
4              
5 29     29   4735 use Test2::IPC;
  29         12280  
  29         122  
6              
7             our $VERSION = '0.000020';
8              
9             our @CARP_NOT = qw/Test2::Util::HashBase/;
10              
11 29     29   1864 use Carp qw/croak cluck/;
  29         62  
  29         1101  
12 29     29   125 use Test2::Util qw/get_tid CAN_THREAD CAN_FORK/;
  29         54  
  29         1171  
13 29     29   177 use Scalar::Util qw/blessed/;
  29         63  
  29         1181  
14 29     29   167 use List::Util qw/first/;
  29         44  
  29         2213  
15              
16 29     29   7077 use Scope::Guard();
  29         9063  
  29         442  
17 29     29   147 use Test2::API();
  29         152  
  29         323  
18 29     29   166 use Test2::API::Context();
  29         54  
  29         340  
19 29     29   136 use Test2::Util::Trace();
  29         37  
  29         296  
20 29     29   8326 use Time::HiRes();
  29         29602  
  29         682  
21              
22 29     29   6704 use Test2::AsyncSubtest::Hub();
  29         59  
  29         462  
23 29     29   6907 use Test2::AsyncSubtest::Event::Attach();
  29         63  
  29         426  
24 29     29   6464 use Test2::AsyncSubtest::Event::Detach();
  29         59  
  29         752  
25              
26 29         245 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   169 };
  29         39  
38              
39             sub CAN_REALLY_THREAD {
40 2     2 0 8854 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 164788 my $self = shift;
51              
52             croak "'name' is a required attribute"
53 216 100       1219 unless $self->{+NAME};
54              
55 215   33     3361 $self->{+SEND_TO} ||= Test2::API::test2_stack()->top;
56              
57 215         82691 $self->{+STACK} = [@STACK];
58 215         915 $_->{+_IN_USE}++ for reverse @STACK;
59              
60 215         943 $self->{+TID} = get_tid;
61 215         703 $self->{+PID} = $$;
62 215         1152 $self->{+ID} = 1;
63 215         649 $self->{+FINISHED} = 0;
64 215         1123 $self->{+ACTIVE} = 0;
65 215         568 $self->{+_IN_USE} = 0;
66 215         1006 $self->{+CHILDREN} = [];
67              
68 215 50       1289 unless($self->{+HUB}) {
69 215         1273 my $ipc = Test2::API::test2_ipc();
70 215         6861 my $formatter = Test2::API::test2_stack->top->format;
71 215   100     7794 my $args = delete $self->{hub_init_args} || {};
72 215         4486 my $hub = Test2::AsyncSubtest::Hub->new(
73             %$args,
74             ipc => $ipc,
75             nested => 1,
76             formatter => $formatter,
77             );
78 215         4258 $self->{+HUB} = $hub;
79             }
80              
81 215   33     6788 $self->{+TRACE} ||= Test2::Util::Trace->new(
82             frame => [caller(1)],
83             );
84              
85 215         9185 my $hub = $self->{+HUB};
86 215 50       1206 $hub->set_ast_ids({}) unless $hub->ast_ids;
87 215         3152 $hub->listen($self->_listener);
88 215         8010 $hub->pre_filter($self->_pre_filter);
89             }
90              
91             sub _listener {
92 215     215   599 my $self = shift;
93              
94 215   50     1456 my $events = $self->{+EVENTS} ||= [];
95              
96 215     498   4282 sub { push @$events => $_[1] };
  498         48560  
97             }
98              
99             sub _pre_filter {
100 215     215   770 my $self = shift;
101              
102             sub {
103 134     134   38656 my ($hub, $e) = @_;
104 134 100       641 return $e if $hub->is_local;
105              
106 73         845 my $attached = $self->{+_ATTACHED};
107 73 50 33     1766 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         2887 };
111             }
112              
113             sub context {
114 167     167 1 436 my $self = shift;
115             return Test2::API::Context->new(
116             trace => $self->{+TRACE},
117 167         2274 hub => $self->{+SEND_TO},
118             );
119             }
120              
121             sub _gen_event {
122 47     47   561 my $self = shift;
123 47         548 my ($type, $id) = @_;
124              
125 47         400 my $class = "Test2::AsyncSubtest::Event::$type";
126              
127 47         3028 return $class->new(id => $id, trace => Test2::Util::Trace->new(frame => [caller(1)]));
128             }
129              
130             sub cleave {
131 185     185 1 535 my $self = shift;
132 185         798 my $id = $self->{+ID}++;
133 185         758 $self->{+HUB}->ast_ids->{$id} = 0;
134 185         2099 return $id;
135             }
136              
137             sub attach {
138 24     24 1 15834 my $self = shift;
139 24         406 my ($id) = @_;
140              
141 24 50       562 croak "An ID is required" unless $id;
142              
143             croak "ID $id is not valid"
144 24 50       1196 unless defined $self->{+HUB}->ast_ids->{$id};
145              
146             croak "ID $id is already attached"
147 24 50       1002 if $self->{+HUB}->ast_ids->{$id};
148              
149             croak "You must attach INSIDE the child process/thread"
150 24 50       1401 if $self->{+HUB}->is_local;
151              
152 24         1288 $self->{+_ATTACHED} = [ $$, get_tid, $id ];
153 24         324 $self->{+HUB}->send($self->_gen_event('Attach', $id));
154             }
155              
156             sub detach {
157 23     23 1 1557 my $self = shift;
158              
159 23 50 33     251 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       224 my $att = $self->{+_ATTACHED}
165             or croak "Not attached";
166              
167 23 50 33     513 croak "Attempt to detach from wrong child"
168             unless $att->[0] == $$ && $att->[1] == get_tid;
169              
170 23         103 my $id = $att->[2];
171              
172 23         187 $self->{+HUB}->send($self->_gen_event('Detach', $id));
173              
174 23         898 delete $self->{+_ATTACHED};
175             }
176              
177 0     0 1 0 sub ready { return !shift->pending }
178             sub pending {
179 2     2 1 274 my $self = shift;
180 2         8 my $hub = $self->{+HUB};
181 2 50       12 return -1 unless $hub->is_local;
182              
183 2         116 $hub->cull;
184              
185 2         716 return $self->{+_IN_USE} + keys %{$self->{+HUB}->ast_ids};
  2         51  
186             }
187              
188             sub run {
189 56     56 1 490365 my $self = shift;
190 56         205 my ($code, @args) = @_;
191              
192 56 50 33     559 croak "AsyncSubtest->run() takes a codeblock as the first argument"
193             unless $code && ref($code) eq 'CODE';
194              
195 56         333 $self->start;
196              
197 56         1117 my ($ok, $err, $finished);
198             T2_SUBTEST_WRAPPER: {
199 56         153 $ok = eval { $code->(@args); 1 };
  56         153  
  56         363  
  36         8495  
200 37         187 $err = $@;
201              
202             # They might have done 'BEGIN { skip_all => "whatever" }'
203 37 50 66     276 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         95 $finished = 1;
209             }
210             }
211              
212 53         11470 $self->stop;
213              
214 53         953 my $hub = $self->{+HUB};
215              
216 53 100       198 if (!$finished) {
217 16 50       294 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         229 my $code = $hub->exit_code;
223 16         110 $ok = !$code;
224 16 50       149 $err = "Subtest ended with exit code $code" if $code;
225             }
226              
227 53 100       194 unless ($ok) {
228 1         15 my $e = Test2::Event::Exception->new(
229             error => $err,
230             trace => Test2::Util::Trace->new(frame => [caller(0)]),
231             );
232 1         82 $hub->send($e);
233             }
234              
235 53         195 return $hub->is_passing;
236             }
237              
238             sub start {
239 59     59 1 215 my $self = shift;
240              
241             croak "Subtest is already complete"
242 59 50       208 if $self->{+FINISHED};
243              
244 59         199 $self->{+ACTIVE}++;
245              
246 59         204 push @STACK => $self;
247 59         143 my $hub = $self->{+HUB};
248 59         361 my $stack = Test2::API::test2_stack();
249 59         1369 $stack->push($hub);
250              
251 59         572 return $hub->is_passing;
252             }
253              
254             sub stop {
255 56     56 1 277 my $self = shift;
256              
257             croak "Subtest is not active"
258 56 50       779 unless $self->{+ACTIVE}--;
259              
260 56 50 33     602 croak "AsyncSubtest stack mismatch"
261             unless @STACK && $self == $STACK[-1];
262              
263 56         268 pop @STACK;
264              
265 56         191 my $hub = $self->{+HUB};
266 56         267 my $stack = Test2::API::test2_stack();
267 56         719 $stack->pop($hub);
268 56         950 return $hub->is_passing;
269             }
270              
271             sub finish {
272 167     167 1 23285 my $self = shift;
273 167         548 my %params = @_;
274              
275 167         3051 my $hub = $self->hub;
276              
277             croak "Subtest is already finished"
278 167 50       1495 if $self->{+FINISHED}++;
279              
280 167 50       2891 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       5050 if $self->{+ACTIVE};
285              
286 167         1235 $self->wait;
287              
288 167         1981 my $todo = $params{todo};
289 167         488 my $skip = $params{skip};
290 167         326 my $empty = !@{$self->{+EVENTS}};
  167         578  
291 167         1181 my $no_asserts = !$hub->count;
292 167         896 my $collapse = $params{collapse};
293 167   100     1766 my $no_plan = $params{no_plan} || ($collapse && $no_asserts) || $skip;
294              
295 167 50 33     752 $hub->finalize($self->trace, !$no_plan)
296             unless $hub->no_ending || $hub->ended;
297              
298 167 50       56961 if ($hub->ipc) {
299 167         1104 $hub->ipc->drop_hub($hub->hid);
300 167         49069 $hub->set_ipc(undef);
301             }
302              
303 167 50       1235 return $hub->is_passing if $params{silent};
304              
305 167         862 my $ctx = $self->context;
306              
307 167         8316 my $pass = 1;
308 167 100       583 if ($skip) {
309 1         7 $ctx->skip($self->{+NAME}, $skip);
310             }
311             else {
312 166 100 100     735 if ($collapse && $empty) {
313 3         13 $ctx->ok($hub->is_passing, $self->{+NAME});
314 3         467 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       747 subevents => $self->{+EVENTS},
324             $todo ? (
325             todo => $todo,
326             effective_pass => 1,
327             ) : (),
328             );
329              
330 163         24828 $ctx->hub->send($e);
331              
332 163 100       15869 unless ($e->effective_pass) {
333 4         33 $ctx->failure_diag($e);
334              
335             $ctx->diag("Bad subtest plan, expected " . $hub->plan . " but ran " . $hub->count)
336 4 50 66     468 if $hub->plan && !$hub->check_plan && !grep {$_->causes_fail} @{$self->{+EVENTS}};
  0   33     0  
  0         0  
337             }
338              
339 163         1256 $pass = $e->pass;
340             }
341              
342 164         1104 $_->{+_IN_USE}-- for reverse @{$self->{+STACK}};
  164         685  
343              
344 164         907 return $pass;
345             }
346              
347             sub wait {
348 167     167 1 450 my $self = shift;
349              
350 167         770 my $hub = $self->{+HUB};
351 167         690 my $children = $self->{+CHILDREN};
352              
353 167         885 while (@$children) {
354 138         3848 $hub->cull;
355 138 50       32847 if (my $child = pop @$children) {
356 138 50       1023 if (blessed($child)) {
357 0         0 $child->join;
358             }
359             else {
360 138         40544837 waitpid($child, 0);
361             }
362             }
363             else {
364 0         0 Time::HiRes::sleep('0.01');
365             }
366             }
367              
368 167         2403 $hub->cull;
369              
370             cluck "Subtest '$self->{+NAME}': All children have completed, but we still appear to be pending"
371 167 50 33     42268 if $hub->is_local && keys %{$self->{+HUB}->ast_ids};
  167         2619  
372             }
373              
374             sub fork {
375 180 50   180 1 1832 croak "Forking is not supported" unless CAN_FORK;
376 180         3139 my $self = shift;
377 180         880 my $id = $self->cleave;
378 180         363255 my $pid = CORE::fork();
379              
380 180 50       4747 unless (defined $pid) {
381 0         0 delete $self->{+HUB}->ast_ids->{$id};
382 0         0 croak "Failed to fork";
383             }
384              
385 180 100       3112 if($pid) {
386 158         1249 push @{$self->{+CHILDREN}} => $pid;
  158         4760  
387 158         4003 return $pid;
388             }
389              
390 22         1730 $self->attach($id);
391              
392 22         2202 return $self->_guard;
393             }
394              
395             sub run_fork {
396 180     180 1 907 my $self = shift;
397 180         665 my ($code, @args) = @_;
398              
399 180         799 my $f = $self->fork;
400 180 100       6264 return $f unless blessed($f);
401              
402 22         293 $self->run($code, @args);
403              
404 21         491 $self->detach();
405 21         181 $f->dismiss();
406 21         688 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   102 my $self = shift;
433              
434 22         206 my ($pid, $tid) = ($$, get_tid);
435              
436             return Scope::Guard->new(sub {
437 1 50 33 1   97 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         1060 });
455             }
456              
457             sub DESTROY {
458 1     1   4 my $self = shift;
459 1 50       8 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__