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   319 use strict;
  45         133  
  45         1365  
3 45     45   258 use warnings;
  45         101  
  45         1713  
4              
5             our $VERSION = '0.000153';
6              
7 45     45   247 use Test2::API();
  45         101  
  45         567  
8 45     45   17789 use Test2::Todo();
  45         118  
  45         931  
9 45     45   19789 use Test2::AsyncSubtest();
  45         129  
  45         1230  
10              
11 45     45   341 use Test2::Util qw/get_tid CAN_REALLY_FORK/;
  45         126  
  45         2508  
12              
13 45     45   325 use Scalar::Util qw/blessed/;
  45         106  
  45         1920  
14 45     45   331 use Time::HiRes qw/sleep/;
  45         145  
  45         427  
15 45     45   6497 use List::Util qw/shuffle min/;
  45         112  
  45         2502  
16 45     45   272 use Carp qw/confess/;
  45         536  
  45         2202  
17              
18 45         223 use Test2::Util::HashBase qw{
19             stack no_fork no_threads max slots pid tid rand subtests filter
20 45     45   294 };
  45         118  
21              
22             use overload(
23             'fallback' => 1,
24             '&{}' => sub {
25 362     362   8330 my $self = shift;
26              
27             sub {
28 362     362   1050 @_ = ($self);
29 362         1897 goto &run;
30             }
31 362         2058 },
32 45     45   24589 );
  45         97  
  45         427  
