File Coverage

blib/lib/Module/Spy.pm
Criterion Covered Total %
statement 166 167 99.4
branch 15 18 83.3
condition n/a
subroutine 41 42 97.6
pod 1 1 100.0
total 223 228 97.8


line stmt bran cond sub pod time code
1             package Module::Spy;
2 3     3   42254 use 5.008005;
  3         12  
3 3     3   16 use strict;
  3         6  
  3         66  
4 3     3   18 use warnings;
  3         6  
  3         90  
5 3     3   15 use Scalar::Util ();
  3         5  
  3         119  
6              
7             our $VERSION = "0.05";
8              
9 3     3   2396 use parent qw(Exporter);
  3         793  
  3         15  
10              
11             our @EXPORT = qw(spy_on);
12              
13             sub spy_on {
14 19     19 1 25479 my ($stuff, $method) = @_;
15              
16 19 100       105 if (Scalar::Util::blessed($stuff)) {
17 6         19 Module::Spy::Object->new($stuff, $method);
18             } else {
19 13         54 Module::Spy::Class->new($stuff, $method);
20             }
21             }
22              
23             package Module::Spy::Base;
24              
25 44     44   173 sub stuff { shift->{stuff} }
26 32     32   216 sub method { shift->{method} }
27              
28 5     5   16 sub calls_any { @{shift->{spy}->calls_all(@_)} > 0 }
  5         16  
29 3     3   14 sub calls_count { 0+@{shift->{spy}->calls_all(@_)} }
  3         13  
30 1     1   12 sub calls_all { shift->{spy}->calls_all(@_) }
31 1     1   12 sub calls_most_recent { shift->{spy}->calls_all(@_)->[-1] }
32 2     2   16 sub calls_first { shift->{spy}->calls_all(@_)->[0] }
33 1     1   12 sub calls_reset { shift->{spy}->calls_reset(@_) }
34              
35             sub called {
36 9     9   1873 my $self = shift;
37 9         31 $self->{spy}->called;
38             }
39              
40             sub and_call_through {
41 2     2   3 my $self = shift;
42 2         8 $self->{spy}->call_through;
43 2         4 return $self;
44             }
45              
46             sub and_call_fake {
47 2     2   3 my ($self, $code) = @_;
48 2         7 $self->{spy}->call_fake($code);
49 2         4 return $self;
50             }
51              
52             sub and_returns {
53 2     2   13 my $self = shift;
54 2         7 $self->{spy}->returns(@_);
55 2         17 return $self;
56             }
57 0     0   0 sub returns { shift->and_returns(@_) }
58              
59             package Module::Spy::Object;
60             our @ISA=('Module::Spy::Base');
61              
62             my $SINGLETON_ID = 0;
63              
64             sub new {
65 6     6   12 my $class = shift;
66 6         9 my ($stuff, $method) = @_;
67              
68 6         21 my $self = bless { stuff => $stuff, method => $method }, $class;
69              
70 6 50       20 my $orig = $self->stuff->can($self->method)
71             or die "Missing $method";
72 6         13 $self->{orig} = $orig;
73              
74 6         17 my $spy = Module::Spy::Sub->new($orig);
75 6         11 $self->{spy} = $spy;
76              
77 6         14 $self->{orig_class} = ref($stuff);
78              
79             {
80 3     3   1580 no strict 'refs';
  3         6  
  3         89  
  6         6  
81 3     3   14 no warnings 'redefine';
  3         4  
  3         1264  
82              
83 6         7 $SINGLETON_ID++;
84 6         14 my $klass = "Module::Spy::__ANON__::" . $SINGLETON_ID;
85 6         14 $self->{id} = $SINGLETON_ID;
86 6         7 $self->{anon_class} = $klass;
87 6         7 $self->{isa} = do { \@{"${klass}::ISA"} };
  6         7  
  6         57  
88 6         7 unshift @{$self->{isa}}, ref($stuff);
  6         49  
89 6         8 *{"${klass}::${method}"} = $spy;
  6         33  
90 6         19 bless $stuff, $klass; # rebless
91             }
92              
93 6         29 return $self;
94             }
95              
96             sub get_stash {
97 6     6   7 my $klass = shift;
98              
99 6         22 my $pack = *main::;
100 6         20 foreach my $part (split /::/, $klass){
101 18 50       63 return undef unless $pack = $pack->{$part . '::'};
102             }
103 6         10 return *{$pack}{HASH};
  6         19  
104             }
105              
106             sub DESTROY {
107 6     6   2867 my $self = shift;
108              
109             # Restore the object's type.
110 6 50       15 if (ref($self->stuff) eq $self->{anon_class}) {
111 6         12 bless $self->stuff, $self->{orig_class};
112             }
113              
114 6         10 @{$self->{isa}} = ();
  6         56  
115              
116 6         18 my $original_stash = get_stash("Module::Spy::__ANON__");
117 6         55 my $sclass_stashgv = delete $original_stash->{$self->{id} . '::'};
118 6         18 %{$sclass_stashgv} = ();
  6         19  
119              
120 6         19 undef $self->{spy};
121             }
122              
123             package Module::Spy::Class;
124             our @ISA=('Module::Spy::Base');
125              
126             sub new {
127 13     13   19 my $class = shift;
128 13         23 my ($stuff, $method) = @_;
129              
130 13         52 my $self = bless { stuff => $stuff, method => $method }, $class;
131              
132 13         47 my $orig = $self->stuff->can($self->method);
133 13         34 $self->{orig} = $orig;
134              
135 13         45 my $spy = Module::Spy::Sub->new($orig);
136 13         111 $self->{spy} = $spy;
137              
138             {
139 3     3   14 no strict 'refs';
  3         4  
  3         85  
  13         21  
140 3     3   12 no warnings 'redefine';
  3         4  
  3         335  
141 13         15 *{$self->stuff . '::' . $self->method} = $spy;
  13         30  
142             }
143              
144 13         59 return $self;
145             }
146              
147             sub DESTROY {
148 13     13   7229 my $self = shift;
149 13         24 my $stuff = $self->{stuff};
150 13         20 my $method = $self->{method};
151 13         21 my $orig = $self->{orig};
152              
153 13 100       27 if (defined $orig) {
154 3     3   29 no strict 'refs';
  3         6  
  3         75  
155 3     3   13 no warnings 'redefine';
  3         4  
  3         150  
156 7         8 *{"${stuff}::${method}"} = $orig;
  7         26  
157             } else {
158 3     3   13 no strict 'refs';
  3         13  
  3         248  
159 6         10 delete ${"${stuff}::"}{${method}};
  6         31  
160             }
161              
162 13         49 undef $self->{spy};
163             }
164              
165             package Module::Spy::Sub;
166 3     3   12 use Scalar::Util qw(refaddr);
  3         6  
  3         1983  
