File Coverage

blib/lib/Test2/Workflow/Runner.pm
Criterion Covered Total %
statement 248 267 92.8
branch 113 142 79.5
condition 32 56 57.1
subroutine 24 24 100.0
pod 0 10 0.0
total 417 499 83.5


line stmt bran cond sub pod time code
1             package Test2::Workflow::Runner;
2 45     45   329 use strict;
  45         118  
  45         1395  
3 45     45   239 use warnings;
  45         70  
  45         1931  
4              
5             our $VERSION = '0.000156';
6              
7 45     45   307 use Test2::API();
  45         249  
  45         1250  
8 45     45   17878 use Test2::Todo();
  45         149  
  45         873  
9 45     45   20978 use Test2::AsyncSubtest();
  45         124  
  45         1277  
10              
11 45     45   399 use Test2::Util qw/get_tid CAN_REALLY_FORK/;
  45         93  
  45         2527  
12              
13 45     45   304 use Scalar::Util qw/blessed/;
  45         114  
  45         1871  
14 45     45   313 use Time::HiRes qw/sleep/;
  45         129  
  45         564  
15 45     45   6243 use List::Util qw/shuffle min/;
  45         179  
  45         2624  
16 45     45   312 use Carp qw/confess/;
  45         93  
  45         2123  
17              
18 45         215 use Test2::Util::HashBase qw{
19             stack no_fork no_threads max slots pid tid rand subtests filter
20 45     45   419 };
  45         115  
21              
22             use overload(
23             'fallback' => 1,
24             '&{}' => sub {
25 367     367   7662 my $self = shift;
26              
27             sub {
28 367     367   901 @_ = ($self);
29 367         1470 goto &run;
30             }
31 367         2088 },
32 45     45   25763 );
  45         111  
  45         408  
