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   89227 use strict;
  44         53  
  44         935  
3 44     44   130 use warnings;
  44         136  
  44         778  
4              
5 44     44   103 use Test2::API();
  44         75  
  44         570  
6 44     44   15481 use Test2::Todo();
  44         87086  
  44         606  
7 44     44   17138 use Test2::AsyncSubtest();
  44         322867  
  44         850  
8              
9 44     44   228 use Test2::Util qw/get_tid CAN_REALLY_FORK/;
  44         56  
  44         1878  
10              
11 44     44   154 use Scalar::Util qw/blessed/;
  44         53  
  44         1510  
12 44     44   142 use Time::HiRes qw/sleep/;
  44         54  
  44         196  
13 44     44   5763 use List::Util qw/shuffle min/;
  44         55  
  44         1902  
14 44     44   158 use Carp qw/confess/;
  44         53  
  44         1604  
15              
16 44         146 use Test2::Util::HashBase qw{
17             stack no_fork no_threads max slots pid tid rand subtests filter
18 44     44   157 };
  44         44  
19              
20             use overload(
21             'fallback' => 1,
22             '&{}' => sub {
23 362     362   83610 my $self = shift;
24              
25             sub {
26 362     362   623 @_ = ($self);
27 362         1099 goto &run;
28             }
29 362         1405 },
30 44     44   9506 );
  44         55  
  44         266  
