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