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   1160 use strict;
  95         192  
  95         2407  
3 95     95   475 use warnings;
  95         235  
  95         2611  
4              
5 95     95   525 use Scalar::Util qw/reftype blessed/;
  95         195  
  95         5186  
6 95     95   528 use Carp qw/confess croak/;
  95         212  
  95         4506  
7              
8 95     95   511 use Test::Stream::Sync;
  95         200  
  95         2405  
9              
10 95     95   52320 use Test::Stream::Workflow::Meta;
  95         254  
  95         2412  
11 95     95   556 use Test::Stream::Workflow::Unit;
  95         186  
  95         2233  
12              
13 95     95   494 use Test::Stream::Context qw/context/;
  95         184  
  95         747  
14 95     95   536 use Test::Stream::Util qw/try set_sub_name CAN_SET_SUB_NAME sub_info update_mask/;
  95         189  
  95         702  
15              
16 95     95   544 use Test::Stream::Exporter;
  95         233  
  95         749  
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         10 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   48 my $caller = caller;
47 5         31 my $meta = Test::Stream::Workflow::Meta->get($caller);
48 5         26 $meta->set_autorun(0);
49             };
50 95     95   537 no Test::Stream::Exporter;
  95         198  
  95         486  
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 26 sub workflow_current { _current(caller) }
65 6     6 1 36 sub workflow_meta { Test::Stream::Workflow::Meta->get(scalar caller) }
66 2     2 1 19 sub workflow_run { Test::Stream::Workflow::Meta->get(scalar caller)->run(@_) }
67 1     1 1 5 sub workflow_runner { Test::Stream::Workflow::Meta->get(scalar caller)->set_runner(@_) }
68 1     1 1 5 sub workflow_runner_args { Test::Stream::Workflow::Meta->get(scalar caller)->set_runner_args(@_) }
69              
70 63 100   63 1 263 sub workflow_build { @BUILD ? $BUILD[-1] : undef }
71 34   100 34 1 143 sub push_workflow_build { push @BUILD => $_[0] || die "Nothing to push"; $_[0] }
  33         59  