33              
34             sub init {
35 77     77 0 14919 my $self = shift;
36              
37 77         1613 $self->{+STACK} = [];
38 77         202 $self->{+SUBTESTS} = [];
39              
40 77         558 $self->{+PID} = $$;
41 77         189 $self->{+TID} = get_tid();
42              
43 77   33     746 $self->{+NO_FORK} ||= $ENV{T2_WORKFLOW_NO_FORK} || !CAN_REALLY_FORK();
      66        
44              
45 77         3442 my $can_thread = Test2::AsyncSubtest->CAN_REALLY_THREAD();
46 77   33     595 my $should_thread = ($ENV{T2_WORKFLOW_USE_THREADS} || $ENV{T2_DO_THREAD_TESTS}) && !$ENV{T2_WORKFLOW_NO_THREADS};
47 77   33     536 $self->{+NO_THREADS} ||= !($can_thread && $should_thread);
      66        
48              
49 77 100       308 $self->{+RAND} = 1 unless defined $self->{+RAND};
50              
51 77         382 my @max = grep {defined $_} $self->{+MAX}, $ENV{T2_WORKFLOW_ASYNC};
  154         416  
52 77 50       282 my $max = @max ? min(@max) : 3;
53 77         201 $self->{+MAX} = $max;
54 77 50       322 $self->{+SLOTS} = [] if $max;
55              
56 77 100       275 unless(defined($self->{+FILTER})) {
57 68 50       249 if (my $raw = $ENV{T2_WORKFLOW}) {
58 0         0 my ($file, $line, $name);
59 0 0       0 if ($raw =~ m/^(.*)\s+(\d+)$/) {
    0          
60 0         0 ($file, $line) = ($1, $2);
61             }
62             elsif($raw =~ m/^(\d+)$/) {
63 0         0 $line = $1;
64             }
65             else {
66 0         0 $name = $raw;
67             }
68              
69 0         0 $self->{+FILTER} = {
70             file => $file,
71             line => $line,
72             name => $name,
73             };
74             }
75             }
76              
77 77 100       304 if (my $task = delete $self->{task}) {
78 33         119 $self->push_task($task);
79             }
80             }
81              
82             sub is_local {
83 470     470 0 1137 my $self = shift;
84 470 100       1781 return 0 unless $self->{+PID} == $$;
85 434 50       1212 return 0 unless $self->{+TID} == get_tid();
86 434         1677 return 1;
87             }
88              
89             sub send_event {
90 269     269 0 9336 my $self = shift;
91 269         4558 my ($type, %params) = @_;
92              
93 269         1116 my $class;
94 269 50       2835 if ($type =~ m/\+(.*)$/) {
95 0         0 $class = $1;
96             }
97             else {
98 269         1070 $class = "Test2::Event::$type";
99             }
100              
101 269         3315 my $hub = Test2::API::test2_stack()->top();
102              
103 269         23591 my $e = $class->new(
104             trace => Test2::Util::Trace->new(
105             frame => [caller(0)],
106             buffered => $hub->buffered,
107             nested => $hub->nested,
108             hid => $hub->hid,
109             huuid => $hub->uuid,
110             #cid => $self->{+CID},
111             #uuid => $self->{+UUID},
112             ),
113              
114             %params,
115             );
116              
117 269         51563 $hub->send($e);
118             }
119              
120             sub current_subtest {
121 1145     1145 0 1498 my $self = shift;
122 1145 50       2693 my $stack = $self->{+STACK} or return undef;
123              
124 1145         2217 for my $state (reverse @$stack) {
125 2256 100       4385 next unless $state->{subtest};
126 1076         2587 return $state->{subtest};
127             }
128              
129 69         248 return undef;
130             }
131              
132             sub run {
133 438     438 0 1394 my $self = shift;
134              
135 438         1583 my $stack = $self->stack;
136              
137 438         1727 my $c = 0;
138 438         1117 while (@$stack) {
139 7149         29809 $self->cull;
140              
141 7149         15005 my $state = $stack->[-1];
142 7149         12722 my $task = $state->{task};
143              
144 7149 100       15348 unless($state->{started}++) {
145 1919         6858 my $skip = $task->skip;
146              
147 1919         6398 my $filter;
148 1919 100       4941 if (my $f = $self->{+FILTER}) {
149 649         1331 my $in_var = grep { $_->{filter_satisfied} } @$stack;
  3115         5366  
150              
151 649 100       1650 $filter = $task->filter($f) unless $in_var;
152 649 100       1517 $state->{filter_satisfied} = 1 if $filter->{satisfied};
153             }
154              
155 1919 100 66     5405 $skip ||= $filter->{skip} if $filter;
156              
157 1919 100       3256 if ($skip) {
158 147         377 $state->{ended}++;
159 147   33     538 $self->send_event(
160             'Skip',
161             reason => $skip || $filter,
162             name => $task->name,
163             pass => 1,
164             effective_pass => 1,
165             );
166 147         10425 pop @$stack;
167 147         617 next;
168             }
169              
170 1772 100       4229 if ($task->flat) {
171 1145         5024 my $st = $self->current_subtest;
172 1145 100       3359 my $hub = $st ? $st->hub : Test2::API::test2_stack->top;
173              
174 1145 50       5429 $state->{todo} = Test2::Todo->new(reason => $task->todo, hub => $hub)
175             if $task->todo;
176              
177 1145         3793 $hub->send($_) for @{$task->events};
  1145         2415  
178             }
179             else {
180 627         3352 my $st = Test2::AsyncSubtest->new(
181             name => $task->name,
182             frame => $task->frame,
183             );
184 627         14788 $state->{subtest} = $st;
185              
186 627 100       2536 $state->{todo} = Test2::Todo->new(reason => $task->todo, hub => $st->hub)
187             if $task->todo;
188              
189 627         4110 for my $e (@{$task->events}) {
  627         2690  
190 3         147 my $hub = $st->hub;
191              
192 3         53 $e->trace->{buffered} = $hub->buffered;
193 3         96 $e->trace->{nested} = $hub->nested;
194 3         48 $e->trace->{hid} = $hub->hid;
195 3         54 $e->trace->{huuid} = $hub->uuid;
196              
197 3         69 $hub->send($e);
198             }
199              
200 627         4286 my $slot = $self->isolate($state);
201              
202             # if we forked/threaded then this state has ended here.
203 627 100       2814 if (defined($slot)) {
204 196 100       3931 push @{$self->{+SUBTESTS}} => [$st, $task] unless $st->finished;
  110         2865  
205 196         1448 $state->{subtest} = undef;
206 196         1242 $state->{ended} = 1;
207             }
208             }
209             }
210              
211 7002 100       22140 if ($state->{ended}) {
212 1718 100       4340 $state->{todo}->end() if $state->{todo};
213 1718 100       4786 $state->{subtest}->stop() if $state->{subtest};
214              
215 1718 50       8478 return if $state->{in_thread};
216 1718 100       6372 if(my $guard = delete $state->{in_fork}) {
217 30         652 $state->{subtest}->detach;
218 30         421 $guard->dismiss;
219 30         2137 exit 0;
220             }
221              
222 1688         3808 pop @$stack;
223 1688         11762 next;
224             }
225              
226 5284 100 100     18024 if($state->{subtest} && !$state->{subtest_started}++) {
227 431         721 push @{$self->{+SUBTESTS}} => [$state->{subtest}, $task];
  431         1924  
228 431         1998 $state->{subtest}->start();
229             }
230              
231 5284 100       34778 if ($task->isa('Test2::Workflow::Task::Action')) {
232 1127         3407 $state->{PID} = $$;
233 1127         1875 my $ok = eval { $task->code->($self); 1 };
  1127         3588  
  1127         30647  
234              
235 1127 50       3812 unless ($state->{PID} == $$) {
236 0         0 print STDERR "Task '" . $task->name . "' started in pid $state->{PID}, but ended in pid $$, did you forget to exit after forking?\n";
237 0         0 exit 255;
238             }
239              
240 1127 50       2314 $task->exception($@) unless $ok;
241 1127         2087 $state->{ended} = 1;
242              
243 1127         3551 next;
244             }
245              
246 4157 100 66     20329 if (!$state->{stage} || $state->{stage} eq 'BEFORE') {
    100          
    100          
    50          
247 1204 100       3122 $state->{before} = (defined $state->{before}) ? $state->{before} : 0;
248              
249 1204 100       3069 if (my $add = $task->before->[$state->{before}++]) {
250 755 100       4058 if ($add->around) {
251 367         1704 $state->{PID} = $$;
252 367         536 my $ok = eval { $add->code->($self); 1 };
  367         909  
  359         6911  
253 359         852 my $err = $@;
254 359   33     1798 my $complete = $state->{stage} && $state->{stage} eq 'AFTER';
255              
256 359 50       1048 unless ($state->{PID} == $$) {
257 0         0 print STDERR "Task '" . $task->name . "' started in pid $state->{PID}, but ended in pid $$, did you forget to exit after forking?\n";
258 0         0 exit 255;
259             }
260              
261 359 50 33     2035 unless($ok && $complete) {
262 0         0 $state->{ended} = 1;
263 0         0 $state->{stage} = 'AFTER';
264 0 0       0 $task->exception($ok ? "'around' task failed to continue into the workflow chain.\n" : $err);
265             }
266             }
267             else {
268 388         1766 $self->push_task($add);
269             }
270             }
271             else {
272 449         3399 $state->{stage} = 'VARIANT';
273             }
274             }
275             elsif ($state->{stage} eq 'VARIANT') {
276 449 100       1629 if (my $v = $task->variant) {
277 105         707 $self->push_task($v);
278             }
279 449         2839 $state->{stage} = 'PRIMARY';
280             }
281             elsif ($state->{stage} eq 'PRIMARY') {
282 1389 100       3496 unless (defined $state->{order}) {
283 449 50       1331 my $rand = defined($task->rand) ? $task->rand : $self->rand;
284 449         3553 $state->{order} = [0 .. scalar(@{$task->primary}) - 1];
  449         1102  
285 449 100       3106 @{$state->{order}} = shuffle(@{$state->{order}})
  261         648  
  261         1196  
286             if $rand;
287             }
288 1389         2011 my $num = shift @{$state->{order}};
  1389         3345  
289 1389 100       3283 if (defined $num) {
290 994         4060 $self->push_task($task->primary->[$num]);
291             }
292             else {
293 395         1340 $state->{stage} = 'AFTER';
294             }
295             }
296             elsif ($state->{stage} eq 'AFTER') {
297 1115 100       2825 $state->{after} = (defined $state->{after}) ? $state->{after} : 0;
298 1115 100       15612 if (my $add = $task->after->[$state->{after}++]) {
299 720 100       4032 return if $add->around;
300 361         1569 $self->push_task($add);
301             }
302             else {
303 395         2540 $state->{ended} = 1;
304             }
305             }
306             }
307              
308 41         308 $self->finish;
309             }
310              
311             sub push_task {
312 1919     1919 0 6178 my $self = shift;
313 1919         4185 my ($task) = @_;
314              
315 1919 50       4374 confess "No Task!" unless $task;
316 1919 50 33     13741 confess "Bad Task ($task)!" unless blessed($task) && $task->isa('Test2::Workflow::Task');
317              
318 1919 100       10183 if ($task->isa('Test2::Workflow::Build')) {
319             confess "Can only push a Build instance when initializing the stack"
320 33 50       70 if @{$self->{+STACK}};
  33         138  
321 33         233 $task = $task->compile();
322             }
323              
324 1919         3416 push @{$self->{+STACK}} => {
  1919         7626  
325             task => $task,
326             name => $task->name,
327             };
328             }
329              
330             sub add_mock {
331 21     21 0 54 my $self = shift;
332 21         55 my ($mock) = @_;
333 21         69 my $stack = $self->{+STACK};
334              
335 21 50 33     289 confess "Nothing on the stack!"
336             unless $stack && @$stack;
337              
338 21         94 my ($state) = grep { !$_->{task}->scaffold} reverse @$stack;
  92         658  
339 21         167 push @{$state->{mocks}} => $mock;
  21         283  
340             }
341              
342             sub isolate {
343 627     627 0 1078 my $self = shift;
344 627         1218 my ($state) = @_;
345              
346 627 50       1635 return if $state->{task}->skip;
347              
348 627         3646 my $iso = $state->{task}->iso;
349 627         3788 my $async = $state->{task}->async;
350              
351             # No need to isolate
352 627 100 66     3664 return undef unless $iso || $async;
353              
354             # Cannot isolate
355 232 100 66     1954 unless($self->{+MAX} && $self->is_local) {
356             # async does not NEED to be isolated
357 18 50       89 return undef unless $iso;
358             }
359              
360             # Wait for a slot, if max is set to 0 then we will not find a slot, instead
361             # we use '0'. We need to return a defined value to let the stack know that
362             # the task has ended.
363 232         529 my $slot = 0;
364 232   66     2149 while($self->{+MAX} && $self->is_local) {
365 220         699 $self->cull;
366 220         1012 for my $s (1 .. $self->{+MAX}) {
367 302         1439 my $st = $self->{+SLOTS}->[$s];
368 302 100 100     1781 next if $st && !$st->finished;
369 214         771 $self->{+SLOTS}->[$s] = undef;
370 214         347 $slot = $s;
371 214         1366 last;
372             }
373 220 100       3742 last if $slot;
374 6         121035 sleep(0.02);
375             }
376              
377             my $st = $state->{subtest}
378 232 50       862 or confess "Cannot isolate a task without a subtest";
379              
380 232 100       1763 if (!$self->no_fork) {
    50          
381 158         1714 my $out = $st->fork;
382 158 100       3150 if (blessed($out)) {
383 36         413 $state->{in_fork} = $out;
384              
385             # drop back out to complete the task.
386 36         898 return undef;
387             }
388             else {
389             $self->send_event(
390             'Note',
391             message => "Forked PID $out to run: " . $state->{task}->name,
392 122         6227 );
393 122         70362 $state->{pid} = $out;
394             }
395             }
396             elsif (!$self->no_threads) {
397 0         0 $state->{in_thread} = 1;
398 0         0 my $thr = $st->run_thread(\&run, $self);
399 0         0 $state->{thread} = $thr;
400 0         0 delete $state->{in_thread};
401             $self->send_event(
402             'Note',
403             message => "Started Thread-ID " . $thr->tid . " to run: " . $state->{task}->name,
404 0         0 );
405             }
406             else {
407 74         741 $st->finish(skip => "No isolation method available");
408 74         596 return 0;
409             }
410              
411 122 100       1417 if($slot) {
412 110         681 $self->{+SLOTS}->[$slot] = $st;
413             }
414             else {
415 12         482 $st->finish;
416             }
417              
418 122         1391 return $slot;
419             }
420              
421             sub cull {
422 8401     8401 0 12639 my $self = shift;
423              
424 8401   50     23657 my $subtests = delete $self->{+SUBTESTS} || return;
425 8401         12662 my @new;
426              
427             # Cull subtests in reverse order, Nested subtests end before their parents.
428 8401         16517 for my $set (reverse @$subtests) {
429 20566         34857 my ($st, $task) = @$set;
430 20566 50       40156 next if $st->finished;
431 20566 100 100     81035 if (!$st->active && $st->ready) {
432 466         5749 $st->finish();
433 466         4163 next;
434             }
435              
436             # Use unshift to preserve order.
437 20100         90829 unshift @new => $set;
438             }
439              
440 8401         19129 $self->{+SUBTESTS} = \@new;
441              
442 8401         19393 return;
443             }
444              
445             sub finish {
446 41     41 0 164 my $self = shift;
447 41         158 while(@{$self->{+SUBTESTS}}) {
  1073         11817  
448 1032         5339 $self->cull;
449 1032 100       3890 sleep(0.02) if @{$self->{+SUBTESTS}};
  1032         20286981  
450             }
451             }
452              
453             1;
454              
455             __END__