File Coverage

blib/lib/Test/MethodFixtures.pm
Criterion Covered Total %
statement 92 92 100.0
branch 31 36 86.1
condition 11 14 78.5
subroutine 17 17 100.0
pod 4 4 100.0
total 155 163 95.0


line stmt bran cond sub pod time code
1 11     11   248188 use strict;
  11         24  
  11         304  
2 11     11   54 use warnings;
  11         17  
  11         571  
3              
4             package Test::MethodFixtures;
5              
6             our $VERSION = '0.06';
7              
8 11     11   56 use Carp;
  11         21  
  11         1073  
9 11     11   8033 use Hook::LexWrap qw( wrap );
  11         42323  
  11         63  
10 11     11   404 use Scalar::Util qw( weaken blessed );
  11         19  
  11         1156  
11 11     11   7161 use version;
  11         22395  
  11         61  
12              
13 11     11   994 use base 'Class::Accessor::Fast';
  11         20  
  11         8383  
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 13     13   3812 my ( $class, %args ) = @_;
28              
29 13         32 $MODE = $args{'-mode'};
30 13         4145 $STORAGE = $args{'-storage'};
31             }
32              
33             sub new {
34 21     21 1 21059 my $class = shift;
35 21 100       37 my %args = %{ shift() || {} };
  21         132  
36              
37 21   100     160 my $mode = delete $args{mode} || $MODE || 'playback';
38 21   66     107 my $storage = delete $args{storage} || $STORAGE || $DEFAULT_STORAGE;
39              
40             # testing mode
41 21   66     115 $mode = $ENV{TEST_MF_MODE} || $mode;
42              
43 21 50       79 croak "Invalid mode '$MODE'" unless $VALID_MODES{$mode};
44              
45             # storage mechanism
46 21 100       94 $storage = { $storage => {} } unless ref $storage;
47              
48 21 100       158 if ( !blessed $storage ) {
49              
50 20         32 my ( $storage_class, $storage_args ) = %{$storage};
  20         62  
51              
52 20 100       135 $storage_class = __PACKAGE__ . "::Storage::" . $storage_class
53             unless $storage_class =~ s/^\++//;
54              
55 20         1419 eval "require $storage_class";
56 20 50       1244 croak "Unable to load '$storage_class': $@" if $@;
57              
58             $storage = $storage_class->new(
59 20 50       38 { %{ $storage_args || {} },
  20         192  
60             %args, # pass in any remaining arguments
61             }
62             );
63             }
64              
65 21         377 return $class->SUPER::new(
66             { mode => $mode,
67             storage => $storage,
68             _wrapped => {},
69             }
70             );
71             }
72              
73             sub store {
74 23     23 1 31 my $self = shift;
75              
76 23         30 my %args = %{ shift() };
  23         105  
77              
78 23         229 $args{ ref $self } = $self->VERSION;
79 23         101 $args{ ref $self->storage } = $self->storage->VERSION;
80              
81 23         230 $self->storage->store( \%args );
82              
83 23         1673 return $self;
84             }
85              
86             sub retrieve {
87 27     27 1 46 my ( $self, $args ) = @_;
88              
89 27         76 my $stored = $self->storage->retrieve($args);
90              
91 27         1404 my $self_class = ref $self;
92 27         80 my $storage_class = ref $self->storage;
93              
94             _compare_versions( $self_class, $stored->{$self_class} )
95 27 100       191 if exists $stored->{$self_class};
96              
97             _compare_versions( $storage_class, $stored->{$storage_class} )
98 27 100       159 if exists $stored->{$storage_class};
99              
100 27 100 66     120 unless ( defined $stored->{output} || $stored->{no_output} ) {
101 4         34 die "Nothing stored for " . $args->{method};
102             }
103              
104 23         62 return $stored->{output};
105             }
106              
107             sub _compare_versions {
108 46     46   80 my ( $class, $version ) = @_;
109              
110 46 50       929 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 10     10   15 my $value = shift;
118              
119             return sub {
120 50     50   86 my ( $config, @args ) = @_;
121 50 100       111 if ($value) {
122 10         49 my @replace = $value->(@args);
123 10         52 splice( @args, 0, scalar(@replace), @replace );
124             }
125 50         135 return [ $config, @args ];
126 10         49 };
127             }
128              
129             sub mock {
130 10     10 1 14887 my $self = shift;
131              
132 10         20 my $self_ref = $self;
133 10         69 weaken $self_ref; # otherwise reference to $self within wrapped methods
134              
135 10         60 while ( my ( $name, $value ) = splice @_, 0, 2 ) {
136              
137 10         31 my $get_key = _get_key_sub($value);
138              
139             my $pre = sub {
140              
141 49     49   36825 my $mode = $self_ref->mode;
142              
143 49 100 100     439 return if $mode eq 'record' or $mode eq 'passthrough';
144              
145 27         60 my @args = @_; # original arguments that method received
146 27         40 pop @args; # currently undef, will be the return value
147              
148 27         104 my $key = $get_key->( { wantarray => wantarray() }, @args );
149              
150             # add cached value into extra arg,
151             # so original sub will not be called
152 27         49 my $retrieved = eval {
153 27         155 $self_ref->retrieve(
154             { method => $name,
155             key => $key,
156             input => \@args,
157             }
158             );
159             };
160 27 100       84 if ($@) {
161 4 100       45 croak "Unable to retrieve $name - in $mode mode: $@"
162             unless $mode eq 'auto';
163             } else {
164 23         100 $_[-1] = $retrieved;
165             }
166 10         53 };
167              
168             my $post = sub {
169 25     25   487 my $mode = $self_ref->mode;
170              
171 25 100       147 return if $mode eq 'passthrough';
172              
173 23 50       56 croak "Problem retrieving data - reached store() in $mode mode"
174             if $mode eq 'playback';
175              
176 23         59 my (@args) = @_; # origin arguments method received, plus result
177 23         38 my $result = pop @args;
178              
179 23         87 my $key = $get_key->( { wantarray => wantarray() }, @args );
180              
181 23 100       145 $self_ref->store(
182             { method => $name,
183             key => $key,
184             input => \@args,
185             defined wantarray()
186             ? ( output => $result )
187             : ( no_output => 1 ),
188             }
189             );
190 10         45 };
191              
192 10         50 $self->_wrapped->{$name} = wrap $name, #
193             pre => $pre, #
194             post => $post;
195             }
196              
197 10         587 return $self;
198             }
199              
200             1;
201              
202             __END__