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   95699 use strict;
  44         47  
  44         1093  
3 44     44   233 use warnings;
  44         61  
  44         850  
4              
5 44     44   140 use Test2::API();
  44         84  
  44         546  
6 44     44   18053 use Test2::Todo();
  44         101251  
  44         770  
7 44     44   20039 use Test2::AsyncSubtest();
  44         400274  
  44         1036  
8              
9 44     44   277 use Test2::Util qw/get_tid CAN_REALLY_FORK/;
  44         66  
  44         2277  
10              
11 44     44   171 use Scalar::Util qw/blessed/;
  44         61  
  44         1680  
12 44     44   166 use Time::HiRes qw/sleep/;
  44         63  
  44         227  
13 44     44   7194 use List::Util qw/shuffle min/;
  44         48  
  44         2334  
14 44     44   189 use Carp qw/confess/;
  44         61  
  44         1939  
15              
16 44         166 use Test2::Util::HashBase qw{
17             stack no_fork no_threads max slots pid tid rand subtests filter
18 44     44   166 };
  44         61  
19              
20             use overload(
21             'fallback' => 1,
22             '&{}' => sub {
23 362     362   78352 my $self = shift;
24              
25             sub {
26 362     362   575 @_ = ($self);
27 362         1155 goto &run;
28             }
29 362         1433 },
30 44     44   11322 );
  44         64  
  44         395  
