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