File Coverage

blib/lib/Test/Stream/Workflow.pm
Criterion Covered Total %
statement 166 166 100.0
branch 78 80 97.5
condition 39 47 82.9
subroutine 38 38 100.0
pod 15 16 93.7
total 336 347 96.8


line stmt bran cond sub pod time code
1             package Test::Stream::Workflow;
2 95     95   689 use strict;
  95         116  
  95         2120  
3 95     95   288 use warnings;
  95         111  
  95         1967  
4              
5 95     95   287 use Scalar::Util qw/reftype blessed/;
  95         107  
  95         3806  
6 95     95   336 use Carp qw/confess croak/;
  95         126  
  95         3343  
7              
8 95     95   367 use Test::Stream::Sync();
  95         121  
  95         1234  
9              
10 95     95   31938 use Test::Stream::Workflow::Meta();
  95         184  
  95         1396  
11 95     95   421 use Test::Stream::Workflow::Unit();
  95         110  
  95         1442  
12              
13 95     95   285 use Test::Stream::Context qw/context/;
  95         115  
  95         540  
14 95     95   372 use Test::Stream::Util qw/try set_sub_name CAN_SET_SUB_NAME sub_info update_mask/;
  95         132  
  95         434  
15              
16 95     95   374 use Test::Stream::Exporter qw/exports export import/;
  95         133  
  95         514  
17             exports qw{
18             workflow_build
19             workflow_current
20             workflow_meta
21             workflow_runner
22             workflow_runner_args
23             workflow_var
24             workflow_run
25             new_proto_unit
26             group_builder
27             gen_unit_builder
28             push_workflow_build
29             pop_workflow_build
30             push_workflow_vars
31             pop_workflow_vars
32             has_workflow_vars
33             };
34              
35             export import => sub {
36 1         11 my $class = shift;
37             my ($pkg, $file, $line) = caller;
38              
39             Test::Stream::Exporter::export_from($class, $pkg, \@_);
40              
41             # This is a no-op if it has already been done.
42             Test::Stream::Workflow::Meta->build($pkg, $file, $line, 'EOF');
43             };
44              
45             export unimport => sub {
46 5     5   41 my $caller = caller;
47 5         21 my $meta = Test::Stream::Workflow::Meta->get($caller);
48 5         21 $meta->set_autorun(0);
49             };
50 95     95   423 no Test::Stream::Exporter;
  95         130  
  95         391  
51              
52             my $PKG = __PACKAGE__;
53             my %ALLOWED_STASHES = map {$_ => 1} qw{
54             primary
55             modify
56             buildup
57             teardown
58             buildup+teardown
59             };
60              
61             my @BUILD;
62             my @VARS;
63              
64 4     4 1 27 sub workflow_current { _current(caller) }
65 6     6 1 26 sub workflow_meta { Test::Stream::Workflow::Meta->get(scalar caller) }
66 2     2 1 16 sub workflow_run { Test::Stream::Workflow::Meta->get(scalar caller)->run(@_) }
67 1     1 1 4 sub workflow_runner { Test::Stream::Workflow::Meta->get(scalar caller)->set_runner(@_) }
68 1     1 1 3 sub workflow_runner_args { Test::Stream::Workflow::Meta->get(scalar caller)->set_runner_args(@_) }
69              
70 63 100   63 1 189 sub workflow_build { @BUILD ? $BUILD[-1] : undef }
71 34   100 34 1 133 sub push_workflow_build { push @BUILD => $_[0] || die "Nothing to push"; $_[0] }
  33         42  
