File Coverage

blib/lib/Module/Spy.pm
Criterion Covered Total %
statement 168 171 98.2
branch 14 18 77.7
condition n/a
subroutine 42 43 97.6
pod 1 1 100.0
total 225 233 96.5


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