72              
73             sub pop_workflow_build {
74 36     36 1 93 my ($should_be) = @_;
75              
76 36 100 100     658 croak "Build stack mismatch"
      100        
77             unless @BUILD && $should_be && $BUILD[-1] == $should_be;
78              
79 33         75 pop @BUILD;
80             }
81              
82 81     81 1 555 sub has_workflow_vars { scalar @VARS }
83             sub push_workflow_vars {
84 210   100 210 1 587 my $vars = shift || {};
85 210         373 push @VARS => $vars;
86 210         493 $vars;
87             }
88              
89             sub pop_workflow_vars {
90 212     212 1 372 my ($should_be) = @_;
91              
92 212 100 100     2149 croak "Vars stack mismatch!"
      100        
93             unless @VARS && $should_be && $VARS[-1] == $should_be;
94              
95 209         385 my $it = pop @VARS;
96 209         483 %$it = ();
97 209         506 return;
98             }
99              
100             sub workflow_var {
101 29 100   29 1 367 confess "No VARS! workflow_var() should only be called inside a unit sub"
102             unless @VARS;
103              
104 28         49 my $vars = $VARS[-1];
105              
106 28         49 my $name = shift;
107 28 100       71 if (@_) {
108 16 100 100     103 if (ref $_[0] && reftype($_[0]) eq 'CODE') {
109             $vars->{$name} = $_[0]->()
110 11 100       48 unless defined $vars->{$name};
111             }
112             else {
113 5         14 ($vars->{$name}) = @_;
114             }
115             }
116 28         132 return $vars->{$name};
117             };
118              
119             sub _current {
120 288     288   438 my ($caller) = @_;
121              
122 288 100       936 return $BUILD[-1] if @BUILD;
123 171   100     719 my $spec_meta = Test::Stream::Workflow::Meta->get($caller) || return;
124 168         565 return $spec_meta->unit;
125             }
126              
127             sub die_at_caller {
128 8     8 0 22 my ($caller, $msg) = @_;
129 8         83 die "$msg at $caller->[1] line $caller->[2].\n";
130             }
131              
132             sub new_proto_unit {
133 297     297 1 1021 my %params = @_;
134 297 100       979 $params{level} = 1 unless defined $params{level};
135 297   50     2899 my $caller = $params{caller} || [caller($params{level})];
136 297         616 my $args = $params{args};
137 297         439 my $subname = $params{subname};
138              
139 297 50       674 unless ($subname) {
140 297         500 $subname = $caller->[3];
141 297         1386 $subname =~ s/^.*:://g;
142             }
143              
144 297         443 my ($name, $code, $meta, @lines);
145 297         581 for my $item (@$args) {
146 611 100       2234 if (my $type = reftype($item)) {
    100          
147 315 100       669 if ($type eq 'CODE') {
    100          
148 293 100       627 die_at_caller $caller => "$subname() only accepts 1 coderef argument per call"
149             if $code;
150              
151 292         596 $code = $item;
152             }
153             elsif ($type eq 'HASH') {
154 21 100       49 die_at_caller $caller => "$subname() only accepts 1 meta-hash argument per call"
155             if $meta;
156              
157 20         34 $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       14 die_at_caller $caller => "$subname() only accepts 2 line number arguments per call (got: " . join(', ', @lines, $item) . ")"
165             if @lines >= 2;
166              
167 2         5 push @lines => $item;
168             }
169             else {
170 293 100       622 die_at_caller $caller => "$subname() only accepts 1 name argument per call (got: '$name', '$item')"
171             if $name;
172              
173 292         542 $name = $item;
174             }
175             }
176              
177 292 100       651 die_at_caller $caller => "$subname() requires a name argument (non-numeric string)"
178             unless $name;
179 291 100       618 die_at_caller $caller => "$subname() requires a code reference"
180             unless $code;
181              
182 290         872 my $info = sub_info($code, @lines);
183 290 100       2842 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 290 100 33     1977 $params{unit} ? (%{$params{unit}}) : (),
  289 100 33     1630  
196             );
197              
198 290         1827 return ($unit, $code, $caller);
199             }
200              
201              
202 95     95   826 BEGIN { update_mask('*', '*', __PACKAGE__ . '::group_builder', {hide => 1}) }
203             sub group_builder {
204 28     28 1 295 my ($unit, $code, $caller) = new_proto_unit(
205             args => \@_,
206             unit => { type => 'group' },
207             );
208              
209 28         111 push_workflow_build($unit);
210             my ($ok, $err) = try {
211 95     95   619 BEGIN { update_mask(__FILE__, __LINE__ + 1, '*', {hide => 1}) }
212 28     28   94 $code->($unit);
213 27         78 1; # To force the previous statement to be in void context
214 28         179 };
215 28         153 pop_workflow_build($unit);
216 28 100       80 die $err unless $ok;
217              
218 27         119 $unit->do_post;
219 27         100 $unit->adjust_lines();
220              
221 27 100       78 return $unit if defined wantarray;
222              
223 25 100       69 my $current = _current($caller->[0])
224             or confess "Could not find the current build!";
225              
226 24         155 $current->add_primary($unit);
227             }
228              
229             sub _unit_builder_callback_simple {
230 219     219   466 my ($current, $unit, @stashes) = @_;
231 219         357 $current->$_($unit) for map {"add_$_"} @stashes;
  222         1228  
232             }
233              
234             sub _unit_builder_callback_modifiers {
235 6     6   20 my ($current, $unit, @stashes) = @_;
236             $current->add_post(sub {
237 7   100 7   33 my $modify = $current->modify || return;
238 6         32 for my $mod (@$modify) {
239 11         20 $mod->$_($unit) for map {"add_$_"} @stashes;
  15         73  
240             }
241 6         43 });
242             }
243              
244             sub _unit_builder_callback_primaries {
245 35     35   89 my ($current, $unit, @stashes) = @_;
246              
247             # Get the stash, we will be using it just like any plugin might
248 35         125 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       168 unless($stash->{$PKG}) {
252             # Add our hash to the stash
253 20         47 $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   50 my $stuff = delete $stash->{$PKG};
259              
260 20         44 my $modify = $stuff->{modify};
261 20         34 my $buildup = $stuff->{buildup};
262 20         38 my $primary = $stuff->{primary};
263 20         38 my $teardown = $stuff->{teardown};
264              
265 20         45 my @search = ($current);
266 20         73 while (my $it = shift @search) {
267 110 100 100     312 if ($it->type && $it->type eq 'group') {
268 32 50       418 my $prim = $it->primary or next;
269 32         168 push @search => @$prim;
270 32         116 next;
271             }
272              
273 78 100       748 unshift @{$it->{modify}} => @$modify if $modify;
  3         7  
274 78 100       206 unshift @{$it->{buildup}} => @$buildup if $buildup;
  72         187  
275 78 100       174 push @{$it->{primary}} => @$primary if $primary;
  3         7  
276 78 100       224 push @{$it->{teardown}} => @$teardown if $teardown;
  59         423  
277             }
278 20         159 });
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         76 push @{$stash->{$PKG}->{$_}} => $unit for @stashes;
  47         248  
284             }
285              
286             sub gen_unit_builder {
287 332     332 1 972 my %params = @_;
288 332         568 my $name = $params{name};
289 332   66     929 my $callback = $params{callback} || croak "'callback' is a required argument";
290 331   66     864 my $stashes = $params{stashes} || croak "'stashes' is a required argument";
291              
292 330   100     1413 my $reftype = reftype($callback) || "";
293 330 100       1756 my $cb_sub = $reftype eq 'CODE' ? $callback : $PKG->can("_unit_builder_callback_$callback");
294 330 100       1032 croak "'$callback' is not a valid callback"
295             unless $cb_sub;
296              
297 328   100     1028 $reftype = reftype($stashes) || "";
298 328 100       869 croak "'stashes' must be an array reference (got: $stashes)"
299             unless $reftype eq 'ARRAY';
300              
301 327 100       652 my $wrap = @$stashes > 1 ? 1 : 0;
302 327         791 my $check = join '+', sort @$stashes;
303             croak "'$check' is not a valid stash"
304 327 100       923 unless $ALLOWED_STASHES{$check};
305              
306             return sub {
307 260     260   3124 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 260   66     1024 my $subname = $name || $caller->[3];
315              
316 260 100       736 confess "$subname must only be called in a void context"
317             if defined wantarray;
318              
319 259 100       623 my $current = _current($caller->[0])
320             or confess "Could not find the current build!";
321              
322 258         1226 $cb_sub->($current, $unit, @$stashes);
323             }
324 326         2642 }
325              
326             1;
327              
328             __END__