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   1142 use strict;
  95         248  
  95         2794  
3 95     95   501 use warnings;
  95         176  
  95         2879  
4              
5 95     95   480 use Carp qw/croak/;
  95         187  
  95         4825  
6 95     95   485 use Scalar::Util qw/blessed reftype weaken/;
  95         216  
  95         5455  
7 95     95   504 use Test::Stream::Util qw/try/;
  95         202  
  95         662  
8 95     95   58005 use Test::Stream::Workflow qw/workflow_build workflow_var has_workflow_vars/;
  95         260  
  95         416  
9              
10 95     95   59994 use Test::Stream::Mock;
  95         283  
  95         3598  
11 95     95   627 use Test::Stream::Workflow::Meta;
  95         199  
  95         2631  
12              
13             require Test::Stream::HashBase;
14              
15 95     95   510 use Test::Stream::Exporter;
  95         183  
  95         758  
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   528 no Test::Stream::Exporter;
  95         211  
  95         1658  
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 34 return unless @BUILD;
33 3         10 return $BUILD[-1];
34             }
35              
36             sub mocked {
37 73     73 1 143 my $proto = shift;
38 73   66     344 my $class = blessed($proto) || $proto;
39              
40             # Check if we have any mocks.
41 73   100     278 my $set = $MOCKS{$class} || return;
42              
43             # Remove dead mocks (undef due to weaken)
44 24   100     161 pop @$set while @$set && !defined($set->[-1]);
45              
46             # Remove the list if it is empty
47 24 100       65 delete $MOCKS{$class} unless @$set;
48              
49             # Return the controls (may be empty list)
50 24         73 return @$set;
51             }
52              
53             sub _delegate {
54 46     46   81 my ($args) = @_;
55              
56 46         418 my $do = __PACKAGE__->can('mock_do');
57 46         204 my $obj = __PACKAGE__->can('mock_obj');
58 46         202 my $class = __PACKAGE__->can('mock_class');
59 46         183 my $build = __PACKAGE__->can('mock_build');
60              
61 46 100       145 return $obj unless @$args;
62              
63 40         90 my ($proto, $arg1) = @$args;
64              
65 40 100 100     249 return $obj if ref($proto) && !blessed($proto);
66              
67 24 100       108 if (blessed($proto)) {
68 2 100       14 return $class unless $proto->isa('Test::Stream::Mock');
69 1 50 33     19 return $build if $arg1 && ref($arg1) && reftype($arg1) eq 'CODE';
      33        
70             }
71              
72 22 100       188 return $class if $proto =~ m/(?:::|')/;
73 6 100       31 return $class if $proto =~ m/^_*[A-Z]/;
74              
75 3 100       24 return $do if Test::Stream::Mock->can($proto);
76              
77 2 100       17 if (my $sub = __PACKAGE__->can("mock_$proto")) {
78 1         2 shift @$args;
79 1         3 return $sub;
80             }
81              
82 1         4 return undef;
83             }
84              
85             sub mock {
86 47 100 100 47 1 703 croak "undef is not a valid first argument to mock()"
87             if @_ && !defined($_[0]);
88              
89 46         144 my $sub = _delegate(\@_);
90              
91 46 100       293 croak "'$_[0]' does not look like a package name, and is not a valid control method"
92             unless $sub;
93              
94 45         147 $sub->(@_);
95             }
96              
97             sub mock_build {
98 8     8 1 30 my ($control, $sub) = @_;
99              
100 8 50 66     465 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     285 croak "mock_build requires a coderef as its second argument"
      66        
104             unless $sub && ref($sub) && reftype($sub) eq 'CODE';
105              
106 4         7 push @BUILD => $control;
107 4         16 my ($ok, $err) = &try($sub);
108 4         9 pop @BUILD;
109 4 50       17 die $err unless $ok;
110             }
111              
112             sub mock_do {
113 7     7 1 46 my ($meth, @args) = @_;
114              
115 7 100       347 croak "Not currently building a mock"
116             unless @BUILD;
117              
118 5         11 my $build = $BUILD[-1];
119              
120 5 100 66     328 croak "'$meth' is not a valid action for mock_do()"
121             if $meth =~ m/^_/ || !$build->can($meth);
122              
123 3         13 $build->$meth(@args);
124             }
125              
126             sub mock_obj {
127 26     26 1 56 my ($proto) = @_;
128              
129 26 100 66     236 if ($proto && ref($proto) && reftype($proto) ne 'CODE') {
      100        
130 19         36 shift @_;
131             }
132             else {
133 7         15 $proto = {};
134             }
135              
136 26         65 my $class = _generate_class();
137 26         44 my $control;
138              
139 26 100 66     107 if (@_ == 1 && reftype($_[0]) eq 'CODE') {
140 1         2 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         5 $c->purge_on_destroy(1);
149 1         4 $c->autoload(1);
150              
151 1         5 $orig->(@_);
152             },
153 1         6 );
154             }
155             else {
156 25         75 $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         102 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         256 $new->{'~~MOCK~CONTROL~~'} = $control;
171 26         107 return $new;
172             }
173              
174             sub _generate_class {
175 26     26   46 my $prefix = __PACKAGE__;
176              
177 26         73 for (1 .. 100) {
178 26         68 my $postfix = join '', map { chr(rand(26) + 65) } 1 .. 32;
  832         1748  
179 26         138 my $class = $prefix . '::__TEMP__::' . $postfix;
180 26         44 my $file = $class;
181 26         147 $file =~ s{::}{/}g;
182 26         39 $file .= '.pm';
183 26 50       93 next if $INC{$file};
184 95     95   703 my $stash = do { no strict 'refs'; \%{"${class}\::"} };
  95         221  
  95         77006  
  26         44  
  26         31  
  26         219  
185 26 50       130 next if keys %$stash;
186 26         77 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 203 my $proto = shift;
194 58   66     326 my $class = blessed($proto) || $proto;
195 58         152 my @args = @_;
196              
197 58         454 my $caller = [caller(0)];
198 58         134 my $void = !defined(wantarray);
199 58         217 my $build = workflow_build();
200 58         395 my $meta = Test::Stream::Workflow::Meta->get($caller->[0]);
201              
202 58 50 100     215 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   1941 my ($parent) = reverse mocked($class);
207 59         79 my $control;
208              
209 59 100 66     269 if (@args == 1 && ref($args[0]) && reftype($args[0]) eq 'CODE') {
      66        
210 1         6 $control = Test::Stream::Mock->new(class => $class);
211 1         5 mock_build($control, @args);
212             }
213             else {
214 58         360 $control = Test::Stream::Mock->new(class => $class, @args);
215             }
216              
217 59 100       160 if ($parent) {
218 3         8 $control->{parent} = $parent;
219 3         12 weaken($parent->{child} = $control);
220             }
221              
222 59   100     302 $MOCKS{$class} ||= [];
223 59         96 push @{$MOCKS{$class}} => $control;
  59         155  
224 59         197 weaken($MOCKS{$class}->[-1]);
225              
226 59         622 return $control;
227 58         314 };
228              
229 58 100       224 return $builder->() unless $void;
230              
231             my $set_vars = sub {
232 9     9   22 workflow_var(__PACKAGE__, sub { {} })->{$class} = $builder->();
  5         19  
233 9         30 };
234              
235 9 100       31 return $set_vars->() if has_workflow_vars;
236              
237 1   33     4 $build ||= $meta->unit;
238              
239 1         3 my $now = $builder->();
240 1     1   11 $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         4 return;
255             }
256              
257             sub mock_accessors {
258 1     1 1 8 return map {( $_ => Test::Stream::HashBase->gen_accessor($_) )} @_;
  3         83  
259             }
260              
261             sub mock_accessor {
262 2     2 1 9 my ($field) = @_;
263 2         10 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         11  
269             }
270              
271             sub mock_getter {
272 1     1 1 7 my ($field) = @_;
273 1         5 return Test::Stream::HashBase->gen_getter($field);
274             }
275              
276             sub mock_setters {
277 1     1 1 3 my ($prefix, @list) = @_;
278 1         2 return map {( "$prefix$_" => Test::Stream::HashBase->gen_setter($_) )} @list;
  3         12  
279             }
280              
281             sub mock_setter {
282 1     1 1 7 my ($field) = @_;
283 1         5 return Test::Stream::HashBase->gen_setter($field);
284             }
285              
286             1;
287              
288             __END__