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