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   1098 use strict;
  95         250  
  95         2437  
3 95     95   484 use warnings;
  95         203  
  95         2726  
4              
5 95     95   473 use Scalar::Util qw/reftype blessed/;
  95         188  
  95         5158  
6 95     95   505 use Carp qw/confess croak/;
  95         224  
  95         4431  
7              
8 95     95   507 use Test::Stream::Sync;
  95         210  
  95         2426  
9              
10 95     95   53255 use Test::Stream::Workflow::Meta;
  95         254  
  95         2487  
11 95     95   608 use Test::Stream::Workflow::Unit;
  95         183  
  95         2215  
12              
13 95     95   476 use Test::Stream::Context qw/context/;
  95         171  
  95         750  
14 95     95   548 use Test::Stream::Util qw/try set_sub_name CAN_SET_SUB_NAME sub_info update_mask/;
  95         184  
  95         642  
15              
16 95     95   607 use Test::Stream::Exporter;
  95         188  
  95         673  
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         12 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   47 my $caller = caller;
47 5         29 my $meta = Test::Stream::Workflow::Meta->get($caller);
48 5         26 $meta->set_autorun(0);
49             };
50 95     95   527 no Test::Stream::Exporter;
  95         191  
  95         488  
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 40 sub workflow_meta { Test::Stream::Workflow::Meta->get(scalar caller) }
66 2     2 1 21 sub workflow_run { Test::Stream::Workflow::Meta->get(scalar caller)->run(@_) }
67 1     1 1 7 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 269 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 92 my ($should_be) = @_;
75              
76 36 100 100     684 croak "Build stack mismatch"
      100        
