File Coverage

blib/lib/Test/Stream/Plugin/Mock.pm
Criterion Covered Total %
statement 155 156 99.3
branch 47 54 87.0
condition 48 67 71.6
subroutine 30 30 100.0
pod 13 13 100.0
total 293 320 91.5


line stmt bran cond sub pod time code
1             package Test::Stream::Plugin::Mock;
2 95     95   1126 use strict;
  95         189  
  95         2513  
3 95     95   505 use warnings;
  95         179  
  95         2830  
4              
5 95     95   470 use Carp qw/croak/;
  95         179  
  95         4661  
6 95     95   524 use Scalar::Util qw/blessed reftype weaken/;
  95         248  
  95         5439  
7 95     95   505 use Test::Stream::Util qw/try/;
  95         211  
  95         678  
8 95     95   58348 use Test::Stream::Workflow qw/workflow_build workflow_var has_workflow_vars/;
  95         256  
  95         422  
9              
10 95     95   61311 use Test::Stream::Mock;
  95         281  
  95         3642  
11 95     95   600 use Test::Stream::Workflow::Meta;
  95         211  
  95         2701  
12              
13             require Test::Stream::HashBase;
14              
15 95     95   534 use Test::Stream::Exporter;
  95         204  
  95         782  
16             default_exports qw/mock mocked/;
17             exports qw{
18             mock_obj mock_class
19             mock_do mock_build
20             mock_accessor mock_accessors
21             mock_getter mock_getters
22             mock_setter mock_setters
23             mock_building
24             };
25 95     95   541 no Test::Stream::Exporter;
  95         218  
  95         1648  
