File Coverage

blib/lib/Test2/Workflow/Runner.pm
Criterion Covered Total %
statement 238 258 92.2
branch 108 138 78.2
condition 35 57 61.4
subroutine 24 24 100.0
pod 0 10 0.0
total 405 487 83.1


line stmt bran cond sub pod time code
1             package Test2::Workflow::Runner;
2 44     44   126157 use strict;
  44         55  
  44         1002  
3 44     44   129 use warnings;
  44         45  
  44         860  
4              
5 44     44   147 use Test2::API();
  44         43  
  44         426  
6 44     44   15919 use Test2::Todo();
  44         21750  
  44         611  
7 44     44   17891 use Test2::AsyncSubtest();
  44         341430  
  44         937  
8              
9 44     44   257 use Test2::Util qw/get_tid CAN_REALLY_FORK/;
  44         55  
  44         2170  
10              
11 44     44   177 use Scalar::Util qw/blessed/;
  44         76  
  44         1582  
12 44     44   147 use Time::HiRes qw/sleep/;
  44         56  
  44         210  
13 44     44   6361 use List::Util qw/shuffle min/;
  44         55  
  44         2251  
14 44     44   192 use Carp qw/confess/;
  44         53  
  44         1752  
15              
16 44         165 use Test2::Util::HashBase qw{
17             stack no_fork no_threads max slots pid tid rand subtests filter
18 44     44   160 };
  44         49  
19              
20             use overload(
21             'fallback' => 1,
22             '&{}' => sub {
23 363     363   85684 my $self = shift;
24              
25             sub {
26 363     363   626 @_ = ($self);
27 363         1264 goto &run;
28             }
29 363         1628 },
30 44     44   11535 );
  44         46  
  44         380  
