File Coverage

blib/lib/Test/MethodFixtures.pm
Criterion Covered Total %
statement 88 90 97.7
branch 27 36 75.0
condition 9 11 81.8
subroutine 16 17 94.1
pod 4 4 100.0
total 144 158 91.1


line stmt bran cond sub pod time code
1 12     12   286729 use strict;
  12         27  
  12         347  
2 12     12   64 use warnings;
  12         21  
  12         611  
3              
4             package Test::MethodFixtures;
5              
6             our $VERSION = '0.05';
7              
8 12     12   62 use Carp;
  12         24  
  12         1109  
9 12     12   8775 use Hook::LexWrap qw( wrap );
  12         48022  
  12         72  
10 12     12   514 use Scalar::Util qw( weaken blessed );
  12         22  
  12         1317  
11 12     12   8380 use version;
  12         25460  
  12         69  
12              
13 12     12   1076 use base 'Class::Accessor::Fast';
  12         22  
  12         9215  
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 14     14   4414 my ( $class, %args ) = @_;
28              
29 14         33 $MODE = $args{'-mode'};
30 14         4364 $STORAGE = $args{'-storage'};
31             }
32              
33             sub new {
34 22     22 1 23426 my $class = shift;
35 22 100       73 my %args = %{ shift() || {} };
  22         143  
36              
37 22   100     174 my $mode = delete $args{mode} || $MODE || 'playback';
38 22   66     92 my $storage = delete $args{storage} || $STORAGE || $DEFAULT_STORAGE;
39              
40             # testing mode
41 22   66     123 $mode = $ENV{TEST_MF_MODE} || $mode;
42              
43 22 50       80 croak "Invalid mode '$MODE'" unless $VALID_MODES{$mode};
44              
45             # storage mechanism
46 22 100       100 $storage = { $storage => {} } unless ref $storage;
47              
48 22 100       156 if ( !blessed $storage ) {
49              
50 21         32 my ( $storage_class, $storage_args ) = %{$storage};
  21         68  
51              
52 21 100       144 $storage_class = __PACKAGE__ . "::Storage::" . $storage_class
53             unless $storage_class =~ s/^\++//;
54              
55 21         1522 eval "require $storage_class";
56 21 50       1490 croak "Unable to load '$storage_class': $@" if $@;
57              
58             $storage = $storage_class->new(
59 21 50       42 { %{ $storage_args || {} },
  21         211  
60             %args, # pass in any remaining arguments
61             }
62             );
63             }
64              
65 22         379 return $class->SUPER::new(
66             { mode => $mode,
67             storage => $storage,
68             _wrapped => {},
69             }
70             );
71             }
72              
73             sub store {
74 22     22 1 35 my $self = shift;
75              
76 22         28 my %args = %{ shift() };
  22         99  
77              
78 22         222 $args{ ref $self } = $self->VERSION;
79 22         100 $args{ ref $self->storage } = $self->storage->VERSION;
80              
81 22         231 $self->storage->store( { %args, version => $VERSION } );
82              
83 22         1735 return $self;
84             }
85              
86             sub retrieve {
87 25     25 1 43 my ( $self, $args ) = @_;
88              
89 25         72 my $stored = $self->storage->retrieve($args);
90              
91             _compare_versions( $self, $stored->{version} )
92 25 50       1326 if exists $stored->{version};
93             _compare_versions( $self->storage, $stored->{storage_version} )
94 25 50       81 if exists $stored->{storage_version};
95              
96 25         52 return $stored->{output};
97             }
98              
99             sub _compare_versions {
100 0     0   0 my ( $class, $version ) = @_;
101              
102 0 0       0 carp "Data saved with a more recent version ($version) of "
103             . ref($class) . "!"
104             if version->parse( $class->VERSION ) < version->parse($version);
105             }
106              
107             # pass in optional coderef to return list of values to use
108             # (for example to stringify objects)
109             sub _get_key_sub {
110 11     11   23 my $value = shift;
111              
112             return sub {
113 47     47   116 my ( $config, @args ) = @_;
114 47 100       117 if ($value) {
115 10         22 my @replace = $value->(@args);
116 10         49 splice( @args, 0, scalar(@replace), @replace );
117             }
118 47         128 return [ $config, @args ];
119 11         54 };
120             }
121              
122             sub mock {
123 11     11 1 15386 my $self = shift;
124              
125 11         23 my $self_ref = $self;
126 11         83 weaken $self_ref; # otherwise reference to $self within wrapped methods
127              
128 11         72 while ( my ( $name, $value ) = splice @_, 0, 2 ) {
129              
130 11         38 my $get_key = _get_key_sub($value);
131              
132             my $pre = sub {
133              
134 46     46   31610 my $mode = $self_ref->mode;
135              
136 46 100 100     465 return if $mode eq 'record' or $mode eq 'passthrough';
137              
138 25         56 my @args = @_; # original arguments that method received
139 25         37 pop @args; # currently undef, will be the return value
140              
141 25         91 my $key = $get_key->( { wantarray => wantarray() }, @args );
142              
143             # add cached value into extra arg,
144             # so original sub will not be called
145 25         48 eval {
146 25         121 my $retrieved = $self_ref->retrieve(
147             { method => $name,
148             key => $key,
149             input => \@args,
150             }
151             );
152              
153 25 100       92 if ( defined $retrieved ) {
154 21         46 $_[-1] = $retrieved;
155             } else {
156 4 100       22 die "Nothing stored for $name"
157             unless $mode eq 'auto';
158             }
159             };
160 25 100       102 if ($@) {
161 1         24 croak "Unable to retrieve $name - in $mode mode: $@";
162             }
163 11         65 };
164              
165             my $post = sub {
166 24     24   489 my $mode = $self_ref->mode;
167              
168 24 100       146 return if $mode eq 'passthrough';
169              
170 22 50       58 croak "Problem retrieving data - reached store() in $mode mode"
171             if $mode eq 'playback';
172              
173 22         41 my (@args) = @_; # origin arguments method received, plus result
174 22         40 my $result = pop @args;
175              
176 22         95 my $key = $get_key->( { wantarray => wantarray() }, @args );
177              
178 22 50       140 $self_ref->store(
179             { method => $name,
180             key => $key,
181             input => \@args,
182             defined wantarray()
183             ? ( output => $result )
184             : ( no_output => 1 ),
185             }
186             );
187 11         52 };
188              
189 11         60 $self->_wrapped->{$name} = wrap $name, #
190             pre => $pre, #
191             post => $post;
192             }
193              
194 11         698 return $self;
195             }
196              
197             1;
198              
199             __END__