77             unless @BUILD && $should_be && $BUILD[-1] == $should_be;
78              
79 33         68 pop @BUILD;
80             }
81              
82 81     81 1 581 sub has_workflow_vars { scalar @VARS }
83             sub push_workflow_vars {
84 210   100 210 1 645 my $vars = shift || {};
85 210         413 push @VARS => $vars;
86 210         544 $vars;
87             }
88              
89             sub pop_workflow_vars {
90 212     212 1 488 my ($should_be) = @_;
91              
92 212 100 100     2380 croak "Vars stack mismatch!"
      100        
93             unless @VARS && $should_be && $VARS[-1] == $should_be;
94              
95 209         418 my $it = pop @VARS;
96 209         533 %$it = ();
97 209         614 return;
98             }
99              
100             sub workflow_var {
101 29 100   29 1 378 confess "No VARS! workflow_var() should only be called inside a unit sub"
102             unless @VARS;
103              
104 28         56 my $vars = $VARS[-1];
105              
106 28         47 my $name = shift;
107 28 100       73 if (@_) {
108 16 100 100     113 if (ref $_[0] && reftype($_[0]) eq 'CODE') {
109             $vars->{$name} = $_[0]->()
110 11 100       53 unless defined $vars->{$name};
111             }
112             else {
113 5         14 ($vars->{$name}) = @_;
114             }
115             }
116 28         141 return $vars->{$name};
117             };
118              
119             sub _current {
120 288     288   457 my ($caller) = @_;
121              
122 288 100       931 return $BUILD[-1] if @BUILD;
123 171   100     731 my $spec_meta = Test::Stream::Workflow::Meta->get($caller) || return;
124 168         591 return $spec_meta->unit;
125             }
126              
127             sub die_at_caller {
128 8     8 0 19 my ($caller, $msg) = @_;
129 8         91 die "$msg at $caller->[1] line $caller->[2].\n";
130             }
131              
132             sub new_proto_unit {
133 297     297 1 1033 my %params = @_;
134 297 100       994 $params{level} = 1 unless defined $params{level};
135 297   50     2945 my $caller = $params{caller} || [caller($params{level})];
136 297         614 my $args = $params{args};
137 297         457 my $subname = $params{subname};
138              
139 297 50       681 unless ($subname) {
140 297         471 $subname = $caller->[3];
141 297         1337 $subname =~ s/^.*:://g;
142             }
143              
144 297         452 my ($name, $code, $meta, @lines);
145 297         598 for my $item (@$args) {
146 611 100       2193 if (my $type = reftype($item)) {
    100          
147 315 100       705 if ($type eq 'CODE') {
    100          
148 293 100       557 die_at_caller $caller => "$subname() only accepts 1 coderef argument per call"
149             if $code;
150              
151 292         606 $code = $item;
152             }
153             elsif ($type eq 'HASH') {
154 21 100       58 die_at_caller $caller => "$subname() only accepts 1 meta-hash argument per call"
155             if $meta;
156              
157 20         40 $meta = $item;
158             }
159             else {
160 1         6 die_at_caller $caller => "Unknown argument to $subname: $item";
161             }
162             }
163             elsif ($item =~ m/^\d+$/) {
164 3 100       13 die_at_caller $caller => "$subname() only accepts 2 line number arguments per call (got: " . join(', ', @lines, $item) . ")"
165             if @lines >= 2;
166              
167 2         4 push @lines => $item;
168             }
169             else {
170 293 100       606 die_at_caller $caller => "$subname() only accepts 1 name argument per call (got: '$name', '$item')"
171             if $name;
172              
173 292         527 $name = $item;
174             }
175             }
176              
177 292 100       643 die_at_caller $caller => "$subname() requires a name argument (non-numeric string)"
178             unless $name;
179 291 100       612 die_at_caller $caller => "$subname() requires a code reference"
180             unless $code;
181              
182 290         906 my $info = sub_info($code, @lines);
183 290 100       2913 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     1956 $params{unit} ? (%{$params{unit}}) : (),
  289 100 33     1641  
196             );
197              
198 290         1832 return ($unit, $code, $caller);
199             }
200              
201              
202 95     95   768 BEGIN { update_mask('*', '*', __PACKAGE__ . '::group_builder', {hide => 1}) }
203             sub group_builder {
204 28     28 1 298 my ($unit, $code, $caller) = new_proto_unit(
205             args => \@_,
206             unit => { type => 'group' },
207             );
208              
209 28         108 push_workflow_build($unit);
210             my ($ok, $err) = try {
211 95     95   659 BEGIN { update_mask(__FILE__, __LINE__ + 1, '*', {hide => 1}) }
212 28     28   85 $code->($unit);
213 27         74 1; # To force the previous statement to be in void context
214 28         182 };
215 28         183 pop_workflow_build($unit);
216 28 100       86 die $err unless $ok;
217              
218 27         109 $unit->do_post;
219 27         102 $unit->adjust_lines();
220              
221 27 100       87 return $unit if defined wantarray;
222              
223 25 100       79 my $current = _current($caller->[0])
224             or confess "Could not find the current build!";
225              
226 24         151 $current->add_primary($unit);
227             }
228              
229             sub _unit_builder_callback_simple {
230 219     219   481 my ($current, $unit, @stashes) = @_;
231 219         349 $current->$_($unit) for map {"add_$_"} @stashes;
  222         1210  
232             }
233              
234             sub _unit_builder_callback_modifiers {
235 6     6   21 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         23 $mod->$_($unit) for map {"add_$_"} @stashes;
  15         71  
240             }
241 6         45 });
242             }
243              
244             sub _unit_builder_callback_primaries {
245 35     35   90 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       183 unless($stash->{$PKG}) {
252             # Add our hash to the stash
253 20         49 $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   44 my $stuff = delete $stash->{$PKG};
259              
260 20         47 my $modify = $stuff->{modify};
261 20         40 my $buildup = $stuff->{buildup};
262 20         41 my $primary = $stuff->{primary};
263 20         33 my $teardown = $stuff->{teardown};
264              
265 20         47 my @search = ($current);
266 20         69 while (my $it = shift @search) {
267 110 100 100     321 if ($it->type && $it->type eq 'group') {
268 32 50       357 my $prim = $it->primary or next;
269 32         167 push @search => @$prim;
270 32         106 next;
271             }
272              
273 78 100       747 unshift @{$it->{modify}} => @$modify if $modify;
  3         8  
274 78 100       177 unshift @{$it->{buildup}} => @$buildup if $buildup;
  72         203  
275 78 100       167 push @{$it->{primary}} => @$primary if $primary;
  3         8  
276 78 100       235 push @{$it->{teardown}} => @$teardown if $teardown;
  59         356  
277             }
278 20         173 });
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         86 push @{$stash->{$PKG}->{$_}} => $unit for @stashes;
  47         251  
284             }
285              
286             sub gen_unit_builder {
287 332     332 1 982 my %params = @_;
288 332         523 my $name = $params{name};
289 332   66     954 my $callback = $params{callback} || croak "'callback' is a required argument";
290 331   66     899 my $stashes = $params{stashes} || croak "'stashes' is a required argument";
291              
292 330   100     1394 my $reftype = reftype($callback) || "";
293 330 100       1741 my $cb_sub = $reftype eq 'CODE' ? $callback : $PKG->can("_unit_builder_callback_$callback");
294 330 100       1013 croak "'$callback' is not a valid callback"
295             unless $cb_sub;
296              
297 328   100     962 $reftype = reftype($stashes) || "";
298 328 100       812 croak "'stashes' must be an array reference (got: $stashes)"
299             unless $reftype eq 'ARRAY';
300              
301 327 100       672 my $wrap = @$stashes > 1 ? 1 : 0;
302 327         843 my $check = join '+', sort @$stashes;
303             croak "'$check' is not a valid stash"
304 327 100       921 unless $ALLOWED_STASHES{$check};
305              
306             return sub {
307 260     260   3000 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     991 my $subname = $name || $caller->[3];
315              
316 260 100       764 confess "$subname must only be called in a void context"
317             if defined wantarray;
318              
319 259 100       594 my $current = _current($caller->[0])
320             or confess "Could not find the current build!";
321              
322 258         1198 $cb_sub->($current, $unit, @$stashes);
323             }
324 326         2711 }
325              
326             1;
327              
328             __END__