167              
168             # inside-out
169             our %COUNTER;
170             our %RETURNS;
171             our %CALL_THROUGH;
172             our %CALL_FAKE;
173             our %ARGS;
174              
175             sub new {
176 19     19   28 my ($class, $orig) = @_;
177              
178 19         18 my $body;
179 19     19   67 my $code = sub { goto $body };
  19         86  
180              
181 19         48 my $code_addr = refaddr($code);
182             $body = sub {
183 19     19   32 $COUNTER{$code_addr}++;
184 19         26 push @{$ARGS{$code_addr}}, [@_];
  19         62  
185              
186 19 100       57 if (my $fake = $CALL_FAKE{$code_addr}) {
187 2         4 goto $fake;
188             }
189              
190 17 100       43 if (exists $RETURNS{$code_addr}) {
191 2 100       3 if (@{$RETURNS{$code_addr}} == 1) {
  2         7  
192 1         6 return $RETURNS{$code_addr}->[0];
193             }
194 1         3 return @{$RETURNS{$code_addr}};
  1         4  
195             }
196              
197 15 100       36 if ($CALL_THROUGH{$code_addr}) {
198 2         4 goto $orig;
199             }
200              
201 13         31 return;
202 19         66 };
203 19         55 $COUNTER{$code_addr} = 0;
204 19         38 $ARGS{$code_addr} = [];
205              
206 19         33 my $self = bless $code, $class;
207 19         38 return $self;
208             }
209              
210             sub DESTROY {
211 19     19   34 my $self = shift;
212 19         48 my $code_addr = refaddr($self);
213              
214 19         41 delete $COUNTER{$code_addr};
215 19         61 delete $RETURNS{$code_addr};
216 19         27 delete $CALL_FAKE{$code_addr};
217 19         39 delete $CALL_THROUGH{$code_addr};
218 19         256 delete $ARGS{$code_addr};
219             }
220              
221             sub called {
222 9     9   10 my $self = shift;
223 9         59 !!$COUNTER{refaddr($self)};
224             }
225              
226             sub calls_all {
227 12     12   17 my $self = shift;
228 12         90 $ARGS{refaddr($self)};
229             }
230              
231             sub calls_reset {
232 1     1   2 my $self = shift;
233 1         6 $COUNTER{refaddr($self)} = 0;
234 1         6 $ARGS{refaddr($self)} = [];
235             }
236              
237             sub returns {
238 2     2   4 my $self = shift;
239 2         10 $RETURNS{refaddr($self)} = [@_];
240             }
241              
242             sub call_through {
243 2     2   3 my $self = shift;
244 2         8 $CALL_THROUGH{refaddr($self)}++;
245             }
246              
247             sub call_fake {
248 2     2   3 my ($self, $code) = @_;
249 2         8 $CALL_FAKE{refaddr($self)} = $code;
250             }
251              
252             1;
253             __END__