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   328 use strict;
  45         107  
  45         1306  
3 45     45   224 use warnings;
  45         94  
  45         1755  
4              
5             our $VERSION = '0.000155';
6              
7 45     45   234 use Test2::API();
  45         88  
  45         634  
8 45     45   17960 use Test2::Todo();
  45         141  
  45         927  
9 45     45   20688 use Test2::AsyncSubtest();
  45         127  
  45         1197  
10              
11 45     45   314 use Test2::Util qw/get_tid CAN_REALLY_FORK/;
  45         106  
  45         2453  
12              
13 45     45   283 use Scalar::Util qw/blessed/;
  45         83  
  45         1916  
14 45     45   256 use Time::HiRes qw/sleep/;
  45         150  
  45         580  
15 45     45   6432 use List::Util qw/shuffle min/;
  45         184  
  45         2519  
16 45     45   271 use Carp qw/confess/;
  45         110  
  45         2179  
17              
18 45         264 use Test2::Util::HashBase qw{
19             stack no_fork no_threads max slots pid tid rand subtests filter
20 45     45   268 };
  45         111  
21              
22             use overload(
23             'fallback' => 1,
24             '&{}' => sub {
25 362     362   8066 my $self = shift;
26              
27             sub {
28 362     362   990 @_ = ($self);
29 362         1726 goto &run;
30             }
31 362         2379 },
32 45     45   26091 );
  45         170  
  45         364  