26              
27             our @CARP_NOT = (__PACKAGE__, 'Test::Stream::Mock');
28             my %MOCKS;
29             my @BUILD;
30              
31             sub mock_building {
32 7 100   7 1 36 return unless @BUILD;
33 3         11 return $BUILD[-1];
34             }
35              
36             sub mocked {
37 73     73 1 136 my $proto = shift;
38 73   66     347 my $class = blessed($proto) || $proto;
39              
40             # Check if we have any mocks.
41 73   100     310 my $set = $MOCKS{$class} || return;
42              
43             # Remove dead mocks (undef due to weaken)
44 24   100     174 pop @$set while @$set && !defined($set->[-1]);
45              
46             # Remove the list if it is empty
47 24 100       73 delete $MOCKS{$class} unless @$set;
48              
49             # Return the controls (may be empty list)
50 24         83 return @$set;
51             }
52              
53             sub _delegate {
54 46     46   77 my ($args) = @_;
55              
56 46         357 my $do = __PACKAGE__->can('mock_do');
57 46         220 my $obj = __PACKAGE__->can('mock_obj');
58 46         206 my $class = __PACKAGE__->can('mock_class');
59 46         198 my $build = __PACKAGE__->can('mock_build');
60              
61 46 100       156 return $obj unless @$args;
62              
63 40         96 my ($proto, $arg1) = @$args;
64              
65 40 100 100     263 return $obj if ref($proto) && !blessed($proto);
66              
67 24 100       106 if (blessed($proto)) {
68 2 100       15 return $class unless $proto->isa('Test::Stream::Mock');
69 1 50 33     15 return $build if $arg1 && ref($arg1) && reftype($arg1) eq 'CODE';
      33        
70             }
71              
72 22 100       194 return $class if $proto =~ m/(?:::|')/;
73 6 100       33 return $class if $proto =~ m/^_*[A-Z]/;
74              
75 3 100       25 return $do if Test::Stream::Mock->can($proto);
76              
77 2 100       19 if (my $sub = __PACKAGE__->can("mock_$proto")) {
78 1         3 shift @$args;
79 1         3 return $sub;
80             }
81              
82 1         3 return undef;
83             }
84              
85             sub mock {
86 47 100 100 47 1 734 croak "undef is not a valid first argument to mock()"
87             if @_ && !defined($_[0]);
88              
89 46         154 my $sub = _delegate(\@_);
90              
91 46 100       291 croak "'$_[0]' does not look like a package name, and is not a valid control method"
92             unless $sub;
93              
94 45         140 $sub->(@_);
95             }
96              
97             sub mock_build {
98 8     8 1 31 my ($control, $sub) = @_;
99              
100 8 50 66     495 croak "mock_build requires a Test::Stream::Mock object as its first argument"
      66        
101             unless $control && blessed($control) && $control->isa('Test::Stream::Mock');
102              
103 6 50 66     280 croak "mock_build requires a coderef as its second argument"
      66        
104             unless $sub && ref($sub) && reftype($sub) eq 'CODE';
105              
106 4         9 push @BUILD => $control;
107 4         19 my ($ok, $err) = &try($sub);
108 4         11 pop @BUILD;
109 4 50       17 die $err unless $ok;
110             }
111              
112             sub mock_do {
113 7     7 1 51 my ($meth, @args) = @_;
114              
115 7 100       314 croak "Not currently building a mock"
116             unless @BUILD;
117              
118 5         40 my $build = $BUILD[-1];
119              
120 5 100 66     392 croak "'$meth' is not a valid action for mock_do()"
121             if $meth =~ m/^_/ || !$build->can($meth);
122              
123 3         16 $build->$meth(@args);
124             }
125              
126             sub mock_obj {
127 26     26 1 64 my ($proto) = @_;
128              
129 26 100 66     237 if ($proto && ref($proto) && reftype($proto) ne 'CODE') {
      100        
130 19         33 shift @_;
131             }
132             else {
133 7         16 $proto = {};
134             }
135              
136 26         65 my $class = _generate_class();
137 26         45 my $control;
138              
139 26 100 66     117 if (@_ == 1 && reftype($_[0]) eq 'CODE') {
140 1         3 my $orig = shift @_;
141             $control = mock_class(
142             $class,
143             sub {
144 1     1   4 my $c = mock_building;
145              
146             # We want to do these BEFORE anything that the sub may do.
147 1         5 $c->block_load(1);
148 1         4 $c->purge_on_destroy(1);
149 1         3 $c->autoload(1);
150              
151 1         4 $orig->(@_);
152             },
153 1         8 );
154             }
155             else {
156 25         94 $control = mock_class(
157             $class,
158             # Do these before anything the user specified.
159             block_load => 1,
160             purge_on_destroy => 1,
161             autoload => 1,
162             @_,
163             );
164             }
165              
166 26         104 my $new = bless($proto, $control->class);
167              
168             # We need to ensure there is a reference to the control object, and we want
169             # it to go away with the object.
170 26         264 $new->{'~~MOCK~CONTROL~~'} = $control;
171 26         110 return $new;
172             }
173              
174             sub _generate_class {
175 26     26   49 my $prefix = __PACKAGE__;
176              
177 26         75 for (1 .. 100) {
178 26         64 my $postfix = join '', map { chr(rand(26) + 65) } 1 .. 32;
  832         1739  
179 26         164 my $class = $prefix . '::__TEMP__::' . $postfix;
180 26         44 my $file = $class;
181 26         141 $file =~ s{::}{/}g;
182 26         48 $file .= '.pm';
183 26 50       107 next if $INC{$file};
184 95     95   580 my $stash = do { no strict 'refs'; \%{"${class}\::"} };
  95         225  
  95         77527  
  26         38  
  26         36  
  26         235  
185 26 50       84 next if keys %$stash;
186 26         72 return $class;
187             }
188              
189 0         0 croak "Could not generate a unique class name after 100 attempts";
190             }
191              
192             sub mock_class {
193 58     58 1 196 my $proto = shift;
194 58   66     326 my $class = blessed($proto) || $proto;
195 58         156 my @args = @_;
196              
197 58         472 my $caller = [caller(0)];
198 58         149 my $void = !defined(wantarray);
199 58         221 my $build = workflow_build();
200 58         403 my $meta = Test::Stream::Workflow::Meta->get($caller->[0]);
201              
202 58 50 100     230 croak "mock_class should not be called in a void context except in a workflow"
      66        
      66        
203             unless has_workflow_vars || $build || $meta || !$void;
204              
205             my $builder = sub {
206 59     59   178 my ($parent) = reverse mocked($class);
207 59         90 my $control;
208              
209 59 100 66     276 if (@args == 1 && ref($args[0]) && reftype($args[0]) eq 'CODE') {
      66        
210 1         6 $control = Test::Stream::Mock->new(class => $class);
211 1         6 mock_build($control, @args);
212             }
213             else {
214 58         383 $control = Test::Stream::Mock->new(class => $class, @args);
215             }
216              
217 59 100       180 if ($parent) {
218 3         8 $control->{parent} = $parent;
219 3         12 weaken($parent->{child} = $control);
220             }
221              
222 59   100     323 $MOCKS{$class} ||= [];
223 59         111 push @{$MOCKS{$class}} => $control;
  59         151  
224 59         208 weaken($MOCKS{$class}->[-1]);
225              
226 59         594 return $control;
227 58         324 };
228              
229 58 100       2024 return $builder->() unless $void;
230              
231             my $set_vars = sub {
232 9     9   24 workflow_var(__PACKAGE__, sub { {} })->{$class} = $builder->();
  5         23  
233 9         35 };
234              
235 9 100       29 return $set_vars->() if has_workflow_vars;
236              
237 1   33     4 $build ||= $meta->unit;
238              
239 1         8 my $now = $builder->();
240 1     1   6 $build->add_post(sub { $now = undef });
  1         6  
241              
242 1         7 $build->add_buildup(
243             Test::Stream::Workflow::Unit->new(
244             name => "Mock $class",
245             package => $caller->[0],
246             file => $caller->[1],
247             start_line => $caller->[2],
248             end_line => $caller->[2],
249             type => 'single',
250             primary => $set_vars,
251             ),
252             );
253              
254 1         5 return;
255             }
256              
257             sub mock_accessors {
258 1     1 1 9 return map {( $_ => Test::Stream::HashBase->gen_accessor($_) )} @_;
  3         23  
259             }
260              
261             sub mock_accessor {
262 2     2 1 10 my ($field) = @_;
263 2         11 return Test::Stream::HashBase->gen_accessor($field);
264             }
265              
266             sub mock_getters {
267 1     1 1 4 my ($prefix, @list) = @_;
268 1         3 return map {( "$prefix$_" => Test::Stream::HashBase->gen_getter($_) )} @list;
  3         12  
269             }
270              
271             sub mock_getter {
272 1     1 1 8 my ($field) = @_;
273 1         4 return Test::Stream::HashBase->gen_getter($field);
274             }
275              
276             sub mock_setters {
277 1     1 1 5 my ($prefix, @list) = @_;
278 1         2 return map {( "$prefix$_" => Test::Stream::HashBase->gen_setter($_) )} @list;
  3         11  
279             }
280              
281             sub mock_setter {
282 1     1 1 9 my ($field) = @_;
283 1         7 return Test::Stream::HashBase->gen_setter($field);
284             }
285              
286             1;
287              
288             __END__