72              
73             sub pop_workflow_build {
74 36     36 1 61 my ($should_be) = @_;
75              
76 36 100 100     533 croak "Build stack mismatch"
      100        
77             unless @BUILD && $should_be && $BUILD[-1] == $should_be;
78              
79 33         50 pop @BUILD;
80             }
81              
82 81     81 1 402 sub has_workflow_vars { scalar @VARS }
83             sub push_workflow_vars {
84 211   100 211 1 417 my $vars = shift || {};
85 211         247 push @VARS => $vars;
86 211         344 $vars;
87             }
88              
89             sub pop_workflow_vars {
90 213     213 1 220 my ($should_be) = @_;
91              
92 213 100 100     1584 croak "Vars stack mismatch!"
      100        
93             unless @VARS && $should_be && $VARS[-1] == $should_be;
94              
95 210         272 my $it = pop @VARS;
96 210         333 %$it = ();
97 210         310 return;
98             }
99              
100             sub workflow_var {
101 29 100   29 1 281 confess "No VARS! workflow_var() should only be called inside a unit sub"
102             unless @VARS;
103              
104 28         27 my $vars = $VARS[-1];
105              
106 28         34 my $name = shift;
107 28 100       49 if (@_) {
108 16 100 100     73 if (ref $_[0] && reftype($_[0]) eq 'CODE') {
109             $vars->{$name} = $_[0]->()
110 11 100       38 unless defined $vars->{$name};
111             }
112             else {
113 5         13 ($vars->{$name}) = @_;
114             }
115             }
116 28         86 return $vars->{$name};
117             };
118              
119             sub _current {
120 290     290   271 my ($caller) = @_;
121              
122 290 100       581 return $BUILD[-1] if @BUILD;
123 173   100     549 my $spec_meta = Test::Stream::Workflow::Meta->get($caller) || return;
124 170         383 return $spec_meta->unit;
125             }
126              
127             sub die_at_caller {
128 8     8 0 22 my ($caller, $msg) = @_;
129 8         70 die "$msg at $caller->[1] line $caller->[2].\n";
130             }
131              
132             sub new_proto_unit {
133 299     299 1 631 my %params = @_;
134 299 100       657 $params{level} = 1 unless defined $params{level};
135 299   50     2166 my $caller = $params{caller} || [caller($params{level})];
136 299         360 my $args = $params{args};
137 299         267 my $subname = $params{subname};
138              
139 299 50       443 unless ($subname) {
140 299         297 $subname = $caller->[3];
141 299         1184 $subname =~ s/^.*:://g;
142             }
143              
144 299         264 my ($name, $code, $meta, @lines);
145 299         382 for my $item (@$args) {
146 615 100       1531 if (my $type = reftype($item)) {
    100          
147 317 100       443 if ($type eq 'CODE') {
    100          
148 295 100       388 die_at_caller $caller => "$subname() only accepts 1 coderef argument per call"
149             if $code;
150              
151 294         358 $code = $item;
152             }
153             elsif ($type eq 'HASH') {
154 21 100       44 die_at_caller $caller => "$subname() only accepts 1 meta-hash argument per call"
155             if $meta;
156              
157 20         21 $meta = $item;
158             }
159             else {
160 1         5 die_at_caller $caller => "Unknown argument to $subname: $item";
161             }
162             }
163             elsif ($item =~ m/^\d+$/) {
164 3 100       11 die_at_caller $caller => "$subname() only accepts 2 line number arguments per call (got: " . join(', ', @lines, $item) . ")"
165             if @lines >= 2;
166              
167 2         3 push @lines => $item;
168             }
169             else {
170 295 100       449 die_at_caller $caller => "$subname() only accepts 1 name argument per call (got: '$name', '$item')"
171             if $name;
172              
173 294         322 $name = $item;
174             }
175             }
176              
177 294 100       430 die_at_caller $caller => "$subname() requires a name argument (non-numeric string)"
178             unless $name;
179 293 100       395 die_at_caller $caller => "$subname() requires a code reference"
180             unless $code;
181              
182 292         636 my $info = sub_info($code, @lines);
183 292 100       2225 set_sub_name("$caller->[0]\::$name", $code) if CAN_SET_SUB_NAME && $info->{name} =~ m/__ANON__$/;
184              
185             my $unit = Test::Stream::Workflow::Unit->new(
186             name => $name,
187             meta => $meta,
188             package => $caller->[0],
189             file => $info->{file},
190             start_line => $info->{start_line} || $caller->[2],
191             end_line => $info->{end_line} || $caller->[2],
192              
193             $params{set_primary} ? (primary => $code) : (),
194              
195 292 100 33     1341 $params{unit} ? (%{$params{unit}}) : (),
  291 100 33     1249  
196             );
197              
198 292         1076 return ($unit, $code, $caller);
199             }
200              
201              
202 95     95   618 BEGIN { update_mask('*', '*', __PACKAGE__ . '::group_builder', {hide => 1}) }
203             sub group_builder {
204 28     28 1 252 my ($unit, $code, $caller) = new_proto_unit(
205             args => \@_,
206             unit => { type => 'group' },
207             );
208              
209 28         79 push_workflow_build($unit);
210             my ($ok, $err) = try {
211 95     95   415 BEGIN { update_mask(__FILE__, __LINE__ + 1, '*', {hide => 1}) }
212 28     28   78 $code->($unit);
213 27         49 1; # To force the previous statement to be in void context
214 28         145 };
215 28         120 pop_workflow_build($unit);
216 28 100       57 die $err unless $ok;
217              
218 27         75 $unit->do_post;
219 27         70 $unit->adjust_lines();
220              
221 27 100       61 return $unit if defined wantarray;
222              
223 25 100       49 my $current = _current($caller->[0])
224             or confess "Could not find the current build!";
225              
226 24         114 $current->add_primary($unit);
227             }
228              
229             sub _unit_builder_callback_simple {
230 221     221   279 my ($current, $unit, @stashes) = @_;
231 221         251 $current->$_($unit) for map {"add_$_"} @stashes;
  224         1707  
232             }
233              
234             sub _unit_builder_callback_modifiers {
235 6     6   14 my ($current, $unit, @stashes) = @_;
236             $current->add_post(sub {
237 7   100 7   22 my $modify = $current->modify || return;
238 6         24 for my $mod (@$modify) {
239 11         15 $mod->$_($unit) for map {"add_$_"} @stashes;
  15         58  
240             }
241 6         30 });
242             }
243              
244             sub _unit_builder_callback_primaries {
245 35     35   59 my ($current, $unit, @stashes) = @_;
246              
247             # Get the stash, we will be using it just like any plugin might
248 35         73 my $stash = $current->stash;
249              
250             # If we do not have data in the stash yet then we need to do some preliminary setup
251 35 100       122 unless($stash->{$PKG}) {
252             # Add our hash to the stash
253 20         37 $stash->{$PKG} = {};
254              
255             # Add the post-callback, do it once here, we don't want to add
256             # duplicate callbacks
257             $current->add_post(sub {
258 20     20   36 my $stuff = delete $stash->{$PKG};
259              
260 20         29 my $modify = $stuff->{modify};
261 20         22 my $buildup = $stuff->{buildup};
262 20         21 my $primary = $stuff->{primary};
263 20         19 my $teardown = $stuff->{teardown};
264              
265 20         34 my @search = ($current);
266 20         48 while (my $it = shift @search) {
267 110 100 100     177 if ($it->type && $it->type eq 'group') {
268 32 50       237 my $prim = $it->primary or next;
269 32         105 push @search => @$prim;
270 32         67 next;
271             }
272              
273 78 100       399 unshift @{$it->{modify}} => @$modify if $modify;
  3         5  
274 78 100       103 unshift @{$it->{buildup}} => @$buildup if $buildup;
  72         106  
275 78 100       101 push @{$it->{primary}} => @$primary if $primary;
  3         5  
276 78 100       131 push @{$it->{teardown}} => @$teardown if $teardown;
  59         232  
277             }
278 20         123 });
279             }
280              
281             # Add the unit to the plugin stash for each unit stash (these names are not
282             # ideal...) The data will be used by the post-callback that has already been added
283 35         54 push @{$stash->{$PKG}->{$_}} => $unit for @stashes;
  47         150  
284             }
285              
286             sub gen_unit_builder {
287 332     332 1 595 my %params = @_;
288 332         282 my $name = $params{name};
289 332   66     630 my $callback = $params{callback} || croak "'callback' is a required argument";
290 331   66     538 my $stashes = $params{stashes} || croak "'stashes' is a required argument";
291              
292 330   100     873 my $reftype = reftype($callback) || "";
293 330 100       1023 my $cb_sub = $reftype eq 'CODE' ? $callback : $PKG->can("_unit_builder_callback_$callback");
294 330 100       642 croak "'$callback' is not a valid callback"
295             unless $cb_sub;
296              
297 328   100     671 $reftype = reftype($stashes) || "";
298 328 100       485 croak "'stashes' must be an array reference (got: $stashes)"
299             unless $reftype eq 'ARRAY';
300              
301 327 100       403 my $wrap = @$stashes > 1 ? 1 : 0;
302 327         495 my $check = join '+', sort @$stashes;
303             croak "'$check' is not a valid stash"
304 327 100       570 unless $ALLOWED_STASHES{$check};
305              
306             return sub {
307 262     262   2069 my ($unit, $code, $caller) = new_proto_unit(
308             set_primary => 1,
309             args => [@_],
310             unit => {type => 'single', wrap => $wrap},
311             name => $name,
312             );
313              
314 262   66     633 my $subname = $name || $caller->[3];
315              
316 262 100       547 confess "$subname must only be called in a void context"
317             if defined wantarray;
318              
319 261 100       409 my $current = _current($caller->[0])
320             or confess "Could not find the current build!";
321              
322 260         805 $cb_sub->($current, $unit, @$stashes);
323             }
324 326         1803 }
325              
326             1;
327              
328             __END__