31              
32             sub init {
33 76     76 0 12138 my $self = shift;
34              
35 76         1158 $self->{+STACK} = [];
36 76         126 $self->{+SUBTESTS} = [];
37              
38 76         321 $self->{+PID} = $$;
39 76         232 $self->{+TID} = get_tid();
40              
41 76   33     551 $self->{+NO_FORK} ||= $ENV{T2_WORKFLOW_NO_FORK} || !CAN_REALLY_FORK();
      66        
42 76   33     2811 $self->{+NO_THREADS} ||= $ENV{T2_WORKFLOW_NO_THREADS} || !Test2::AsyncSubtest->CAN_REALLY_THREAD();
      66        
43              
44 76 100       540 $self->{+RAND} = 1 unless defined $self->{+RAND};
45              
46 76         242 my @max = grep {defined $_} $self->{+MAX}, $ENV{T2_WORKFLOW_ASYNC};
  152         313  
47 76 50       175 my $max = @max ? min(@max) : 3;
48 76         101 $self->{+MAX} = $max;
49 76 50       208 $self->{+SLOTS} = [] if $max;
50              
51 76 100       281 unless(defined($self->{+FILTER})) {
52 67 50       208 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       221 if (my $task = delete $self->{task}) {
73 33         94 $self->push_task($task);
74             }
75             }
76              
77             sub is_local {
78 440     440 0 404 my $self = shift;
79 440 100       1053 return 0 unless $self->{+PID} == $$;
80 404 50       642 return 0 unless $self->{+TID} == get_tid();
81 404         1154 return 1;
82             }
83              
84             sub send_event {
85 259     259 0 3299 my $self = shift;
86 259         2117 my ($type, %params) = @_;
87              
88 259         355 my $class;
89 259 50       1365 if ($type =~ m/\+(.*)$/) {
90 0         0 $class = $1;
91             }
92             else {
93 259         520 $class = "Test2::Event::$type";
94             }
95              
96 259         6865 my $e = $class->new(
97             trace => Test2::Util::Trace->new(frame => [caller(0)]),
98             %params,
99             );
100              
101 259         14796 Test2::API::test2_stack()->top()->send($e);
102             }
103              
104             sub current_subtest {
105 1117     1117 0 896 my $self = shift;
106 1117 50       1756 my $stack = $self->{+STACK} or return undef;
107              
108 1117         1073 for my $state (reverse @$stack) {
109 2201 100       3223 next unless $state->{subtest};
110 1049         1400 return $state->{subtest};
111             }
112              
113 68         91 return undef;
114             }
115              
116             sub run {
117 432     432 0 933 my $self = shift;
118              
119 432         899 my $stack = $self->stack;
120              
121 432         1055 my $c = 0;
122 432         864 while (@$stack) {
123 6991         17961 $self->cull;
124              
125 6991         5807 my $state = $stack->[-1];
126 6991         5884 my $task = $state->{task};
127              
128 6991 100       10365 unless($state->{started}++) {
129 1874         3785 my $skip = $task->skip;
130              
131 1874         3483 my $filter;
132 1874 100       3090 if (my $f = $self->{+FILTER}) {
133 649         694 my $in_var = grep { $_->{filter_satisfied} } @$stack;
  3115         2789  
134              
135 649 100       1062 $filter = $task->filter($f) unless $in_var;
136 649 100       1148 $state->{filter_satisfied} = 1 if $filter->{satisfied};
137             }
138              
139 1874 100 66     3390 $skip ||= $filter->{skip} if $filter;
140              
141 1874 100       2214 if ($skip) {
142 149         254 $state->{ended}++;
143 149   33     459 $self->send_event(
144             'Skip',
145             reason => $skip || $filter,
146             name => $task->name,
147             pass => 1,
148             effective_pass => 1,
149             );
150 149         10083 pop @$stack;
151 149         475 next;
152             }
153              
154 1725 100       2835 if ($task->flat) {
155 1117         3243 my $st = $self->current_subtest;
156 1117 100       3267 my $hub = $st ? $st->hub : Test2::API::test2_stack->top;
157              
158 1117 50       3434 $state->{todo} = Test2::Todo->new(reason => $task->todo, hub => $hub)
159             if $task->todo;
160              
161 1117         3195 $hub->send($_) for @{$task->events};
  1117         1588  
162             }
163             else {
164 608         2056 my $st = Test2::AsyncSubtest->new(
165             name => $task->name,
166             trace => Test2::Util::Trace->new(frame => $task->frame),
167             );
168 608         158905 $state->{subtest} = $st;
169              
170 608 100       1459 $state->{todo} = Test2::Todo->new(reason => $task->todo, hub => $st->hub)
171             if $task->todo;
172              
173 608         3784 $st->hub->send($_) for @{$task->events};
  608         1249  
174              
175 608         3504 my $slot = $self->isolate($state);
176              
177             # if we forked/threaded then this state has ended here.
178 608 100       1482 if (defined($slot)) {
179 184 100       1551 push @{$self->{+SUBTESTS}} => [$st, $task] unless $st->finished;
  98         1002  
180 184         599 $state->{subtest} = undef;
181 184         750 $state->{ended} = 1;
182             }
183             }
184             }
185              
186 6842 100       13966 if ($state->{ended}) {
187 1671 100       2792 $state->{todo}->end() if $state->{todo};
188 1671 100       4854 $state->{subtest}->stop() if $state->{subtest};
189              
190 1671 50       11814 return if $state->{in_thread};
191 1671 100       3168 if(my $guard = delete $state->{in_fork}) {
192 30         355 $state->{subtest}->detach;
193 30         11871 $guard->dismiss;
194 30         1454 exit 0;
195             }
196              
197 1641         1984 pop @$stack;
198 1641         6071 next;
199             }
200              
201 5171 100 100     15047 if($state->{subtest} && !$state->{subtest_started}++) {
202 424         404 push @{$self->{+SUBTESTS}} => [$state->{subtest}, $task];
  424         1237  
203 424         1207 $state->{subtest}->start();
204             }
205              
206 5171 100       33198 if ($task->isa('Test2::Workflow::Task::Action')) {
207 1101         2010 $state->{PID} = $$;
208 1101         1105 my $ok = eval { $task->code->($self); 1 };
  1101         2121  
  1101         583336  
209              
210 1101 50       2903 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 1101 50       1658 $task->exception($@) unless $ok;
216 1101         1300 $state->{ended} = 1;
217              
218 1101         2404 next;
219             }
220              
221 4070 100 66     15551 if (!$state->{stage} || $state->{stage} eq 'BEFORE') {
    100          
    100          
    50          
222 1180   100     2630 $state->{before} //= 0;
223 1180 100       2068 if (my $add = $task->before->[$state->{before}++]) {
224 740 100       2808 if ($add->around) {
225 362         1158 $state->{PID} = $$;
226 362         349 my $ok = eval { $add->code->($self); 1 };
  362         569  
  354         76223  
227 354         449 my $err = $@;
228 354   33     1411 my $complete = $state->{stage} && $state->{stage} eq 'AFTER';
229              
230 354 50       703 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     1575 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 378         1198 $self->push_task($add);
243             }
244             }
245             else {
246 440         2487 $state->{stage} = 'VARIANT';
247             }
248             }
249             elsif ($state->{stage} eq 'VARIANT') {
250 440 100       887 if (my $v = $task->variant) {
251 102         409 $self->push_task($v);
252             }
253 440         1882 $state->{stage} = 'PRIMARY';
254             }
255             elsif ($state->{stage} eq 'PRIMARY') {
256 1354 100       2159 unless (defined $state->{order}) {
257 440 50       781 my $rand = defined($task->rand) ? $task->rand : $self->rand;
258 440         2275 $state->{order} = [0 .. scalar(@{$task->primary}) - 1];
  440         759  
259 440 100       2064 @{$state->{order}} = shuffle(@{$state->{order}})
  252         426  
  252         628  
260             if $rand;
261             }
262 1354         1030 my $num = shift @{$state->{order}};
  1354         2012  
263 1354 100       1804 if (defined $num) {
264 968         2421 $self->push_task($task->primary->[$num]);
265             }
266             else {
267 386         836 $state->{stage} = 'AFTER';
268             }
269             }
270             elsif ($state->{stage} eq 'AFTER') {
271 1096   100     2296 $state->{after} //= 0;
272 1096 100       1933 if (my $add = $task->after->[$state->{after}++]) {
273 710 100       2634 return if $add->around;
274 356         1174 $self->push_task($add);
275             }
276             else {
277 386         1680 $state->{ended} = 1;
278             }
279             }
280             }
281              
282 40         187 $self->finish;
283             }
284              
285             sub push_task {
286 1874     1874 0 3249 my $self = shift;
287 1874         1825 my ($task) = @_;
288              
289 1874 50       2876 confess "No Task!" unless $task;
290 1874 50 33     10190 confess "Bad Task ($task)!" unless blessed($task) && $task->isa('Test2::Workflow::Task');
291              
292 1874 100       6507 if ($task->isa('Test2::Workflow::Build')) {
293             confess "Can only push a Build instance when initializing the stack"
294 33 50       36 if @{$self->{+STACK}};
  33         129  
295 33         154 $task = $task->compile();
296             }
297              
298 1874         1373 push @{$self->{+STACK}} => {
  1874         5624  
299             task => $task,
300             name => $task->name,
301             };
302             }
303              
304             sub add_mock {
305 17     17 0 3096 my $self = shift;
306 17         21 my ($mock) = @_;
307 17         28 my $stack = $self->{+STACK};
308              
309 17 50 33     104 confess "Nothing on the stack!"
310             unless $stack && @$stack;
311              
312 17         31 my ($state) = grep { !$_->{task}->scaffold} reverse @$stack;
  76         330  
313 17         50 push @{$state->{mocks}} => $mock;
  17         63  
314             }
315              
316             sub isolate {
317 608     608 0 658 my $self = shift;
318 608         727 my ($state) = @_;
319              
320 608 50       1099 return if $state->{task}->skip;
321              
322 608         2206 my $iso = $state->{task}->iso;
323 608         1941 my $async = $state->{task}->async;
324              
325             # No need to isolate
326 608 100 66     2830 return undef unless $iso || $async;
327              
328             # Cannot isolate
329 220 100 66     1225 unless($self->{+MAX} && $self->is_local) {
330             # async does not NEED to be isolated
331 18 50       94 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         287 my $slot = 0;
338 220   66     852 while($self->{+MAX} && $self->is_local) {
339 202         362 $self->cull;
340 202         630 for my $s (1 .. $self->{+MAX}) {
341 264         600 my $st = $self->{+SLOTS}->[$s];
342 264 100 100     976 next if $st && !$st->finished;
343 202         431 $self->{+SLOTS}->[$s] = undef;
344 202         213 $slot = $s;
345 202         272 last;
346             }
347 202 50       376 last if $slot;
348 0         0 sleep(0.02);
349             }
350              
351             my $st = $state->{subtest}
352 220 50       484 or confess "Cannot isolate a task without a subtest";
353              
354 220 100       715 if (!$self->no_fork) {
    50          
355 146         824 my $out = $st->fork;
356 146 100       163119 if (blessed($out)) {
357 36         540 $state->{in_fork} = $out;
358              
359             # drop back out to complete the task.
360 36         517 return undef;
361             }
362             else {
363             $self->send_event(
364             'Note',
365             message => "Forked PID $out to run: " . $state->{task}->name,
366 110         2931 );
367 110         34715 $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         635 $st->finish(skip => "No isolation method available");
382 74         41595 return 0;
383             }
384              
385 110 100       596 if($slot) {
386 98         323 $self->{+SLOTS}->[$slot] = $st;
387             }
388             else {
389 12         179 $st->finish;
390             }
391              
392 110         9368656 return $slot;
393             }
394              
395             sub cull {
396 7353     7353 0 5349 my $self = shift;
397              
398 7353   50     13730 my $subtests = delete $self->{+SUBTESTS} || return;
399 7353         5746 my @new;
400              
401             # Cull subtests in reverse order, Nested subtests end before their parents.
402 7353         8017 for my $set (reverse @$subtests) {
403 18572         17539 my ($st, $task) = @$set;
404 18572 50       25699 next if $st->finished;
405 18572 100 100     50697 if (!$st->active && $st->ready) {
406 454         102493 $st->finish();
407 454         35504828 next;
408             }
409              
410             # Use unshift to preserve order.
411 18118         210724 unshift @new => $set;
412             }
413              
414 7353         8750 $self->{+SUBTESTS} = \@new;
415              
416 7353         9683 return;
417             }
418              
419             sub finish {
420 40     40 0 86 my $self = shift;
421 40         72 while(@{$self->{+SUBTESTS}}) {
  200         3334  
422 160         1492 $self->cull;
423 160 100       221 sleep(0.02) if @{$self->{+SUBTESTS}};
  160         2718383  
424             }
425             }
426              
427             1;
428              
429             __END__