File Coverage

blib/lib/Mock/Quick/Class.pm
Criterion Covered Total %
statement 160 160 100.0
branch 40 48 83.3
condition 16 21 76.1
subroutine 35 35 100.0
pod 8 12 66.6
total 259 276 93.8


line stmt bran cond sub pod time code
1             package Mock::Quick::Class;
2 4     4   500 use strict;
  4         4  
  4         93  
3 4     4   11 use warnings;
  4         4  
  4         69  
4              
5 4     4   1194 use Mock::Quick::Util;
  4         5  
  4         231  
6 4     4   14 use Scalar::Util qw/blessed weaken/;
  4         4  
  4         136  
7 4     4   12 use Carp qw/croak confess carp/;
  4         4  
  4         2465  
8              
9             our @CARP_NOT = ('Mock::Quick', 'Mock::Quick::Object');
10             our $ANON = 'AAAAAAAAAA';
11              
12 122     122 1 1803 sub package { shift->{'-package'} }
13 5     5 0 27 sub inc { shift->{'-inc'} }
14 44     44 1 196 sub is_takeover { shift->{'-takeover'} }
15 14     14 1 60 sub is_implement { shift->{'-implement'}}
16              
17             sub metrics {
18 45     45 1 42 my $self = shift;
19 45   100     105 $self->{'-metrics'} ||= {};
20 45         60 return $self->{'-metrics'};
21             }
22              
23             sub takeover {
24 7     7 0 417 my $class = shift;
25 7         14 my ( $proto, %params ) = @_;
26 7   66     32 my $package = blessed( $proto ) || $proto;
27              
28 7         23 my $self = bless( { -package => $package, -takeover => 1 }, $class );
29              
30 7         14 for my $key ( keys %params ) {
31 2 50       7 croak "param '$key' is not valid in a takeover"
32             if $key =~ m/^-/;
33 2         8 $self->override( $key => $params{$key} );
34             }
35              
36 7         12 $self->inject_meta();
37              
38 7         16 return $self;
39             }
40              
41             sub implement {
42 3     3 0 26 my $class = shift;
43 3         7 my ( $package, %params ) = @_;
44 3   50     14 my $caller = delete $params{'-caller'} || [caller()];
45              
46 3         44 my $inc = $package;
47 3         6 $inc =~ s|::|/|g;
48 3         4 $inc .= '.pm';
49              
50             croak "$package has already been loaded, cannot implement it."
51 3 50       7 if $INC{$inc};
52              
53 3         25 $INC{$inc} = $caller->[1];
54              
55 3         13 my $self = bless(
56             { -package => $package, -implement => 1, -inc => $inc },
57             $class
58             );
59              
60 3         6 $self->inject_meta();
61              
62 3         8 $self->_configure( %params );
63              
64 3         8 return $self;
65             }
66              
67             alt_meth new => (
68             obj => sub { my $self = shift; $self->package->new(@_) },
69             class => sub {
70             my $class = shift;
71             my %params = @_;
72              
73             croak "You cannot combine '-takeover' and '-implement' arguments"
74             if $params{'-takeover'} && $params{'-implement'};
75              
76             return $class->takeover( delete( $params{'-takeover'} ), %params )
77             if $params{'-takeover'};
78              
79             return $class->implement( delete( $params{'-implement'} ), %params )
80             if $params{'-implement'};
81              
82             my $package = __PACKAGE__ . "::__ANON__::" . $ANON++;
83              
84             my $self = bless( { %params, -package => $package }, $class );
85              
86             $self->inject_meta();
87              
88             $self->_configure( %params );
89              
90             return $self;
91             }
92             );
93              
94             sub inject_meta {
95 18     18 0 17 my $self = shift;
96 18         18 my $weak_self = $self;
97 18         37 weaken $weak_self;
98 18     1   26 inject( $self->package, 'MQ_CONTROL', sub { $weak_self } );
  1         5  
99             }
100              
101             sub _configure {
102 11     11   11 my $self = shift;
103 11         17 my %params = @_;
104 11         33 my $package = $self->package;
105 11         21 my $metrics = $self->metrics;
106              
107 11         19 for my $key ( keys %params ) {
108 17         17 my $value = $params{$key};
109              
110 17 100       53 if ( $key =~ m/^-/ ) {
    100          
111 9         17 $self->_configure_pair( $key, $value );
112             }
113             elsif( _is_sub_ref( $value )) {
114 4     6   15 inject( $package, $key, sub { $metrics->{$key}++; $value->(@_) });
  6         14  
  6         9  
115             }
116             else {
117 4     1   16 inject( $package, $key, sub { $metrics->{$key}++; $value });
  1         2  
  1         3  
118             }
119             }
120             }
121              
122             sub _configure_pair {
123 9     9   7 my $control = shift;
124 9         10 my ( $param, $value ) = @_;
125 9         11 my $package = $control->package;
126 9         24 my $metrics = $control->metrics;
127              
128 9 100       37 if ( $param eq '-subclass' ) {
    100          
    50          
129 2 100       8 $value = [ $value ] unless ref $value eq 'ARRAY';
130 4     4   16 no strict 'refs';
  4         4  
  4         1936  
131 2         2 push @{"$package\::ISA"} => @$value;
  2         25  
132             }
133             elsif ( $param eq '-attributes' ) {
134 1 50       5 $value = [ $value ] unless ref $value eq 'ARRAY';
135 1         2 for my $attr ( @$value ) {
136             inject( $package, $attr, sub {
137 2     2   5 my $self = shift;
138              
139 2 50       6 croak "$attr() called on class '$self' instead of an instance"
140             unless blessed( $self );
141              
142 2         3 $metrics->{$attr}++;
143 2 100       8 ( $self->{$attr} ) = @_ if @_;
144 2         5 return $self->{$attr};
145 3         10 });
146             }
147             }
148             elsif ( $param eq '-with_new' ) {
149             inject( $package, 'new', sub {
150 5     5   4 my $class = shift;
151 5 50 33     14 croak "Expected hash, received reference to hash"
152             if @_ == 1 and ref $_[0] eq 'HASH';
153 5         7 my %proto = @_;
154 5         6 $metrics->{new}++;
155              
156 5 50       11 croak "new() cannot be called on an instance"
157             if blessed( $class );
158              
159 5         15 return bless( \%proto, $class );
160 6         26 });
161             }
162             }
163              
164             sub _is_sub_ref {
165 27     27   23 my $in = shift;
166 27         32 my $type = ref $in;
167 27         37 my $class = blessed( $in );
168              
169 27 100 100     124 return 1 if $type && $type eq 'CODE';
170 8 100 66     27 return 1 if $class && $class->isa( 'Mock::Quick::Method' );
171 7         20 return 0;
172             }
173              
174             sub override {
175 11     11 1 488 my $self = shift;
176 11         35 my $package = $self->package;
177 11         19 my %pairs = @_;
178 11         10 my @originals;
179 11         17 my $metrics = $self->metrics;
180              
181 11         22 for my $name ( keys %pairs ) {
182 12         13 my $orig_value = $pairs{$name};
183              
184 12 100 100     20 carp "Overriding non-existent method '$name'"
185             if $self->is_takeover && !$package->can($name);
186              
187             my $real_value = _is_sub_ref( $orig_value )
188 9     9   17 ? sub { $metrics->{$name}++; return $orig_value->(@_) }
  9         19  
189 12 100   6   924 : sub { $metrics->{$name}++; return $orig_value };
  6         10  
  6         5  
190              
191 12         25 my $original = $self->original( $name );
192 12         26 inject( $package, $name, $real_value );
193              
194 12         19 push @originals, $original;
195             }
196              
197 11         27 return @originals;
198             }
199              
200             sub original {
201 25     25 1 15 my $self = shift;
202 25         27 my ( $name ) = @_;
203 25 100       48 unless ( exists $self->{$name} ) {
204 9   100     15 $self->{$name} = $self->package->can( $name ) || undef;
205             }
206 25         31 return $self->{$name};
207             }
208              
209             sub restore {
210 11     11 1 958 my $self = shift;
211              
212 11         17 for my $name ( @_ ) {
213 12         23 my $original = $self->original($name);
214 12         18 delete $self->metrics->{$name};
215              
216 12 100       34 if ( $original ) {
217 7 100   2   9 my $sub = _is_sub_ref( $original ) ? $original : sub { $original };
  2         7  
218 7         15 inject( $self->package, $name, $sub );
219             }
220             else {
221 5         11 $self->_clear( $name );
222             }
223             }
224             }
225              
226             sub _clear {
227 5     5   6 my $self = shift;
228 5         6 my ( $name ) = @_;
229 5         11 my $package = $self->package;
230 4     4   15 no strict 'refs';
  4         5  
  4         297  
231 5         7 my $ref = \%{"$package\::"};
  5         11  
232 5         31 delete $ref->{ $name };
233             }
234              
235             sub undefine {
236 14     14 1 1068 my $self = shift;
237 14         20 my $package = $self->package;
238 14 50       18 croak "Refusing to undefine a class that was taken over."
239             if $self->is_takeover;
240 4     4   14 no strict 'refs';
  4         4  
  4         288  
241 14         12 undef( *{"$package\::"} );
  14         109  
242 14 100       102 delete $INC{$self->inc} if $self->is_implement;
243             }
244              
245             sub DESTROY {
246 18     18   4283 my $self = shift;
247 18 100       29 return $self->undefine unless $self->is_takeover;
248              
249 7         19 my $package = $self->package;
250              
251             {
252 4     4   13 no strict 'refs';
  4         6  
  4         80  
  7         5  
253 4     4   11 no warnings 'redefine';
  4         3  
  4         394  
254              
255 7         7 my $ref = \%{"$package\::"};
  7         18  
256 7         26 delete $ref->{MQ_CONTROL};
257             }
258              
259 7         7 for my $sub ( keys %{$self} ) {
  7         16  
260 26 100       76 next if $sub =~ m/^-/;
261 6         13 $self->restore( $sub );
262             }
263             }
264              
265             purge_util();
266              
267             1;
268              
269             __END__