31              
32             sub init {
33 76     76 0 12838 my $self = shift;
34              
35 76         1239 $self->{+STACK} = [];
36 76         127 $self->{+SUBTESTS} = [];
37              
38 76         385 $self->{+PID} = $$;
39 76         129 $self->{+TID} = get_tid();
40              
41 76   33     535 $self->{+NO_FORK} ||= $ENV{T2_WORKFLOW_NO_FORK} || !CAN_REALLY_FORK();
      66        
42 76   33     3254 $self->{+NO_THREADS} ||= $ENV{T2_WORKFLOW_NO_THREADS} || !Test2::AsyncSubtest->CAN_REALLY_THREAD();
      66        
43              
44 76 100       438 $self->{+RAND} = 1 unless defined $self->{+RAND};
45              
46 76         329 my @max = grep {defined $_} $self->{+MAX}, $ENV{T2_WORKFLOW_ASYNC};
  152         347  
47 76 50       231 my $max = @max ? min(@max) : 3;
48 76         119 $self->{+MAX} = $max;
49 76 50       248 $self->{+SLOTS} = [] if $max;
50              
51 76 100       250 unless(defined($self->{+FILTER})) {
52 67 50       205 if (my $raw = $ENV{T2_WORKFLOW}) {
53 0         0 my ($file, $line, $name);
54 0 0       0 if ($raw =~ m/^(.*)\s+(\d+)$/) {
    0          
55 0         0 ($file, $line) = ($1, $2);
56             }
57             elsif($raw =~ m/^(\d+)$/) {
58 0         0 $line = $1;
59             }
60             else {
61 0         0 $name = $raw;
62             }
63              
64 0         0 $self->{+FILTER} = {
65             file => $file,
66             line => $line,
67             name => $name,
68             };
69             }
70             }
71              
72 76 100       249 if (my $task = delete $self->{task}) {
73 33         123 $self->push_task($task);
74             }
75             }
76              
77             sub is_local {
78 440     440 0 469 my $self = shift;
79 440 100       1082 return 0 unless $self->{+PID} == $$;
80 404 50       649 return 0 unless $self->{+TID} == get_tid();
81 404         1201 return 1;
82             }
83              
84             sub send_event {
85 260     260 0 3998 my $self = shift;
86 260         2127 my ($type, %params) = @_;
87              
88 260         372 my $class;
89 260 50       1538 if ($type =~ m/\+(.*)$/) {
90 0         0 $class = $1;
91             }
92             else {
93 260         485 $class = "Test2::Event::$type";
94             }
95              
96 260         6887 my $e = $class->new(
97             trace => Test2::Util::Trace->new(frame => [caller(0)]),
98             %params,
99             );
100              
101 260         16442 Test2::API::test2_stack()->top()->send($e);
102             }
103              
104             sub current_subtest {
105 1120     1120 0 932 my $self = shift;
106 1120 50       1933 my $stack = $self->{+STACK} or return undef;
107              
108 1120         1277 for my $state (reverse @$stack) {
109 2207 100       3880 next unless $state->{subtest};
110 1052         1586 return $state->{subtest};
111             }
112              
113 68         101 return undef;
114             }
115              
116             sub run {
117 433     433 0 760 my $self = shift;
118              
119 433         1021 my $stack = $self->stack;
120              
121 433         1111 my $c = 0;
122 433         895 while (@$stack) {
123 6998         20101 $self->cull;
124              
125 6998         6296 my $state = $stack->[-1];
126 6998         5776 my $task = $state->{task};
127              
128 6998 100       10864 unless($state->{started}++) {
129 1875         3839 my $skip = $task->skip;
130              
131 1875         3693 my $filter;
132 1875 100       3520 if (my $f = $self->{+FILTER}) {
133 649         684 my $in_var = grep { $_->{filter_satisfied} } @$stack;
  3115         2690  
134              
135 649 100       1135 $filter = $task->filter($f) unless $in_var;
136 649 100       1087 $state->{filter_satisfied} = 1 if $filter->{satisfied};
137             }
138              
139 1875 100 66     3528 $skip ||= $filter->{skip} if $filter;
140              
141 1875 100       2411 if ($skip) {
142 150         196 $state->{ended}++;
143 150   33     443 $self->send_event(
144             'Skip',
145             reason => $skip || $filter,
146             name => $task->name,
147             pass => 1,
148             effective_pass => 1,
149             );
150 150         10287 pop @$stack;
151 150         482 next;
152             }
153              
154 1725 100       2794 if ($task->flat) {
155 1120         3393 my $st = $self->current_subtest;
156 1120 100       2739 my $hub = $st ? $st->hub : Test2::API::test2_stack->top;
157              
158 1120 50       3540 $state->{todo} = Test2::Todo->new(reason => $task->todo, hub => $hub)
159             if $task->todo;
160              
161 1120         3107 $hub->send($_) for @{$task->events};
  1120         1672  
162             }
163             else {
164 605         2100 my $st = Test2::AsyncSubtest->new(
165             name => $task->name,
166             trace => Test2::Util::Trace->new(frame => $task->frame),
167             );
168 605         209237 $state->{subtest} = $st;
169              
170 605 100       1617 $state->{todo} = Test2::Todo->new(reason => $task->todo, hub => $st->hub)
171             if $task->todo;
172              
173 605         4100 $st->hub->send($_) for @{$task->events};
  605         1316  
174              
175 605         3683 my $slot = $self->isolate($state);
176              
177             # if we forked/threaded then this state has ended here.
178 605 100       1661 if (defined($slot)) {
179 184 100       1299 push @{$self->{+SUBTESTS}} => [$st, $task] unless $st->finished;
  98         1107  
180 184         683 $state->{subtest} = undef;
181 184         801 $state->{ended} = 1;
182             }
183             }
184             }
185              
186 6848 100       14147 if ($state->{ended}) {
187 1671 100       2874 $state->{todo}->end() if $state->{todo};
188 1671 100       5140 $state->{subtest}->stop() if $state->{subtest};
189              
190 1671 50       15432 return if $state->{in_thread};
191 1671 100       3291 if(my $guard = delete $state->{in_fork}) {
192 30         424 $state->{subtest}->detach;
193 30         12553 $guard->dismiss;
194 30         1593 exit 0;
195             }
196              
197 1641         2066 pop @$stack;
198 1641         6609 next;
199             }
200              
201 5177 100 100     16838 if($state->{subtest} && !$state->{subtest_started}++) {
202 421         664 push @{$self->{+SUBTESTS}} => [$state->{subtest}, $task];
  421         1354  
203 421         1342 $state->{subtest}->start();
204             }
205              
206 5177 100       34945 if ($task->isa('Test2::Workflow::Task::Action')) {
207 1100         1974 $state->{PID} = $$;
208 1100         1219 my $ok = eval { $task->code->($self); 1 };
  1100         2454  
  1100         676319  
209              
210 1100 50       3163 unless ($state->{PID} == $$) {
211 0         0 print STDERR "Task '" . $task->name . "' started in pid $state->{PID}, but ended in pid $$, did you forget to exit after forking?\n";
212 0         0 exit 255;
213             }
214              
215 1100 50       1805 $task->exception($@) unless $ok;
216 1100         1332 $state->{ended} = 1;
217              
218 1100         2665 next;
219             }
220              
221 4077 100 66     16530 if (!$state->{stage} || $state->{stage} eq 'BEFORE') {
    100          
    100          
    50          
222 1183   100     2778 $state->{before} //= 0;
223 1183 100       2271 if (my $add = $task->before->[$state->{before}++]) {
224 742 100       3230 if ($add->around) {
225 363         1182 $state->{PID} = $$;
226 363         349 my $ok = eval { $add->code->($self); 1 };
  363         650  
  355         84377  
227 355         477 my $err = $@;
228 355   33     1421 my $complete = $state->{stage} && $state->{stage} eq 'AFTER';
229              
230 355 50       779 unless ($state->{PID} == $$) {
231 0         0 print STDERR "Task '" . $task->name . "' started in pid $state->{PID}, but ended in pid $$, did you forget to exit after forking?\n";
232 0         0 exit 255;
233             }
234              
235 355 50 33     1632 unless($ok && $complete) {
236 0         0 $state->{ended} = 1;
237 0         0 $state->{stage} = 'AFTER';
238 0 0       0 $task->exception($ok ? "'around' task failed to continue into the workflow chain.\n" : $err);
239             }
240             }
241             else {
242 379         1180 $self->push_task($add);
243             }
244             }
245             else {
246 441         2486 $state->{stage} = 'VARIANT';
247             }
248             }
249             elsif ($state->{stage} eq 'VARIANT') {
250 441 100       953 if (my $v = $task->variant) {
251 102         1852 $self->push_task($v);
252             }
253 441         2023 $state->{stage} = 'PRIMARY';
254             }
255             elsif ($state->{stage} eq 'PRIMARY') {
256 1354 100       2271 unless (defined $state->{order}) {
257 441 50       913 my $rand = defined($task->rand) ? $task->rand : $self->rand;
258 441         2640 $state->{order} = [0 .. scalar(@{$task->primary}) - 1];
  441         851  
259 441 100       2193 @{$state->{order}} = shuffle(@{$state->{order}})
  253         505  
  253         786  
260             if $rand;
261             }
262 1354         1032 my $num = shift @{$state->{order}};
  1354         2229  
263 1354 100       1927 if (defined $num) {
264 967         2442 $self->push_task($task->primary->[$num]);
265             }
266             else {
267 387         1004 $state->{stage} = 'AFTER';
268             }
269             }
270             elsif ($state->{stage} eq 'AFTER') {
271 1099   100     2556 $state->{after} //= 0;
272 1099 100       2128 if (my $add = $task->after->[$state->{after}++]) {
273 712 100       2891 return if $add->around;
274 357         1158 $self->push_task($add);
275             }
276             else {
277 387         1748 $state->{ended} = 1;
278             }
279             }
280             }
281              
282 40         183 $self->finish;
283             }
284              
285             sub push_task {
286 1875     1875 0 3920 my $self = shift;
287 1875         1820 my ($task) = @_;
288              
289 1875 50       2828 confess "No Task!" unless $task;
290 1875 50 33     12502 confess "Bad Task ($task)!" unless blessed($task) && $task->isa('Test2::Workflow::Task');
291              
292 1875 100       7046 if ($task->isa('Test2::Workflow::Build')) {
293             confess "Can only push a Build instance when initializing the stack"
294 33 50       40 if @{$self->{+STACK}};
  33         114  
295 33         141 $task = $task->compile();
296             }
297              
298 1875         1487 push @{$self->{+STACK}} => {
  1875         5779  
299             task => $task,
300             name => $task->name,
301             };
302             }
303              
304             sub add_mock {
305 17     17 0 3425 my $self = shift;
306 17         28 my ($mock) = @_;
307 17         29 my $stack = $self->{+STACK};
308              
309 17 50 33     126 confess "Nothing on the stack!"
310             unless $stack && @$stack;
311              
312 17         38 my ($state) = grep { !$_->{task}->scaffold} reverse @$stack;
  76         349  
313 17         47 push @{$state->{mocks}} => $mock;
  17         80  
314             }
315              
316             sub isolate {
317 605     605 0 680 my $self = shift;
318 605         577 my ($state) = @_;
319              
320 605 50       1237 return if $state->{task}->skip;
321              
322 605         2297 my $iso = $state->{task}->iso;
323 605         1907 my $async = $state->{task}->async;
324              
325             # No need to isolate
326 605 100 66     3348 return undef unless $iso || $async;
327              
328             # Cannot isolate
329 220 100 66     1459 unless($self->{+MAX} && $self->is_local) {
330             # async does not NEED to be isolated
331 18 50       122 return undef unless $iso;
332             }
333              
334             # Wait for a slot, if max is set to 0 then we will not find a slot, instead
335             # we use '0'. We need to return a defined value to let the stack know that
336             # the task has ended.
337 220         288 my $slot = 0;
338 220   66     992 while($self->{+MAX} && $self->is_local) {
339 202         348 $self->cull;
340 202         840 for my $s (1 .. $self->{+MAX}) {
341 256         653 my $st = $self->{+SLOTS}->[$s];
342 256 100 100     901 next if $st && !$st->finished;
343 202         446 $self->{+SLOTS}->[$s] = undef;
344 202         212 $slot = $s;
345 202         277 last;
346             }
347 202 50       429 last if $slot;
348 0         0 sleep(0.02);
349             }
350              
351             my $st = $state->{subtest}
352 220 50       479 or confess "Cannot isolate a task without a subtest";
353              
354 220 100       790 if (!$self->no_fork) {
    50          
355 146         909 my $out = $st->fork;
356 146 100       202909 if (blessed($out)) {
357 36         561 $state->{in_fork} = $out;
358              
359             # drop back out to complete the task.
360 36         498 return undef;
361             }
362             else {
363             $self->send_event(
364             'Note',
365             message => "Forked PID $out to run: " . $state->{task}->name,
366 110         6014 );
367 110         33179 $state->{pid} = $out;
368             }
369             }
370             elsif (!$self->no_threads) {
371 0         0 $state->{in_thread} = 1;
372 0         0 my $thr = $st->run_thread(\&run, $self);
373 0         0 $state->{thread} = $thr;
374 0         0 delete $state->{in_thread};
375             $self->send_event(
376             'Note',
377             message => "Started Thread-ID " . $thr->tid . " to run: " . $state->{task}->name,
378 0         0 );
379             }
380             else {
381 74         661 $st->finish(skip => "No isolation method available");
382 74         42507 return 0;
383             }
384              
385 110 100       650 if($slot) {
386 98         447 $self->{+SLOTS}->[$slot] = $st;
387             }
388             else {
389 12         308 $st->finish;
390             }
391              
392 110         16541079 return $slot;
393             }
394              
395             sub cull {
396 7571     7571 0 7493 my $self = shift;
397              
398 7571   50     15744 my $subtests = delete $self->{+SUBTESTS} || return;
399 7571         6196 my @new;
400              
401             # Cull subtests in reverse order, Nested subtests end before their parents.
402 7571         9263 for my $set (reverse @$subtests) {
403 18942         18818 my ($st, $task) = @$set;
404 18942 50       28452 next if $st->finished;
405 18942 100 100     56032 if (!$st->active && $st->ready) {
406 452         114921 $st->finish();
407 452         43862499 next;
408             }
409              
410             # Use unshift to preserve order.
411 18490         318948 unshift @new => $set;
412             }
413              
414 7571         10925 $self->{+SUBTESTS} = \@new;
415              
416 7571         10396 return;
417             }
418              
419             sub finish {
420 40     40 0 91 my $self = shift;
421 40         82 while(@{$self->{+SUBTESTS}}) {
  411         7068  
422 371         2816 $self->cull;
423 371 100       568 sleep(0.02) if @{$self->{+SUBTESTS}};
  371         6982736  
424             }
425             }
426              
427             1;
428              
429             __END__