File Coverage

blib/lib/Test/MethodFixtures.pm
Criterion Covered Total %
statement 96 96 100.0
branch 33 40 82.5
condition 13 17 76.4
subroutine 16 16 100.0
pod 4 4 100.0
total 162 173 93.6


line stmt bran cond sub pod time code
1 13     13   309767 use strict;
  13         34  
  13         345  
2 13     13   67 use warnings;
  13         43  
  13         688  
3              
4             package Test::MethodFixtures;
5              
6             our $VERSION = '0.07';
7              
8 13     13   73 use Carp;
  13         25  
  13         1185  
9 13     13   11615 use Hook::LexWrap qw( wrap );
  13         48818  
  13         71  
10 13     13   487 use Scalar::Util qw( weaken blessed );
  13         23  
  13         1297  
11 13     13   8648 use version;
  13         26743  
  13         79  
12              
13 13     13   1235 use base 'Class::Accessor::Fast';
  13         22  
  13         9689  
14              
15             __PACKAGE__->mk_accessors(qw( mode storage _wrapped ));
16              
17             our $DEFAULT_STORAGE = 'File';
18             our ( $MODE, $STORAGE );
19             my %VALID_MODES = (
20             playback => 1, # default mode
21             record => 1,
22             auto => 1,
23             passthrough => 1,
24             );
25              
26             sub import {
27 15     15   3852 my ( $class, %args ) = @_;
28              
29 15         33 $MODE = $args{'-mode'};
30 15         6376 $STORAGE = $args{'-storage'};
31             }
32              
33             sub new {
34 20     20 1 16411 my $class = shift;
35 20 50       35 my %args = %{ shift() || {} };
  20         123  
36              
37 20   100     156 my $mode = delete $args{mode} || $MODE || 'playback';
38 20   66     83 my $storage = delete $args{storage} || $STORAGE || $DEFAULT_STORAGE;
39              
40             # testing mode
41 20   66     113 $mode = $ENV{TEST_MF_MODE} || $mode;
42              
43 20 50       67 croak "Invalid mode '$MODE'" unless $VALID_MODES{$mode};
44              
45             # storage mechanism
46 20 100       91 $storage = { $storage => {} } unless ref $storage;
47              
48 20 100       143 if ( !blessed $storage ) {
49              
50 19         32 my ( $storage_class, $storage_args ) = %{$storage};
  19         60  
51              
52 19 50       126 $storage_class = __PACKAGE__ . "::Storage::" . $storage_class
53             unless $storage_class =~ s/^\++//;
54              
55 19         1324 eval "require $storage_class";
56 19 50       1699 croak "Unable to load '$storage_class': $@" if $@;
57              
58             $storage = $storage_class->new(
59 19 50       35 { %{ $storage_args || {} },
  19         195  
60             %args, # pass in any remaining arguments
61             }
62             );
63             }
64              
65 20         349 return $class->SUPER::new(
66             { mode => $mode,
67             storage => $storage,
68             _wrapped => {},
69             }
70             );
71             }
72              
73             sub store {
74 25     25 1 34 my $self = shift;
75              
76 25         33 my %args = %{ shift() };
  25         116  
77              
78 25         260 $args{ ref $self } = $self->VERSION;
79 25         110 $args{ ref $self->storage } = $self->storage->VERSION;
80              
81 25         263 $self->storage->store( \%args );
82              
83 25         1866 return $self;
84             }
85              
86             sub retrieve {
87 29     29 1 50 my ( $self, $args ) = @_;
88              
89 29         86 my $stored = $self->storage->retrieve($args);
90              
91 29         1535 my $self_class = ref $self;
92 29         88 my $storage_class = ref $self->storage;
93              
94             _compare_versions( $self_class, $stored->{$self_class} )
95 29 100       227 if exists $stored->{$self_class};
96              
97             _compare_versions( $storage_class, $stored->{$storage_class} )
98 29 100       171 if exists $stored->{$storage_class};
99              
100 29 100 66     140 unless ( defined $stored->{output} || $stored->{no_output} ) {
101 4         37 die "Nothing stored for " . $args->{method};
102             }
103              
104 25         61 return $stored;
105             }
106              
107             sub _compare_versions {
108 50     50   82 my ( $class, $version ) = @_;
109              
110 50 50       1000 carp "Data saved with a more recent version ($version) of $class!"
111             if version->parse( $class->VERSION ) < version->parse($version);
112             }
113              
114             # pass in optional coderef to return list of values to use
115             # (for example to stringify objects)
116             sub _get_key_sub {
117 12     12   21 my $value = shift;
118              
119             return sub {
120 51     51   91 my ( $config, @args ) = @_;
121 51 100       110 if ($value) {
122 10         22 my @replace = $value->(@args);
123 10         56 splice( @args, 0, scalar(@replace), @replace );
124             }
125 51         139 return [ $config, @args ];
126 12         59 };
127             }
128              
129             sub mock {
130 12     12 1 15098 my $self = shift;
131              
132 12         20 my $self_ref = $self;
133 12         83 weaken $self_ref; # otherwise reference to $self within wrapped methods
134              
135 12         73 while ( my ( $name, $value ) = splice @_, 0, 2 ) {
136              
137 12         20 my $original_fn = \&{$name};
  12         51  
138              
139 12         40 my $key_sub = _get_key_sub($value);
140              
141             $self->_wrapped->{$name} = wrap $name, pre => sub {
142              
143 53     53   47764 my $mode = $self_ref->mode;
144              
145 53 100       341 return if $mode eq 'passthrough';
146              
147 51         118 my @args = @_; # original arguments that method received
148 51         69 pop @args; # currently undef, will be the return value
149              
150 51         191 my $key = $key_sub->( { wantarray => wantarray() }, @args );
151              
152 51 100 100     236 if ( $mode eq 'playback' or $mode eq 'auto' ) {
153              
154 29         46 my $retrieved = eval {
155 29         142 $self_ref->retrieve(
156             { method => $name,
157             key => $key,
158             input => \@args,
159             }
160             );
161             };
162 29 100       93 if ($@) {
163 4 100       35 croak "Unable to retrieve $name - in $mode mode: $@"
164             unless $mode eq 'auto';
165             } else {
166              
167             # add cached value into extra arg,
168             # so original sub will not be called
169 25         46 $_[-1] = $retrieved->{output};
170 25         73 return;
171             }
172             }
173              
174 25 50 66     152 if ( $mode eq 'record' or $mode eq 'auto' ) {
175              
176 25         30 my $result;
177 25 100       94 if (wantarray) {
    100          
178 6         16 $result = [ $original_fn->(@args) ];
179             } elsif ( defined wantarray ) {
180 16         46 $result = $original_fn->(@args);
181             } else {
182 3         10 $original_fn->(@args);
183             }
184              
185 25 100       270 $self_ref->store(
186             { method => $name,
187             key => $key,
188             input => \@args,
189             defined wantarray()
190             ? ( output => $result )
191             : ( no_output => 1 ),
192             }
193             );
194              
195 25         66 $_[-1] = $result;
196 25         81 return;
197             }
198 12         107 };
199             }
200              
201 12         655 return $self;
202             }
203              
204             1;
205              
206             __END__