31              
32             sub init {
33 76     76 0 11832 my $self = shift;
34              
35 76         1287 $self->{+STACK} = [];
36 76         146 $self->{+SUBTESTS} = [];
37              
38 76         392 $self->{+PID} = $$;
39 76         125 $self->{+TID} = get_tid();
40              
41 76   33     673 $self->{+NO_FORK} ||= $ENV{T2_WORKFLOW_NO_FORK} || !CAN_REALLY_FORK();
      66        
42 76   33     3211 $self->{+NO_THREADS} ||= $ENV{T2_WORKFLOW_NO_THREADS} || !Test2::AsyncSubtest->CAN_REALLY_THREAD();
      66        
43              
44 76 100       437 $self->{+RAND} = 1 unless defined $self->{+RAND};
45              
46 76         277 my @max = grep {defined $_} $self->{+MAX}, $ENV{T2_WORKFLOW_ASYNC};
  152         323  
47 76 50       171 my $max = @max ? min(@max) : 3;
48 76         125 $self->{+MAX} = $max;
49 76 50       296 $self->{+SLOTS} = [] if $max;
50              
51 76 100       234 unless(defined($self->{+FILTER})) {
52 67 50       202 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       269 if (my $task = delete $self->{task}) {
73 33         101 $self->push_task($task);
74             }
75             }
76              
77             sub is_local {
78 446     446 0 390 my $self = shift;
79 446 100       1040 return 0 unless $self->{+PID} == $$;
80 410 50       648 return 0 unless $self->{+TID} == get_tid();
81 410         1141 return 1;
82             }
83              
84             sub send_event {
85 261     261 0 3389 my $self = shift;
86 261         2029 my ($type, %params) = @_;
87              
88 261         402 my $class;
89 261 50       1454 if ($type =~ m/\+(.*)$/) {
90 0         0 $class = $1;
91             }
92             else {
93 261         434 $class = "Test2::Event::$type";
94             }
95              
96 261         6171 my $e = $class->new(
97             trace => Test2::Util::Trace->new(frame => [caller(0)]),
98             %params,
99             );
100              
101 261         15404 Test2::API::test2_stack()->top()->send($e);
102             }
103              
104             sub current_subtest {
105 1108     1108 0 861 my $self = shift;
106 1108 50       1770 my $stack = $self->{+STACK} or return undef;
107              
108 1108         1197 for my $state (reverse @$stack) {
109 2183 100       3314 next unless $state->{subtest};
110 1040         1453 return $state->{subtest};
111             }
112              
113 68         88 return undef;
114             }
115              
116             sub run {
117 432     432 0 677 my $self = shift;
118              
119 432         1016 my $stack = $self->stack;
120              
121 432         1109 my $c = 0;
122 432         844 while (@$stack) {
123 6861         18755 $self->cull;
124              
125 6861         5764 my $state = $stack->[-1];
126 6861         5608 my $task = $state->{task};
127              
128 6861 100       10484 unless($state->{started}++) {
129 1845         4127 my $skip = $task->skip;
130              
131 1845         3398 my $filter;
132 1845 100       3280 if (my $f = $self->{+FILTER}) {
133 649         672 my $in_var = grep { $_->{filter_satisfied} } @$stack;
  3115         2774  
134              
135 649 100       1203 $filter = $task->filter($f) unless $in_var;
136 649 100       992 $state->{filter_satisfied} = 1 if $filter->{satisfied};
137             }
138              
139 1845 100 66     3652 $skip ||= $filter->{skip} if $filter;
140              
141 1845 100       2234 if ($skip) {
142 148         221 $state->{ended}++;
143 148   33     408 $self->send_event(
144             'Skip',
145             reason => $skip || $filter,
146             name => $task->name,
147             pass => 1,
148             effective_pass => 1,
149             );
150 148         9141 pop @$stack;
151 148         453 next;
152             }
153              
154 1697 100       2689 if ($task->flat) {
155 1108         3189 my $st = $self->current_subtest;
156 1108 100       2677 my $hub = $st ? $st->hub : Test2::API::test2_stack->top;
157              
158 1108 50       3280 $state->{todo} = Test2::Todo->new(reason => $task->todo, hub => $hub)
159             if $task->todo;
160              
161 1108         3033 $hub->send($_) for @{$task->events};
  1108         1631  
162             }
163             else {
164 589         1928 my $st = Test2::AsyncSubtest->new(
165             name => $task->name,
166             trace => Test2::Util::Trace->new(frame => $task->frame),
167             );
168 589         154568 $state->{subtest} = $st;
169              
170 589 100       1509 $state->{todo} = Test2::Todo->new(reason => $task->todo, hub => $st->hub)
171             if $task->todo;
172              
173 589         3031 $st->hub->send($_) for @{$task->events};
  589         1187  
174              
175 589         3005 my $slot = $self->isolate($state);
176              
177             # if we forked/threaded then this state has ended here.
178 589 100       1640 if (defined($slot)) {
179 187 100       1586 push @{$self->{+SUBTESTS}} => [$st, $task] unless $st->finished;
  101         1085  
180 187         681 $state->{subtest} = undef;
181 187         773 $state->{ended} = 1;
182             }
183             }
184             }
185              
186 6713 100       13882 if ($state->{ended}) {
187 1643 100       2737 $state->{todo}->end() if $state->{todo};
188 1643 100       4225 $state->{subtest}->stop() if $state->{subtest};
189              
190 1643 50       11195 return if $state->{in_thread};
191 1643 100       2781 if(my $guard = delete $state->{in_fork}) {
192 30         388 $state->{subtest}->detach;
193 30         12797 $guard->dismiss;
194 30         1463 exit 0;
195             }
196              
197 1613         1933 pop @$stack;
198 1613         6428 next;
199             }
200              
201 5070 100 100     14940 if($state->{subtest} && !$state->{subtest_started}++) {
202 402         462 push @{$self->{+SUBTESTS}} => [$state->{subtest}, $task];
  402         1198  
203 402         1274 $state->{subtest}->start();
204             }
205              
206 5070 100       32596 if ($task->isa('Test2::Workflow::Task::Action')) {
207 1084         1959 $state->{PID} = $$;
208 1084         1149 my $ok = eval { $task->code->($self); 1 };
  1084         2291  
  1084         583329  
209              
210 1084 50       2805 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 1084 50       2063 $task->exception($@) unless $ok;
216 1084         1338 $state->{ended} = 1;
217              
218 1084         2403 next;
219             }
220              
221 3986 100 66     15656 if (!$state->{stage} || $state->{stage} eq 'BEFORE') {
    100          
    100          
    50          
222 1162   100     2837 $state->{before} //= 0;
223 1162 100       2144 if (my $add = $task->before->[$state->{before}++]) {
224 736 100       2904 if ($add->around) {
225 362         1106 $state->{PID} = $$;
226 362         296 my $ok = eval { $add->code->($self); 1 };
  362         658  
  354         77814  
227 354         460 my $err = $@;
228 354   33     1301 my $complete = $state->{stage} && $state->{stage} eq 'AFTER';
229              
230 354 50       730 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 354 50 33     1770 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 374         1136 $self->push_task($add);
243             }
244             }
245             else {
246 426         2403 $state->{stage} = 'VARIANT';
247             }
248             }
249             elsif ($state->{stage} eq 'VARIANT') {
250 426 100       890 if (my $v = $task->variant) {
251 101         454 $self->push_task($v);
252             }
253 426         1874 $state->{stage} = 'PRIMARY';
254             }
255             elsif ($state->{stage} eq 'PRIMARY') {
256 1316 100       2159 unless (defined $state->{order}) {
257 426 50       833 my $rand = defined($task->rand) ? $task->rand : $self->rand;
258 426         2291 $state->{order} = [0 .. scalar(@{$task->primary}) - 1];
  426         723  
259 426 100       2097 @{$state->{order}} = shuffle(@{$state->{order}})
  238         418  
  238         646  
260             if $rand;
261             }
262 1316         1150 my $num = shift @{$state->{order}};
  1316         2062  
263 1316 100       1644 if (defined $num) {
264 944         2448 $self->push_task($task->primary->[$num]);
265             }
266             else {
267 372         955 $state->{stage} = 'AFTER';
268             }
269             }
270             elsif ($state->{stage} eq 'AFTER') {
271 1082   100     2508 $state->{after} //= 0;
272 1082 100       2111 if (my $add = $task->after->[$state->{after}++]) {
273 710 100       2888 return if $add->around;
274 356         1228 $self->push_task($add);
275             }
276             else {
277 372         1598 $state->{ended} = 1;
278             }
279             }
280             }
281              
282 40         243 $self->finish;
283             }
284              
285             sub push_task {
286 1845     1845 0 3579 my $self = shift;
287 1845         1801 my ($task) = @_;
288              
289 1845 50       3062 confess "No Task!" unless $task;
290 1845 50 33     10252 confess "Bad Task ($task)!" unless blessed($task) && $task->isa('Test2::Workflow::Task');
291              
292 1845 100       6626 if ($task->isa('Test2::Workflow::Build')) {
293             confess "Can only push a Build instance when initializing the stack"
294 33 50       41 if @{$self->{+STACK}};
  33         93  
295 33         154 $task = $task->compile();
296             }
297              
298 1845         1536 push @{$self->{+STACK}} => {
  1845         5336  
299             task => $task,
300             name => $task->name,
301             };
302             }
303              
304             sub add_mock {
305 13     13 0 2810 my $self = shift;
306 13         40 my ($mock) = @_;
307 13         37 my $stack = $self->{+STACK};
308              
309 13 50 33     134 confess "Nothing on the stack!"
310             unless $stack && @$stack;
311              
312 13         35 my ($state) = grep { !$_->{task}->scaffold} reverse @$stack;
  56         488  
313 13         64 push @{$state->{mocks}} => $mock;
  13         68  
314             }
315              
316             sub isolate {
317 589     589 0 544 my $self = shift;
318 589         581 my ($state) = @_;
319              
320 589 50       1085 return if $state->{task}->skip;
321              
322 589         2252 my $iso = $state->{task}->iso;
323 589         1908 my $async = $state->{task}->async;
324              
325             # No need to isolate
326 589 100 66     2801 return undef unless $iso || $async;
327              
328             # Cannot isolate
329 223 100 66     1200 unless($self->{+MAX} && $self->is_local) {
330             # async does not NEED to be isolated
331 18 50       92 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 223         316 my $slot = 0;
338 223   66     912 while($self->{+MAX} && $self->is_local) {
339 205         323 $self->cull;
340 205         690 for my $s (1 .. $self->{+MAX}) {
341 261         655 my $st = $self->{+SLOTS}->[$s];
342 261 100 100     957 next if $st && !$st->finished;
343 205         490 $self->{+SLOTS}->[$s] = undef;
344 205         223 $slot = $s;
345 205         290 last;
346             }
347 205 50       364 last if $slot;
348 0         0 sleep(0.02);
349             }
350              
351             my $st = $state->{subtest}
352 223 50       539 or confess "Cannot isolate a task without a subtest";
353              
354 223 100       722 if (!$self->no_fork) {
    50          
355 149         895 my $out = $st->fork;
356 149 100       181325 if (blessed($out)) {
357 36         536 $state->{in_fork} = $out;
358              
359             # drop back out to complete the task.
360 36         575 return undef;
361             }
362             else {
363             $self->send_event(
364             'Note',
365             message => "Forked PID $out to run: " . $state->{task}->name,
366 113         3118 );
367 113         38661 $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         633 $st->finish(skip => "No isolation method available");
382 74         40923 return 0;
383             }
384              
385 113 100       602 if($slot) {
386 101         487 $self->{+SLOTS}->[$slot] = $st;
387             }
388             else {
389 12         260 $st->finish;
390             }
391              
392 113         9760566 return $slot;
393             }
394              
395             sub cull {
396 7270     7270 0 5601 my $self = shift;
397              
398 7270   50     13468 my $subtests = delete $self->{+SUBTESTS} || return;
399 7270         5574 my @new;
400              
401             # Cull subtests in reverse order, Nested subtests end before their parents.
402 7270         7767 for my $set (reverse @$subtests) {
403 18649         16622 my ($st, $task) = @$set;
404 18649 50       25612 next if $st->finished;
405 18649 100 100     51725 if (!$st->active && $st->ready) {
406 437         109622 $st->finish();
407 437         38560865 next;
408             }
409              
410             # Use unshift to preserve order.
411 18212         241814 unshift @new => $set;
412             }
413              
414 7270         9038 $self->{+SUBTESTS} = \@new;
415              
416 7270         9834 return;
417             }
418              
419             sub finish {
420 40     40 0 100 my $self = shift;
421 40         115 while(@{$self->{+SUBTESTS}}) {
  244         2454  
422 204         909 $self->cull;
423 204 100       285 sleep(0.02) if @{$self->{+SUBTESTS}};
  204         3603669  
424             }
425             }
426              
427             1;
428              
429             __END__