33              
34             sub init {
35 77     77 0 17335 my $self = shift;
36              
37 77         1683 $self->{+STACK} = [];
38 77         251 $self->{+SUBTESTS} = [];
39              
40 77         565 $self->{+PID} = $$;
41 77         243 $self->{+TID} = get_tid();
42              
43 77   33     802 $self->{+NO_FORK} ||= $ENV{T2_WORKFLOW_NO_FORK} || !CAN_REALLY_FORK();
      66        
44              
45 77         3724 my $can_thread = Test2::AsyncSubtest->CAN_REALLY_THREAD();
46 77   33     647 my $should_thread = ($ENV{T2_WORKFLOW_USE_THREADS} || $ENV{T2_DO_THREAD_TESTS}) && !$ENV{T2_WORKFLOW_NO_THREADS};
47 77   33     579 $self->{+NO_THREADS} ||= !($can_thread && $should_thread);
      66        
48              
49 77 100       406 $self->{+RAND} = 1 unless defined $self->{+RAND};
50              
51 77         415 my @max = grep {defined $_} $self->{+MAX}, $ENV{T2_WORKFLOW_ASYNC};
  154         471  
52 77 50       271 my $max = @max ? min(@max) : 3;
53 77         197 $self->{+MAX} = $max;
54 77 50       311 $self->{+SLOTS} = [] if $max;
55              
56 77 100       248 unless(defined($self->{+FILTER})) {
57 68 50       254 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       362 if (my $task = delete $self->{task}) {
78 33         136 $self->push_task($task);
79             }
80             }
81              
82             sub is_local {
83 470     470 0 1101 my $self = shift;
84 470 100       2013 return 0 unless $self->{+PID} == $$;
85 434 50       1074 return 0 unless $self->{+TID} == get_tid();
86 434         1564 return 1;
87             }
88              
89             sub send_event {
90 272     272 0 8464 my $self = shift;
91 272         4743 my ($type, %params) = @_;
92              
93 272         920 my $class;
94 272 50       3229 if ($type =~ m/\+(.*)$/) {
95 0         0 $class = $1;
96             }
97             else {
98 272         1120 $class = "Test2::Event::$type";
99             }
100              
101 272         3423 my $hub = Test2::API::test2_stack()->top();
102              
103 272         23175 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 272         52042 $hub->send($e);
118             }
119              
120             sub current_subtest {
121 1130     1130 0 1736 my $self = shift;
122 1130 50       2740 my $stack = $self->{+STACK} or return undef;
123              
124 1130         2665 for my $state (reverse @$stack) {
125 2226 100       4704 next unless $state->{subtest};
126 1061         2629 return $state->{subtest};
127             }
128              
129 69         393 return undef;
130             }
131              
132             sub run {
133 433     433 0 1695 my $self = shift;
134              
135 433         1643 my $stack = $self->stack;
136              
137 433         2019 my $c = 0;
138 433         1253 while (@$stack) {
139 7058         30327 $self->cull;
140              
141 7058         14796 my $state = $stack->[-1];
142 7058         11680 my $task = $state->{task};
143              
144 7058 100       16984 unless($state->{started}++) {
145 1898         7112 my $skip = $task->skip;
146              
147 1898         6844 my $filter;
148 1898 100       4998 if (my $f = $self->{+FILTER}) {
149 649         1252 my $in_var = grep { $_->{filter_satisfied} } @$stack;
  3115         5707  
150              
151 649 100       1859 $filter = $task->filter($f) unless $in_var;
152 649 100       1658 $state->{filter_satisfied} = 1 if $filter->{satisfied};
153             }
154              
155 1898 100 66     5651 $skip ||= $filter->{skip} if $filter;
156              
157 1898 100       3850 if ($skip) {
158 150         378 $state->{ended}++;
159 150   33     658 $self->send_event(
160             'Skip',
161             reason => $skip || $filter,
162             name => $task->name,
163             pass => 1,
164             effective_pass => 1,
165             );
166 150         13792 pop @$stack;
167 150         678 next;
168             }
169              
170 1748 100       4632 if ($task->flat) {
171 1130         5582 my $st = $self->current_subtest;
172 1130 100       3587 my $hub = $st ? $st->hub : Test2::API::test2_stack->top;
173              
174 1130 50       5538 $state->{todo} = Test2::Todo->new(reason => $task->todo, hub => $hub)
175             if $task->todo;
176              
177 1130         4303 $hub->send($_) for @{$task->events};
  1130         2646  
178             }
179             else {
180 618         3187 my $st = Test2::AsyncSubtest->new(
181             name => $task->name,
182             frame => $task->frame,
183             );
184 618         14903 $state->{subtest} = $st;
185              
186 618 100       2431 $state->{todo} = Test2::Todo->new(reason => $task->todo, hub => $st->hub)
187             if $task->todo;
188              
189 618         4366 for my $e (@{$task->events}) {
  618         2103  
190 3         155 my $hub = $st->hub;
191              
192 3         25 $e->trace->{buffered} = $hub->buffered;
193 3         90 $e->trace->{nested} = $hub->nested;
194 3         58 $e->trace->{hid} = $hub->hid;
195 3         85 $e->trace->{huuid} = $hub->uuid;
196              
197 3         65 $hub->send($e);
198             }
199              
200 618         5525 my $slot = $self->isolate($state);
201              
202             # if we forked/threaded then this state has ended here.
203 618 100       2979 if (defined($slot)) {
204 196 100       2895 push @{$self->{+SUBTESTS}} => [$st, $task] unless $st->finished;
  110         2253  
205 196         1168 $state->{subtest} = undef;
206 196         1409 $state->{ended} = 1;
207             }
208             }
209             }
210              
211 6908 100       22255 if ($state->{ended}) {
212 1694 100       4241 $state->{todo}->end() if $state->{todo};
213 1694 100       5745 $state->{subtest}->stop() if $state->{subtest};
214              
215 1694 50       9701 return if $state->{in_thread};
216 1694 100       6045 if(my $guard = delete $state->{in_fork}) {
217 30         762 $state->{subtest}->detach;
218 30         386 $guard->dismiss;
219 30         2277 exit 0;
220             }
221              
222 1664         3815 pop @$stack;
223 1664         11200 next;
224             }
225              
226 5214 100 100     20070 if($state->{subtest} && !$state->{subtest_started}++) {
227 422         791 push @{$self->{+SUBTESTS}} => [$state->{subtest}, $task];
  422         16798  
228 422         2286 $state->{subtest}->start();
229             }
230              
231 5214 100       37904 if ($task->isa('Test2::Workflow::Task::Action')) {
232 1108         3476 $state->{PID} = $$;
233 1108         1929 my $ok = eval { $task->code->($self); 1 };
  1108         4026  
  1108         32080  
234              
235 1108 50       3768 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 1108 50       2563 $task->exception($@) unless $ok;
241 1108         2452 $state->{ended} = 1;
242              
243 1108         3531 next;
244             }
245              
246 4106 100 66     21016 if (!$state->{stage} || $state->{stage} eq 'BEFORE') {
    100          
    100          
    50          
247 1189 100       3229 $state->{before} = (defined $state->{before}) ? $state->{before} : 0;
248              
249 1189 100       3351 if (my $add = $task->before->[$state->{before}++]) {
250 745 100       5006 if ($add->around) {
251 362         1812 $state->{PID} = $$;
252 362         747 my $ok = eval { $add->code->($self); 1 };
  362         1219  
  354         7191  
253 354         1065 my $err = $@;
254 354   33     2111 my $complete = $state->{stage} && $state->{stage} eq 'AFTER';
255              
256 354 50       1187 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     2181 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 383         1805 $self->push_task($add);
269             }
270             }
271             else {
272 444         3947 $state->{stage} = 'VARIANT';
273             }
274             }
275             elsif ($state->{stage} eq 'VARIANT') {
276 444 100       1512 if (my $v = $task->variant) {
277 105         713 $self->push_task($v);
278             }
279 444         2817 $state->{stage} = 'PRIMARY';
280             }
281             elsif ($state->{stage} eq 'PRIMARY') {
282 1373 100       3784 unless (defined $state->{order}) {
283 444 50       1507 my $rand = defined($task->rand) ? $task->rand : $self->rand;
284 444         3701 $state->{order} = [0 .. scalar(@{$task->primary}) - 1];
  444         1117  
285 444 100       3461 @{$state->{order}} = shuffle(@{$state->{order}})
  256         670  
  256         1132  
286             if $rand;
287             }
288 1373         2027 my $num = shift @{$state->{order}};
  1373         3593  
289 1373 100       3001 if (defined $num) {
290 983         4234 $self->push_task($task->primary->[$num]);
291             }
292             else {
293 390         1798 $state->{stage} = 'AFTER';
294             }
295             }
296             elsif ($state->{stage} eq 'AFTER') {
297 1100 100       3648 $state->{after} = (defined $state->{after}) ? $state->{after} : 0;
298 1100 100       3532 if (my $add = $task->after->[$state->{after}++]) {
299 710 100       4399 return if $add->around;
300 356         1721 $self->push_task($add);
301             }
302             else {
303 390         2898 $state->{ended} = 1;
304             }
305             }
306             }
307              
308 41         405 $self->finish;
309             }
310              
311             sub push_task {
312 1898     1898 0 6670 my $self = shift;
313 1898         3950 my ($task) = @_;
314              
315 1898 50       4764 confess "No Task!" unless $task;
316 1898 50 33     14556 confess "Bad Task ($task)!" unless blessed($task) && $task->isa('Test2::Workflow::Task');
317              
318 1898 100       11072 if ($task->isa('Test2::Workflow::Build')) {
319             confess "Can only push a Build instance when initializing the stack"
320 33 50       96 if @{$self->{+STACK}};
  33         184  
321 33         275 $task = $task->compile();
322             }
323              
324 1898         3239 push @{$self->{+STACK}} => {
  1898         8374  
325             task => $task,
326             name => $task->name,
327             };
328             }
329              
330             sub add_mock {
331 21     21 0 52 my $self = shift;
332 21         75 my ($mock) = @_;
333 21         91 my $stack = $self->{+STACK};
334              
335 21 50 33     169 confess "Nothing on the stack!"
336             unless $stack && @$stack;
337              
338 21         79 my ($state) = grep { !$_->{task}->scaffold} reverse @$stack;
  92         493  
339 21         100 push @{$state->{mocks}} => $mock;
  21         140  
340             }
341              
342             sub isolate {
343 618     618 0 1208 my $self = shift;
344 618         1259 my ($state) = @_;
345              
346 618 50       1829 return if $state->{task}->skip;
347              
348 618         3910 my $iso = $state->{task}->iso;
349 618         3583 my $async = $state->{task}->async;
350              
351             # No need to isolate
352 618 100 66     4013 return undef unless $iso || $async;
353              
354             # Cannot isolate
355 232 100 66     2112 unless($self->{+MAX} && $self->is_local) {
356             # async does not NEED to be isolated
357 18 50       177 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         533 my $slot = 0;
364 232   66     1997 while($self->{+MAX} && $self->is_local) {
365 220         767 $self->cull;
366 220         1685 for my $s (1 .. $self->{+MAX}) {
367 304         1428 my $st = $self->{+SLOTS}->[$s];
368 304 100 100     1919 next if $st && !$st->finished;
369 214         786 $self->{+SLOTS}->[$s] = undef;
370 214         443 $slot = $s;
371 214         19803 last;
372             }
373 220 100       3997 last if $slot;
374 6         121086 sleep(0.02);
375             }
376              
377             my $st = $state->{subtest}
378 232 50       847 or confess "Cannot isolate a task without a subtest";
379              
380 232 100       1714 if (!$self->no_fork) {
    50          
381 158         1692 my $out = $st->fork;
382 158 100       3162 if (blessed($out)) {
383 36         666 $state->{in_fork} = $out;
384              
385             # drop back out to complete the task.
386 36         15407 return undef;
387             }
388             else {
389             $self->send_event(
390             'Note',
391             message => "Forked PID $out to run: " . $state->{task}->name,
392 122         6038 );
393 122         78948 $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         787 $st->finish(skip => "No isolation method available");
408 74         739 return 0;
409             }
410              
411 122 100       1300 if($slot) {
412 110         799 $self->{+SLOTS}->[$slot] = $st;
413             }
414             else {
415 12         481 $st->finish;
416             }
417              
418 122         1675 return $slot;
419             }
420              
421             sub cull {
422 8554     8554 0 13813 my $self = shift;
423              
424 8554   50     26191 my $subtests = delete $self->{+SUBTESTS} || return;
425 8554         13417 my @new;
426              
427             # Cull subtests in reverse order, Nested subtests end before their parents.
428 8554         18574 for my $set (reverse @$subtests) {
429 20637         40021 my ($st, $task) = @$set;
430 20637 50       47621 next if $st->finished;
431 20637 100 100     89819 if (!$st->active && $st->ready) {
432 464         6182 $st->finish();
433 464         4585 next;
434             }
435              
436             # Use unshift to preserve order.
437 20173         103407 unshift @new => $set;
438             }
439              
440 8554         20571 $self->{+SUBTESTS} = \@new;
441              
442 8554         41425 return;
443             }
444              
445             sub finish {
446 41     41 0 170 my $self = shift;
447 41         177 while(@{$self->{+SUBTESTS}}) {
  1317         14891  
448 1276         6717 $self->cull;
449 1276 100       4630 sleep(0.02) if @{$self->{+SUBTESTS}};
  1276         25237659  
450             }
451             }
452              
453             1;
454              
455             __END__