33              
34             sub init {
35 77     77 0 16153 my $self = shift;
36              
37 77         2053 $self->{+STACK} = [];
38 77         288 $self->{+SUBTESTS} = [];
39              
40 77         526 $self->{+PID} = $$;
41 77         235 $self->{+TID} = get_tid();
42              
43 77   33     783 $self->{+NO_FORK} ||= $ENV{T2_WORKFLOW_NO_FORK} || !CAN_REALLY_FORK();
      66        
44              
45 77         3763 my $can_thread = Test2::AsyncSubtest->CAN_REALLY_THREAD();
46 77   33     656 my $should_thread = ($ENV{T2_WORKFLOW_USE_THREADS} || $ENV{T2_DO_THREAD_TESTS}) && !$ENV{T2_WORKFLOW_NO_THREADS};
47 77   33     538 $self->{+NO_THREADS} ||= !($can_thread && $should_thread);
      66        
48              
49 77 100       344 $self->{+RAND} = 1 unless defined $self->{+RAND};
50              
51 77         433 my @max = grep {defined $_} $self->{+MAX}, $ENV{T2_WORKFLOW_ASYNC};
  154         453  
52 77 50       329 my $max = @max ? min(@max) : 3;
53 77         187 $self->{+MAX} = $max;
54 77 50       381 $self->{+SLOTS} = [] if $max;
55              
56 77 100       266 unless(defined($self->{+FILTER})) {
57 68 50       482 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       345 if (my $task = delete $self->{task}) {
78 33         151 $self->push_task($task);
79             }
80             }
81              
82             sub is_local {
83 470     470 0 1053 my $self = shift;
84 470 100       1798 return 0 unless $self->{+PID} == $$;
85 434 50       1226 return 0 unless $self->{+TID} == get_tid();
86 434         1828 return 1;
87             }
88              
89             sub send_event {
90 273     273 0 9424 my $self = shift;
91 273         5477 my ($type, %params) = @_;
92              
93 273         835 my $class;
94 273 50       3416 if ($type =~ m/\+(.*)$/) {
95 0         0 $class = $1;
96             }
97             else {
98 273         1115 $class = "Test2::Event::$type";
99             }
100              
101 273         3699 my $hub = Test2::API::test2_stack()->top();
102              
103 273         26240 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 273         55881 $hub->send($e);
118             }
119              
120             sub current_subtest {
121 1114     1114 0 1847 my $self = shift;
122 1114 50       2631 my $stack = $self->{+STACK} or return undef;
123              
124 1114         2536 for my $state (reverse @$stack) {
125 2194 100       4874 next unless $state->{subtest};
126 1045         2438 return $state->{subtest};
127             }
128              
129 69         217 return undef;
130             }
131              
132             sub run {
133 433     433 0 1335 my $self = shift;
134              
135 433         1593 my $stack = $self->stack;
136              
137 433         1872 my $c = 0;
138 433         1347 while (@$stack) {
139 6988         30328 $self->cull;
140              
141 6988         16064 my $state = $stack->[-1];
142 6988         11364 my $task = $state->{task};
143              
144 6988 100       16422 unless($state->{started}++) {
145 1883         6777 my $skip = $task->skip;
146              
147 1883         7056 my $filter;
148 1883 100       4857 if (my $f = $self->{+FILTER}) {
149 649         1282 my $in_var = grep { $_->{filter_satisfied} } @$stack;
  3115         5303  
150              
151 649 100       1766 $filter = $task->filter($f) unless $in_var;
152 649 100       1595 $state->{filter_satisfied} = 1 if $filter->{satisfied};
153             }
154              
155 1883 100 66     5908 $skip ||= $filter->{skip} if $filter;
156              
157 1883 100       3783 if ($skip) {
158 151         408 $state->{ended}++;
159 151   33     782 $self->send_event(
160             'Skip',
161             reason => $skip || $filter,
162             name => $task->name,
163             pass => 1,
164             effective_pass => 1,
165             );
166 151         11839 pop @$stack;
167 151         794 next;
168             }
169              
170 1732 100       4427 if ($task->flat) {
171 1114         5317 my $st = $self->current_subtest;
172 1114 100       3673 my $hub = $st ? $st->hub : Test2::API::test2_stack->top;
173              
174 1114 50       5216 $state->{todo} = Test2::Todo->new(reason => $task->todo, hub => $hub)
175             if $task->todo;
176              
177 1114         4351 $hub->send($_) for @{$task->events};
  1114         2458  
178             }
179             else {
180 618         3496 my $st = Test2::AsyncSubtest->new(
181             name => $task->name,
182             frame => $task->frame,
183             );
184 618         15348 $state->{subtest} = $st;
185              
186 618 100       2616 $state->{todo} = Test2::Todo->new(reason => $task->todo, hub => $st->hub)
187             if $task->todo;
188              
189 618         4700 for my $e (@{$task->events}) {
  618         2228  
190 11         489 my $hub = $st->hub;
191              
192 11         63 $e->trace->{buffered} = $hub->buffered;
193 11         190 $e->trace->{nested} = $hub->nested;
194 11         155 $e->trace->{hid} = $hub->hid;
195 11         75 $e->trace->{huuid} = $hub->uuid;
196              
197 11         134 $hub->send($e);
198             }
199              
200 618         5465 my $slot = $self->isolate($state);
201              
202             # if we forked/threaded then this state has ended here.
203 618 100       3001 if (defined($slot)) {
204 196 100       3144 push @{$self->{+SUBTESTS}} => [$st, $task] unless $st->finished;
  110         2636  
205 196         1395 $state->{subtest} = undef;
206 196         1452 $state->{ended} = 1;
207             }
208             }
209             }
210              
211 6837 100       22614 if ($state->{ended}) {
212 1678 100       4713 $state->{todo}->end() if $state->{todo};
213 1678 100       5245 $state->{subtest}->stop() if $state->{subtest};
214              
215 1678 50       9161 return if $state->{in_thread};
216 1678 100       6545 if(my $guard = delete $state->{in_fork}) {
217 30         670 $state->{subtest}->detach;
218 30         337 $guard->dismiss;
219 30         2465 exit 0;
220             }
221              
222 1648         4190 pop @$stack;
223 1648         11278 next;
224             }
225              
226 5159 100 100     22374 if($state->{subtest} && !$state->{subtest_started}++) {
227 422         882 push @{$self->{+SUBTESTS}} => [$state->{subtest}, $task];
  422         2048  
228 422         38361 $state->{subtest}->start();
229             }
230              
231 5159 100       36795 if ($task->isa('Test2::Workflow::Task::Action')) {
232 1100         3566 $state->{PID} = $$;
233 1100         2065 my $ok = eval { $task->code->($self); 1 };
  1100         3727  
  1100         29011  
234              
235 1100 50       4278 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 1100 50       4254 $task->exception($@) unless $ok;
241 1100         2424 $state->{ended} = 1;
242              
243 1100         3556 next;
244             }
245              
246 4059 100 66     21947 if (!$state->{stage} || $state->{stage} eq 'BEFORE') {
    100          
    100          
    50          
247 1173 100       3536 $state->{before} = (defined $state->{before}) ? $state->{before} : 0;
248              
249 1173 100       3385 if (my $add = $task->before->[$state->{before}++]) {
250 737 100       5132 if ($add->around) {
251 362         1847 $state->{PID} = $$;
252 362         670 my $ok = eval { $add->code->($self); 1 };
  362         996  
  354         7196  
253 354         943 my $err = $@;
254 354   33     1901 my $complete = $state->{stage} && $state->{stage} eq 'AFTER';
255              
256 354 50       1435 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 354 50 33     2097 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 375         1854 $self->push_task($add);
269             }
270             }
271             else {
272 436         3934 $state->{stage} = 'VARIANT';
273             }
274             }
275             elsif ($state->{stage} eq 'VARIANT') {
276 436 100       1428 if (my $v = $task->variant) {
277 105         723 $self->push_task($v);
278             }
279 436         2938 $state->{stage} = 'PRIMARY';
280             }
281             elsif ($state->{stage} eq 'PRIMARY') {
282 1358 100       3556 unless (defined $state->{order}) {
283 436 50       1221 my $rand = defined($task->rand) ? $task->rand : $self->rand;
284 436         3832 $state->{order} = [0 .. scalar(@{$task->primary}) - 1];
  436         1102  
285 436 100       3581 @{$state->{order}} = shuffle(@{$state->{order}})
  248         782  
  248         1493  
286             if $rand;
287             }
288 1358         2029 my $num = shift @{$state->{order}};
  1358         3666  
289 1358 100       2993 if (defined $num) {
290 976         4554 $self->push_task($task->primary->[$num]);
291             }
292             else {
293 382         1392 $state->{stage} = 'AFTER';
294             }
295             }
296             elsif ($state->{stage} eq 'AFTER') {
297 1092 100       3293 $state->{after} = (defined $state->{after}) ? $state->{after} : 0;
298 1092 100       3442 if (my $add = $task->after->[$state->{after}++]) {
299 710 100       4278 return if $add->around;
300 356         1684 $self->push_task($add);
301             }
302             else {
303 382         2708 $state->{ended} = 1;
304             }
305             }
306             }
307              
308 41         544 $self->finish;
309             }
310              
311             sub push_task {
312 1883     1883 0 6898 my $self = shift;
313 1883         4093 my ($task) = @_;
314              
315 1883 50       5107 confess "No Task!" unless $task;
316 1883 50 33     14455 confess "Bad Task ($task)!" unless blessed($task) && $task->isa('Test2::Workflow::Task');
317              
318 1883 100       11143 if ($task->isa('Test2::Workflow::Build')) {
319             confess "Can only push a Build instance when initializing the stack"
320 33 50       101 if @{$self->{+STACK}};
  33         148  
321 33         280 $task = $task->compile();
322             }
323              
324 1883         3557 push @{$self->{+STACK}} => {
  1883         8150  
325             task => $task,
326             name => $task->name,
327             };
328             }
329              
330             sub add_mock {
331 13     13 0 134 my $self = shift;
332 13         127 my ($mock) = @_;
333 13         103 my $stack = $self->{+STACK};
334              
335 13 50 33     253 confess "Nothing on the stack!"
336             unless $stack && @$stack;
337              
338 13         110 my ($state) = grep { !$_->{task}->scaffold} reverse @$stack;
  56         1072  
339 13         195 push @{$state->{mocks}} => $mock;
  13         129  
340             }
341              
342             sub isolate {
343 618     618 0 1118 my $self = shift;
344 618         1404 my ($state) = @_;
345              
346 618 50       1813 return if $state->{task}->skip;
347              
348 618         3492 my $iso = $state->{task}->iso;
349 618         3453 my $async = $state->{task}->async;
350              
351             # No need to isolate
352 618 100 66     4355 return undef unless $iso || $async;
353              
354             # Cannot isolate
355 232 100 66     2136 unless($self->{+MAX} && $self->is_local) {
356             # async does not NEED to be isolated
357 18 50       120 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         739 my $slot = 0;
364 232   66     2009 while($self->{+MAX} && $self->is_local) {
365 220         741 $self->cull;
366 220         1947 for my $s (1 .. $self->{+MAX}) {
367 302         1532 my $st = $self->{+SLOTS}->[$s];
368 302 100 100     1527 next if $st && !$st->finished;
369 214         1017 $self->{+SLOTS}->[$s] = undef;
370 214         378 $slot = $s;
371 214         17746 last;
372             }
373 220 100       5093 last if $slot;
374 6         121386 sleep(0.02);
375             }
376              
377             my $st = $state->{subtest}
378 232 50       879 or confess "Cannot isolate a task without a subtest";
379              
380 232 100       1620 if (!$self->no_fork) {
    50          
381 158         1828 my $out = $st->fork;
382 158 100       3213 if (blessed($out)) {
383 36         476 $state->{in_fork} = $out;
384              
385             # drop back out to complete the task.
386 36         1056 return undef;
387             }
388             else {
389             $self->send_event(
390             'Note',
391             message => "Forked PID $out to run: " . $state->{task}->name,
392 122         6752 );
393 122         71182 $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         845 $st->finish(skip => "No isolation method available");
408 74         818 return 0;
409             }
410              
411 122 100       1136 if($slot) {
412 110         784 $self->{+SLOTS}->[$slot] = $st;
413             }
414             else {
415 12         570 $st->finish;
416             }
417              
418 122         1655 return $slot;
419             }
420              
421             sub cull {
422 8311     8311 0 14450 my $self = shift;
423              
424 8311   50     25680 my $subtests = delete $self->{+SUBTESTS} || return;
425 8311         13603 my @new;
426              
427             # Cull subtests in reverse order, Nested subtests end before their parents.
428 8311         19238 for my $set (reverse @$subtests) {
429 20289         37995 my ($st, $task) = @$set;
430 20289 50       45220 next if $st->finished;
431 20289 100 100     86927 if (!$st->active && $st->ready) {
432 457         5728 $st->finish();
433 457         4572 next;
434             }
435              
436             # Use unshift to preserve order.
437 19832         100964 unshift @new => $set;
438             }
439              
440 8311         21095 $self->{+SUBTESTS} = \@new;
441              
442 8311         20573 return;
443             }
444              
445             sub finish {
446 41     41 0 194 my $self = shift;
447 41         199 while(@{$self->{+SUBTESTS}}) {
  1144         14126  
448 1103         6363 $self->cull;
449 1103 100       4452 sleep(0.02) if @{$self->{+SUBTESTS}};
  1103         21709731  
450             }
451             }
452              
453             1;
454              
455             __END__