File Coverage

blib/lib/CHI/t/Driver.pm
Criterion Covered Total %
statement 1038 1228 84.5
branch 35 74 47.3
condition 8 20 40.0
subroutine 169 182 92.8
pod 0 65 0.0
total 1250 1569 79.6


line stmt bran cond sub pod time code
1             package CHI::t::Driver;
2             $CHI::t::Driver::VERSION = '0.61';
3 9     9   78 use strict;
  9         24  
  9         284  
4 9     9   65 use warnings;
  9         20  
  9         271  
5 9     9   48 use CHI::Test;
  9         20  
  9         61  
6             use CHI::Test::Util
7 9     9   66 qw(activate_test_logger cmp_bool is_between random_string skip_until);
  9         33  
  9         722  
8 9     9   68 use CHI::Util qw(can_load dump_one_line write_file);
  9         17  
  9         543  
9 9     9   60 use Encode;
  9         67  
  9         842  
10 9     9   71 use File::Spec::Functions qw(tmpdir);
  9         31  
  9         529  
11 9     9   2410 use File::Temp qw(tempdir);
  9         57312  
  9         512  
12 9     9   78 use List::Util qw(shuffle);
  9         20  
  9         537  
13 9     9   58 use Scalar::Util qw(weaken);
  9         43  
  9         444  
14 9     9   61 use Storable qw(dclone);
  9         18  
  9         525  
15 9     9   3420 use Test::Warn;
  9         15281  
  9         580  
16 9     9   64 use Time::HiRes qw(usleep);
  9         18  
  9         85  
17 9     9   1317 use base qw(CHI::Test::Class);
  9         20  
  9         4609  
18              
19             # Flags indicating what each test driver supports
20 435     435 0 3465 sub supports_clear { 1 }
21 7     7 0 42 sub supports_expires_on_backend { 0 }
22 8     8 0 43 sub supports_get_namespaces { 1 }
23              
24             sub standard_keys_and_values : Test(startup) {
25 8     8 0 19168 my ($self) = @_;
26              
27 8         60 my ( $keys_ref, $values_ref ) = $self->set_standard_keys_and_values();
28 8         31 $self->{keys} = $keys_ref;
29 8         23 $self->{values} = $values_ref;
30 8         19 $self->{keynames} = [ keys( %{$keys_ref} ) ];
  8         48  
31 8         22 $self->{key_count} = scalar( @{ $self->{keynames} } );
  8         25  
32 8         86 $self->{all_test_keys} = [ values(%$keys_ref), $self->extra_test_keys() ];
33 8         65 my $cache = $self->new_cache();
34             push(
35 8         32 @{ $self->{all_test_keys} },
36 8         26 $self->process_keys( $cache, @{ $self->{all_test_keys} } )
  8         109  
37             );
38             $self->{all_test_keys_hash} =
39 8         35 { map { ( $_, 1 ) } @{ $self->{all_test_keys} } };
  736         1707  
  8         27  
40 9     9   97 }
  9         22  
  9         52  
41              
42             sub kvpair {
43 175     175 0 413 my $self = shift;
44 175   100     905 my $count = shift || 1;
45              
46             return map {
47 175         602 (
48             $self->{keys}->{medium} . ( $_ == 1 ? '' : $_ ),
49 205 100       2020 $self->{values}->{medium} . ( $_ == 1 ? '' : $_ )
    100          
50             )
51             } ( 1 .. $count );
52             }
53              
54             sub setup : Test(setup) {
55 415     415 0 977463 my $self = shift;
56              
57 415         1889 $self->{cache} = $self->new_cache();
58 415 50       1827 $self->{cache}->clear() if $self->supports_clear();
59 9     9   4568 }
  9         22  
  9         50  
60              
61             sub testing_driver_class {
62 383     383 0 688 my $self = shift;
63 383         861 my $class = ref($self);
64              
65             # By default, take the last part of the classname and use it as driver
66 383         2142 my $driver_class = 'CHI::Driver::' . ( split( '::', $class ) )[-1];
67 383         3629 return $driver_class;
68             }
69              
70             sub testing_chi_root_class {
71 446     446 0 2053 return 'CHI';
72             }
73              
74             sub new_cache {
75 433     433 0 885 my $self = shift;
76              
77 433         1325 return $self->testing_chi_root_class->new( $self->new_cache_options(), @_ );
78             }
79              
80             sub new_cleared_cache {
81 94     94 0 1211 my $self = shift;
82              
83 94         424 my $cache = $self->new_cache(@_);
84 94         2026 $cache->clear();
85 94         9936 return $cache;
86             }
87              
88             sub new_cache_options {
89 720     720 0 1504 my $self = shift;
90              
91             return (
92 720         2568 driver => '+' . $self->testing_driver_class(),
93             on_get_error => 'die',
94             on_set_error => 'die'
95             );
96             }
97              
98             sub set_standard_keys_and_values {
99 8     8 0 22 my $self = shift;
100              
101 8         20 my ( %keys, %values );
102 8         51 my @mixed_chars = ( 32 .. 48, 57 .. 65, 90 .. 97, 122 .. 126, 240 );
103              
104             %keys = (
105             'space' => ' ',
106             'newline' => "\n",
107             'char' => 'a',
108             'zero' => 0,
109             'one' => 1,
110             'medium' => 'medium',
111 320         558 'mixed' => join( "", map { chr($_) } @mixed_chars ),
112 8         87 'binary' => join( "", map { chr($_) } ( 129 .. 255 ) ),
  1016         1663  
113             'large' => scalar( 'ab' x 256 ),
114             'empty' => 'empty',
115             'arrayref' => [ 1, 2 ],
116             'hashref' => { foo => [ 'bar', 'baz' ] },
117             'utf8' => "Have \x{263a} a nice day",
118             );
119              
120             %values = map {
121 8 100       107 ( $_, ref( $keys{$_} ) ? $keys{$_} : scalar( reverse( $keys{$_} ) ) )
  104         327  
122             } keys(%keys);
123 8         37 $values{empty} = '';
124              
125 8         41 return ( \%keys, \%values );
126             }
127              
128             # Extra keys (beyond the standard keys above) that we may use in these
129             # tests. We need to adhere to this for the benefit of drivers that don't
130             # support get_keys (like memcached) - they simulate get_keys(), clear(),
131             # etc. by using this fixed list of keys.
132             #
133             sub extra_test_keys {
134 8     8 0 64 my ($class) = @_;
135             return (
136             '', '2',
137             'medium2', 'foo',
138             'hashref', 'test_namespace_types',
139             "utf8", "encoded",
140 24         83 "binary", ( map { "done$_" } ( 0 .. 2 ) ),
141 8         38 ( map { "key$_" } ( 0 .. 20 ) )
  168         469  
142             );
143             }
144              
145             sub set_some_keys {
146 58     58 0 182 my ( $self, $c ) = @_;
147              
148 58         137 foreach my $keyname ( @{ $self->{keynames} } ) {
  58         227  
149 754         8652 $c->set( $self->{keys}->{$keyname}, $self->{values}->{$keyname} );
150             }
151             }
152              
153             sub test_encode : Tests {
154 8     8 0 6013 my $self = shift;
155 8         40 my $cache = $self->new_cleared_cache();
156              
157 8         43 my $utf8 = $self->{keys}->{utf8};
158 8         57 my $encoded = encode( utf8 => $utf8 );
159 8         439 my $binary_off = $self->{keys}->{binary};
160 8         88 my $binary_on = substr( $binary_off . $utf8, 0, length($binary_off) );
161              
162 8         71 ok( $binary_off eq $binary_on, "binary_off eq binary_on" );
163 8         3816 ok( !Encode::is_utf8($binary_off), "!is_utf8(binary_off)" );
164 8         2824 ok( Encode::is_utf8($binary_on), "is_utf8(binary_on)" );
165              
166             # Key maps to same thing whether encoded or non-encoded
167             #
168 8         2778 my $value = time;
169 8         156 $cache->set( $utf8, $value );
170 8         110 is( $cache->get($utf8), $value, "get" );
171 8         4006 is( $cache->get($encoded),
172             $value, "encoded and non-encoded map to same value" );
173              
174             # Key maps to same thing whether utf8 flag is off or on
175             #
176             # Commenting out for now - this is broken on FastMmap and
177             # DBI drivers (at least), and not entirely sure whether or
178             # with what priority we should demand this behavior.
179             #
180 8         3242 if (0) {
181             $cache->set( $binary_off, $value );
182             is( $cache->get($binary_off), $value, "get binary_off" );
183             is( $cache->get($binary_on),
184             $value, "binary_off and binary_on map to same value" );
185             $cache->clear($binary_on);
186             ok( !$cache->get($binary_off), "cleared binary_off" ); #
187             }
188              
189             # Value is maintained as a utf8 or binary string, in scalar or in arrayref
190 8         122 $cache->set( "utf8", $utf8 );
191 8         97 is( $cache->get("utf8"), $utf8, "utf8 in scalar" );
192 8         3748 $cache->set( "utf8", [$utf8] );
193 8         99 is( $cache->get("utf8")->[0], $utf8, "utf8 in arrayref" );
194              
195 8         3560 $cache->set( "encoded", $encoded );
196 8         98 is( $cache->get("encoded"), $encoded, "encoded in scalar" );
197 8         3550 $cache->set( "encoded", [$encoded] );
198 8         101 is( $cache->get("encoded")->[0], $encoded, "encoded in arrayref" );
199              
200             # Value retrieves as same thing whether stored with utf8 flag off or on
201             #
202 8         3574 $cache->set( "binary", $binary_off );
203 8         95 is( $cache->get("binary"), $binary_on, "stored binary_off = binary_on" );
204 8         3525 $cache->set( "binary", $binary_on );
205 8         97 is( $cache->get("binary"), $binary_off, "stored binary_on = binary_off" );
206 9     9   11838 }
  9         29  
  9         45  
207              
208             sub test_simple : Tests {
209 12     12 0 6130 my $self = shift;
210 12   33     86 my $cache = shift || $self->{cache};
211              
212 12         182 ok( $cache->set( $self->{keys}->{medium}, $self->{values}->{medium} ) );
213 10         5128 is( $cache->get( $self->{keys}->{medium} ), $self->{values}->{medium} );
214 9     9   3508 }
  9         20  
  9         49  
215              
216             sub test_driver_class : Tests {
217 8     8 0 6208 my $self = shift;
218 8         30 my $cache = $self->{cache};
219              
220 8         60 isa_ok( $cache, 'CHI::Driver' );
221 8         4613 isa_ok( $cache, $cache->driver_class );
222 8         3341 can_ok( $cache, 'get', 'set', 'remove', 'clear', 'expire' );
223 9     9   3105 }
  9         24  
  9         38  
224              
225             sub test_key_types : Tests {
226 8     8 0 7480 my $self = shift;
227 8         31 my $cache = $self->{cache};
228 8         107 $self->num_tests( $self->{key_count} * 9 + 1 );
229              
230 8         1117 my @keys_set;
231             my $check_keys_set = sub {
232 216     216   459 my $desc = shift;
233 216         885 cmp_set( [ $cache->get_keys ], \@keys_set, "checking keys $desc" );
234 8         60 };
235              
236 8         34 $check_keys_set->("before sets");
237 8         6211 foreach my $keyname ( @{ $self->{keynames} } ) {
  8         43  
238 104         64218 my $key = $self->{keys}->{$keyname};
239 104         503 my $value = $self->{values}->{$keyname};
240 104         1428 ok( !defined $cache->get($key), "miss for key '$keyname'" );
241 104         39841 is( $cache->set( $key, $value ), $value, "set for key '$keyname'" );
242 104         41666 push( @keys_set, $self->process_keys( $cache, $key ) );
243 104         527 $check_keys_set->("after set of key '$keyname'");
244 104         884115 cmp_deeply( $cache->get($key), $value, "hit for key '$keyname'" );
245             }
246              
247 8         6690 foreach my $keyname ( reverse @{ $self->{keynames} } ) {
  8         38  
248 104         738119 my $key = $self->{keys}->{$keyname};
249 104         4102 $cache->remove($key);
250 104         4390 ok( !defined $cache->get($key),
251             "miss after remove for key '$keyname'" );
252 104         40145 pop(@keys_set);
253 104         448 $check_keys_set->("after removal of key '$keyname'");
254             }
255              
256             # Confirm that transform_key is idempotent
257             #
258 8         9752 foreach my $keyname ( @{ $self->{keynames} } ) {
  8         40  
259 104         63945 my $key = $self->{keys}->{$keyname};
260 104         294 my $value = $self->{values}->{$keyname};
261 104         443 is(
262             $cache->transform_key( $cache->transform_key($key) ),
263             $cache->transform_key($key),
264             "transform_key is idempotent for '$keyname'"
265             );
266 104         40031 $cache->clear();
267 104         23205 $cache->set( $key, $value );
268 104         480 is( scalar( $cache->get_keys() ), 1, "exactly one key" );
269 104         41365 cmp_deeply( $cache->get( ( $cache->get_keys )[0] ),
270             $value, "get with get_keys[0] got same value" );
271             }
272 9     9   6758 }
  9         26  
  9         55  
273              
274             sub test_deep_copy : Tests {
275 7     7 0 13207 my $self = shift;
276 7         21 my $cache = $self->{cache};
277              
278 7         41 $self->set_some_keys($cache);
279 7         36 foreach my $keyname (qw(arrayref hashref)) {
280 14         2810 my $key = $self->{keys}->{$keyname};
281 14         45 my $value = $self->{values}->{$keyname};
282 14         190 cmp_deeply( $cache->get($key), $value,
283             "get($key) returns original data structure" );
284 14         77137 cmp_deeply( $cache->get($key), $cache->get($key),
285             "multiple get($key) return same data structure" );
286 14         29945 isnt( $cache->get($key), $value,
287             "get($key) does not return original reference" );
288 14         5913 isnt( $cache->get($key), $cache->get($key),
289             "multiple get($key) do not return same reference" );
290             }
291              
292 7         3237 my $struct = { a => [ 1, 2 ], b => [ 4, 5 ] };
293 7         579 my $struct2 = dclone($struct);
294 7         145 $cache->set( 'hashref', $struct );
295 7         24 push( @{ $struct->{a} }, 3 );
  7         31  
296 7         27 delete( $struct->{b} );
297 7         92 cmp_deeply( $cache->get('hashref'),
298             $struct2,
299             "altering original set structure does not affect cached copy" );
300 9     9   4447 }
  9         25  
  9         46  
301              
302             sub test_expires_immediately : Tests {
303 8     8 0 6221 my $self = shift;
304              
305             return 'author testing only - timing is unreliable'
306 8 50       58 unless ( $ENV{AUTHOR_TESTING} );
307              
308             # expires_in default should be ignored
309 0         0 my $cache = $self->new_cache( expires_in => '1 hour' );
310              
311             # Expires immediately
312             my $test_expires_immediately = sub {
313 0     0   0 my ($set_option) = @_;
314 0         0 my ( $key, $value ) = $self->kvpair();
315 0         0 my $desc = dump_one_line($set_option);
316 0         0 is( $cache->set( $key, $value, $set_option ), $value, "set ($desc)" );
317 0         0 is_between(
318             $cache->get_expires_at($key),
319             time() - 4,
320             time(), "expires_at ($desc)"
321             );
322 0         0 ok( $cache->exists_and_is_expired($key), "is_expired ($desc)" );
323 0         0 ok( !defined $cache->get($key), "immediate miss ($desc)" );
324 0         0 };
325 0         0 $test_expires_immediately->(0);
326 0         0 $test_expires_immediately->(-1);
327 0         0 $test_expires_immediately->("0 seconds");
328 0         0 $test_expires_immediately->("0 hours");
329 0         0 $test_expires_immediately->("-1 seconds");
330 0         0 $test_expires_immediately->( { expires_in => "0 seconds" } );
331 0         0 $test_expires_immediately->( { expires_at => time - 1 } );
332 0         0 $test_expires_immediately->("now");
333 9     9   4780 }
  9         21  
  9         53  
334              
335             sub test_expires_shortly : Tests {
336 8     8 0 7782 my $self = shift;
337              
338             return 'author testing only - timing is unreliable'
339 8 50       61 unless ( $ENV{AUTHOR_TESTING} );
340              
341             # expires_in default should be ignored
342 0         0 my $cache = $self->new_cache( expires_in => '1 hour' );
343              
344             # Expires shortly (real time)
345             my $test_expires_shortly = sub {
346 0     0   0 my ($set_option) = @_;
347 0         0 my ( $key, $value ) = $self->kvpair();
348 0         0 my $desc = "set_option = " . dump_one_line($set_option);
349 0         0 my $start_time = time();
350 0         0 is( $cache->set( $key, $value, $set_option ), $value, "set ($desc)" );
351 0         0 is( $cache->get($key), $value, "hit ($desc)" );
352 0         0 is_between(
353             $cache->get_expires_at($key),
354             $start_time + 1,
355             $start_time + 8,
356             "expires_at ($desc)"
357             );
358 0         0 ok( !$cache->exists_and_is_expired($key), "not expired ($desc)" );
359 0         0 ok( $cache->is_valid($key), "valid ($desc)" );
360              
361             # Only bother sleeping and expiring for one of the variants
362 0 0       0 if ( $set_option eq "3 seconds" ) {
363 0         0 sleep(3);
364 0         0 ok( !defined $cache->get($key), "miss after 2 seconds ($desc)" );
365 0         0 ok( $cache->exists_and_is_expired($key), "is_expired ($desc)" );
366 0         0 ok( !$cache->is_valid($key), "invalid ($desc)" );
367             }
368 0         0 };
369 0         0 $test_expires_shortly->(3);
370 0         0 $test_expires_shortly->("3 seconds");
371 0         0 $test_expires_shortly->( { expires_at => time + 3 } );
372 9     9   5248 }
  9         23  
  9         42  
373              
374             sub test_expires_later : Tests {
375 8     8 0 6155 my $self = shift;
376              
377             return 'author testing only - timing is unreliable'
378 8 50       61 unless ( $ENV{AUTHOR_TESTING} );
379              
380             # expires_in default should be ignored
381 0         0 my $cache = $self->new_cache( expires_in => '1s' );
382              
383             # Expires later (test time)
384             my $test_expires_later = sub {
385 0     0   0 my ($set_option) = @_;
386 0         0 my ( $key, $value ) = $self->kvpair();
387 0         0 my $desc = "set_option = " . dump_one_line($set_option);
388 0         0 is( $cache->set( $key, $value, $set_option ), $value, "set ($desc)" );
389 0         0 is( $cache->get($key), $value, "hit ($desc)" );
390 0         0 my $start_time = time();
391 0         0 is_between(
392             $cache->get_expires_at($key),
393             $start_time + 3580,
394             $start_time + 3620,
395             "expires_at ($desc)"
396             );
397 0         0 ok( !$cache->exists_and_is_expired($key), "not expired ($desc)" );
398 0         0 ok( $cache->is_valid($key), "valid ($desc)" );
399 0         0 local $CHI::Driver::Test_Time = $start_time + 3590;
400 0         0 ok( !$cache->exists_and_is_expired($key), "not expired ($desc)" );
401 0         0 ok( $cache->is_valid($key), "valid ($desc)" );
402 0         0 local $CHI::Driver::Test_Time = $start_time + 3610;
403 0         0 ok( !defined $cache->get($key), "miss after 1 hour ($desc)" );
404 0         0 ok( $cache->exists_and_is_expired($key), "is_expired ($desc)" );
405 0         0 ok( !$cache->is_valid($key), "invalid ($desc)" );
406 0         0 };
407 0         0 $test_expires_later->(3600);
408 0         0 $test_expires_later->("1 hour");
409 0         0 $test_expires_later->( { expires_at => time + 3600 } );
410 9     9   6030 }
  9         20  
  9         52  
411              
412             sub test_expires_never : Tests {
413 8     8 0 7760 my $self = shift;
414 8         19 my $cache;
415              
416             # Expires never (will fail in 2037)
417 8         42 my ( $key, $value ) = $self->kvpair();
418             my $test_expires_never = sub {
419 16     16   54 my (@set_options) = @_;
420 16         221 $cache->set( $key, $value, @set_options );
421 16         88 ok(
422             $cache->get_expires_at($key) >
423             time + Time::Duration::Parse::parse_duration('1 year'),
424             "expires never"
425             );
426 16         9256 ok( !$cache->exists_and_is_expired($key), "not expired" );
427 16         6051 ok( $cache->is_valid($key), "valid" );
428 8         53 };
429              
430             # never is default
431 8         37 $cache = $self->new_cache();
432 8         46 $test_expires_never->();
433              
434             # expires_in default should be ignored when never passed to set (RT #67970)
435 8         3019 $cache = $self->new_cache( expires_in => '1s' );
436 8         38 $test_expires_never->('never');
437 9     9   4090 }
  9         25  
  9         55  
438              
439             sub test_expires_defaults : Tests {
440 8     8 0 7771 my $self = shift;
441              
442 8         23 my $start_time = time();
443 8         26 local $CHI::Driver::Test_Time = $start_time;
444 8         19 my $cache;
445              
446             my $set_and_confirm_expires_at = sub {
447 32     32   111 my ( $expected_expires_at, $desc ) = @_;
448 32         132 my ( $key, $value ) = $self->kvpair();
449 32         398 $cache->set( $key, $value );
450 32         171 is( $cache->get_expires_at($key), $expected_expires_at, $desc );
451 32         15020 $cache->clear();
452 8         49 };
453              
454 8         39 $cache = $self->new_cache( expires_in => 10 );
455 8         50 $set_and_confirm_expires_at->(
456             $start_time + 10,
457             "after expires_in constructor option"
458             );
459 8         1568 $cache->expires_in(20);
460 8         222 $set_and_confirm_expires_at->( $start_time + 20,
461             "after expires_in method" );
462              
463 8         1432 $cache = $self->new_cache( expires_at => $start_time + 30 );
464 8         49 $set_and_confirm_expires_at->(
465             $start_time + 30,
466             "after expires_at constructor option"
467             );
468 8         1386 $cache->expires_at( $start_time + 40 );
469 8         32 $set_and_confirm_expires_at->( $start_time + 40,
470             "after expires_at method" );
471 9     9   4072 }
  9         38  
  9         58  
472              
473             sub test_expires_manually : Tests {
474 8     8 0 6039 my $self = shift;
475 8         35 my $cache = $self->{cache};
476              
477 8         42 my ( $key, $value ) = $self->kvpair();
478 8         31 my $desc = "expires manually";
479 8         161 $cache->set( $key, $value );
480 8         109 is( $cache->get($key), $value, "hit ($desc)" );
481 8         3944 $cache->expire($key);
482 8         132 ok( !defined $cache->get($key), "miss after expire ($desc)" );
483 8         3692 ok( !$cache->is_valid($key), "invalid after expire ($desc)" );
484 9     9   3577 }
  9         31  
  9         71  
485              
486             sub test_expires_conditionally : Tests {
487 8     8 0 10950 my $self = shift;
488 8         26 my $cache = $self->{cache};
489              
490             # Expires conditionally
491             my $test_expires_conditionally = sub {
492 32     32   101 my ( $code, $cond_desc, $expect_expire ) = @_;
493              
494 32         130 my ( $key, $value ) = $self->kvpair();
495 32         120 my $desc = "expires conditionally ($cond_desc)";
496 32         453 $cache->set( $key, $value );
497 32 100       353 is(
498             $cache->get( $key, expire_if => $code ),
499             $expect_expire ? undef : $value,
500             "get result ($desc)"
501             );
502              
503 32         16897 is( $cache->get($key), $value, "hit after expire_if ($desc)" );
504              
505 8         59 };
506 8         27 my $time = time();
507 8     9   50 $test_expires_conditionally->( sub { 1 }, 'true', 1 );
  9         44  
508 8     8   3222 $test_expires_conditionally->( sub { 0 }, 'false', 0 );
  8         36  
509             $test_expires_conditionally->(
510 9     9   46 sub { $_[0]->created_at >= $time },
511 8         3301 'created_at >= now', 1
512             );
513             $test_expires_conditionally->(
514 8     8   44 sub { $_[0]->created_at < $time },
515 8         3234 'created_at < now', 0
516             );
517 9     9   5023 }
  9         25  
  9         60  
518              
519             sub test_expires_variance : Tests {
520 8     8 0 5949 my $self = shift;
521 8         30 my $cache = $self->{cache};
522              
523 8         77 my $start_time = time();
524 8         28 my $expires_at = $start_time + 10;
525 8         44 my ( $key, $value ) = $self->kvpair();
526 8         154 $cache->set( $key, $value,
527             { expires_at => $expires_at, expires_variance => 0.5 } );
528 8         53 is( $cache->get_object($key)->expires_at(),
529             $expires_at, "expires_at = $start_time" );
530 8         3602 is(
531             $cache->get_object($key)->early_expires_at(),
532             $start_time + 5,
533             "early_expires_at = $start_time + 5"
534             );
535              
536 8         2978 my %expire_count;
537 8         72 for ( my $time = $start_time + 3 ; $time <= $expires_at + 1 ; $time++ ) {
538 72         198 local $CHI::Driver::Test_Time = $time;
539 72         204 for ( my $i = 0 ; $i < 100 ; $i++ ) {
540 7200 100       70540 if ( !defined $cache->get($key) ) {
541 3154         9694 $expire_count{$time}++;
542             }
543             }
544             }
545 8         79 for ( my $time = $start_time + 3 ; $time <= $start_time + 5 ; $time++ ) {
546 24         8325 ok( !$expire_count{$time}, "got no expires at $time" );
547             }
548 8         2796 for ( my $time = $start_time + 7 ; $time <= $start_time + 8 ; $time++ ) {
549 16   33     3059 ok( $expire_count{$time} > 0 && $expire_count{$time} < 100,
550             "got some expires at $time" );
551             }
552 8         2802 for ( my $time = $expires_at ; $time <= $expires_at + 1 ; $time++ ) {
553 16         2856 ok( $expire_count{$time} == 100, "got all expires at $time" );
554             }
555 9     9   5275 }
  9         25  
  9         58  
556              
557             sub test_not_in_cache : Tests {
558 8     8 0 6564 my $self = shift;
559 8         27 my $cache = $self->{cache};
560              
561 8         49 ok( !defined $cache->get_object('not in cache') );
562 8         3750 ok( !defined $cache->get_expires_at('not in cache') );
563 8         2870 ok( !$cache->is_valid('not in cache') );
564 9     9   3022 }
  9         34  
  9         57  
565              
566             sub test_serialize : Tests {
567 7     7 0 6656 my $self = shift;
568 7         24 my $cache = $self->{cache};
569 7         45 $self->num_tests( $self->{key_count} );
570              
571 7         808 $self->set_some_keys($cache);
572 7         24 foreach my $keyname ( @{ $self->{keynames} } ) {
  7         37  
573 91 100 100     33102 my $expect_transformed =
    100          
574             ( $keyname eq 'arrayref' || $keyname eq 'hashref' ) ? 1
575             : ( $keyname eq 'utf8' ) ? 2
576             : 0;
577             is(
578 91         413 $cache->get_object( $self->{keys}->{$keyname} )->_is_transformed(),
579             $expect_transformed,
580             "is_transformed = $expect_transformed ($keyname)"
581             );
582             }
583 9     9   3671 }
  9         32  
  9         57  
584              
585             {
586             package DummySerializer;
587             $DummySerializer::VERSION = '0.61';
588       0     sub serialize { }
589       0     sub deserialize { }
590             }
591              
592             sub test_serializers : Tests {
593 7     7 0 16256 my ($self) = @_;
594              
595 7 50       54 unless ( can_load('Data::Serializer') ) {
596 7         48 $self->num_tests(1);
597 7         801 return 'Data::Serializer not installed';
598             }
599              
600 0         0 my @modes = (qw(string hash object));
601 0         0 my @variants = (qw(Storable Data::Dumper YAML));
602 0         0 @variants = grep { can_load($_) } @variants;
  0         0  
603 0         0 ok( scalar(@variants), "some variants ok" );
604              
605 0         0 my $initial_count = 5;
606 0         0 my $test_key_types_count = $self->{key_count};
607 0         0 my $test_count = $initial_count +
608             scalar(@variants) * scalar(@modes) * ( 1 + $test_key_types_count );
609              
610 0         0 my $cache1 = $self->new_cache();
611 0         0 isa_ok( $cache1->serializer, 'CHI::Serializer::Storable' );
612 0         0 my $cache2 = $self->new_cache();
613 0         0 is( $cache1->serializer, $cache2->serializer,
614             'same serializer returned from two objects' );
615              
616             throws_ok(
617             sub {
618 0     0   0 $self->new_cache( serializer => [1] );
619             },
620 0         0 qr/Validation failed for|isa check for ".*?" failed/,
621             "invalid serializer"
622             );
623             lives_ok(
624 0     0   0 sub { $self->new_cache( serializer => bless( {}, 'DummySerializer' ) ) }
625             ,
626 0         0 "valid dummy serializer"
627             );
628              
629 0         0 foreach my $mode (@modes) {
630 0         0 foreach my $variant (@variants) {
631 0 0       0 my $serializer_param = (
    0          
632             $mode eq 'string' ? $variant
633             : $mode eq 'hash' ? { serializer => $variant }
634             : Data::Serializer->new( serializer => $variant )
635             );
636 0         0 my $cache = $self->new_cache( serializer => $serializer_param );
637 0         0 is( $cache->serializer->serializer,
638             $variant, "serializer = $variant, mode = $mode" );
639 0         0 $self->{cache} = $cache;
640              
641 0         0 foreach my $keyname ( @{ $self->{keynames} } ) {
  0         0  
642 0         0 my $key = $self->{keys}->{$keyname};
643 0         0 my $value = $self->{values}->{$keyname};
644 0         0 $cache->set( $key, $value );
645 0         0 cmp_deeply( $cache->get($key), $value,
646             "hit for key '$keyname'" );
647             }
648              
649 0         0 $self->num_tests($test_count);
650             }
651             }
652 9     9   7509 }
  9         20  
  9         80  
653              
654             sub test_namespaces : Tests {
655 8     8 0 6169 my $self = shift;
656 8         22 my $cache = $self->{cache};
657              
658 8         38 my $cache0 = $self->new_cache();
659 8         119 is( $cache0->namespace, 'Default', 'namespace defaults to "Default"' );
660              
661 8         3770 my ( $ns1, $ns2, $ns3 ) = ( 'ns1', 'ns2', 'ns3' );
662             my ( $cache1, $cache1a, $cache2, $cache3 ) =
663 8         35 map { $self->new_cache( namespace => $_ ) } ( $ns1, $ns1, $ns2, $ns3 );
  32         96  
664             cmp_deeply(
665 8         41 [ map { $_->namespace } ( $cache1, $cache1a, $cache2, $cache3 ) ],
  32         137  
666             [ $ns1, $ns1, $ns2, $ns3 ],
667             'cache->namespace()'
668             );
669 8         13360 $self->set_some_keys($cache1);
670 8         230 cmp_deeply(
671             $cache1->dump_as_hash(),
672             $cache1a->dump_as_hash(),
673             'cache1 and cache1a are same cache'
674             );
675 8         36606 cmp_deeply( [ $cache2->get_keys() ],
676             [], 'cache2 empty after setting keys in cache1' );
677 8         11737 $cache3->set( $self->{keys}->{medium}, 'different' );
678             is(
679             $cache1->get('medium'),
680             $self->{values}->{medium},
681 8         108 'cache1{medium} = medium'
682             );
683 8         3629 is( $cache3->get('medium'), 'different', 'cache1{medium} = different' );
684              
685 8 50       3201 if ( $self->supports_get_namespaces() ) {
686              
687             # get_namespaces may or may not automatically include empty namespaces
688 8         281 cmp_deeply(
689             [ $cache1->get_namespaces() ],
690             supersetof( $ns1, $ns3 ),
691             "get_namespaces contains $ns1 and $ns3"
692             );
693              
694 8         13317 foreach my $c ( $cache0, $cache1, $cache1a, $cache2, $cache3 ) {
695 40         93022 cmp_set(
696             [ $cache->get_namespaces() ],
697             [ $c->get_namespaces() ],
698             'get_namespaces the same regardless of which cache asks'
699             );
700             }
701             }
702             else {
703             throws_ok(
704 0     0   0 sub { $cache1->get_namespaces() },
705 0         0 qr/not supported/,
706             "get_namespaces not supported"
707             );
708 0         0 SKIP: { skip "get_namespaces not supported", 5 }
  0         0  
709             }
710 9     9   5848 }
  9         32  
  9         89  
711              
712             sub test_persist : Tests {
713 8     8 0 7520 my $self = shift;
714 8         28 my $cache = $self->{cache};
715              
716 8         21 my $hash;
717             {
718 8         18 my $cache1 = $self->new_cache();
  8         35  
719 8         139 $self->set_some_keys($cache1);
720 8         55 $hash = $cache1->dump_as_hash();
721             }
722 8         60 my $cache2 = $self->new_cache();
723 8         53 cmp_deeply(
724             $hash,
725             $cache2->dump_as_hash(),
726             'cache persisted between cache object creations'
727             );
728 9     9   3295 }
  9         25  
  9         46  
729              
730             sub test_multi : Tests {
731 8     8 0 6264 my $self = shift;
732 8         28 my $cache = $self->{cache};
733              
734             my ( $keys, $values, $keynames ) =
735 8         49 ( $self->{keys}, $self->{values}, $self->{keynames} );
736              
737 8         39 my @ordered_keys = map { $keys->{$_} } @{$keynames};
  104         248  
  8         31  
738             my @ordered_values =
739 8         24 map { $values->{$_} } @{$keynames};
  104         196  
  8         26  
740             my %ordered_scalar_key_values =
741 88         270 map { ( $keys->{$_}, $values->{$_} ) }
742 8         24 grep { !ref( $keys->{$_} ) } @{$keynames};
  104         213  
  8         35  
743              
744 8         95 cmp_deeply( $cache->get_multi_arrayref( ['foo'] ),
745             [undef], "get_multi_arrayref before set" );
746              
747 8         13601 $cache->set_multi( \%ordered_scalar_key_values );
748 8         117 $cache->set( $keys->{arrayref}, $values->{arrayref} );
749 8         107 $cache->set( $keys->{hashref}, $values->{hashref} );
750              
751 8         69 cmp_deeply( $cache->get_multi_arrayref( \@ordered_keys ),
752             \@ordered_values, "get_multi_arrayref" );
753 8         36020 cmp_deeply( $cache->get( $ordered_keys[0] ),
754             $ordered_values[0], "get one after set_multi" );
755 8         4852 cmp_deeply(
756             $cache->get_multi_arrayref( [ reverse @ordered_keys ] ),
757             [ reverse @ordered_values ],
758             "get_multi_arrayref"
759             );
760             cmp_deeply(
761 8         35057 $cache->get_multi_hashref( [ grep { !ref($_) } @ordered_keys ] ),
  104         239  
762             \%ordered_scalar_key_values, "get_multi_hashref" );
763 8         15876 cmp_set(
764             [ $cache->get_keys ],
765             [ $self->process_keys( $cache, @ordered_keys ) ],
766             "get_keys after set_multi"
767             );
768              
769 8         160499 $cache->remove_multi( \@ordered_keys );
770 8         270 cmp_deeply(
771             $cache->get_multi_arrayref( \@ordered_keys ),
772             [ (undef) x scalar(@ordered_values) ],
773             "get_multi_arrayref after remove_multi"
774             );
775 8         14444 cmp_set( [ $cache->get_keys ], [], "get_keys after remove_multi" );
776 9     9   5538 }
  9         21  
  9         91  
777              
778             sub test_multi_no_keys : Tests {
779 8     8 0 14341 my $self = shift;
780 8         25 my $cache = $self->{cache};
781              
782 8         75 cmp_deeply( $cache->get_multi_arrayref( [] ),
783             [], "get_multi_arrayref (no args)" );
784 8         12572 cmp_deeply( $cache->get_multi_hashref( [] ),
785             {}, "get_multi_hashref (no args)" );
786 8     8   11936 lives_ok { $cache->set_multi( {} ) } "set_multi (no args)";
  8         396  
787 8     8   3239 lives_ok { $cache->remove_multi( [] ) } "remove_multi (no args)";
  8         268  
788 9     9   3443 }
  9         19  
  9         51  
789              
790             sub test_l1_cache : Tests {
791 6     6 0 5591 my $self = shift;
792 6         26 my @keys = map { "key$_" } ( 0 .. 2 );
  18         63  
793 6         22 my @values = map { "value$_" } ( 0 .. 2 );
  18         52  
794 6         17 my ( $cache, $l1_cache );
795              
796 6 50       24 return "skipping - no support for clear" unless $self->supports_clear();
797              
798             my $test_l1_cache = sub {
799              
800 12     12   144 is( $l1_cache->subcache_type, "l1_cache", "subcache_type = l1_cache" );
801              
802             # Get on cache should populate l1 cache
803             #
804 12         5224 $cache->set( $keys[0], $values[0] );
805 12         169 $l1_cache->clear();
806 12         1336 ok( !$l1_cache->get( $keys[0] ), "l1 miss after clear" );
807 12         5895 is( $cache->get( $keys[0] ),
808             $values[0], "primary hit after primary set" );
809 12         5088 is( $l1_cache->get( $keys[0] ), $values[0],
810             "l1 hit after primary get" );
811              
812             # Primary cache should be reading l1 cache first
813             #
814 12         4877 $l1_cache->set( $keys[0], $values[1] );
815 12         297 is( $cache->get( $keys[0] ),
816             $values[1], "got new value set explicitly in l1 cache" );
817 12         5225 $l1_cache->remove( $keys[0] );
818 12         533 is( $cache->get( $keys[0] ), $values[0], "got old value again" );
819              
820 12         5223 $cache->clear();
821 12         1503 ok( !$cache->get( $keys[0] ), "miss after clear" );
822 12         4594 ok( !$l1_cache->get( $keys[0] ), "miss after clear" );
823              
824             # get_multi_* - one from l1 cache, one from primary cache, one miss
825             #
826 12         4830 $cache->set( $keys[0], $values[0] );
827 12         368 $cache->set( $keys[1], $values[1] );
828 12         306 $l1_cache->remove( $keys[0] );
829 12         264 $l1_cache->set( $keys[1], $values[2] );
830              
831 12         403 cmp_deeply(
832             $cache->get_multi_arrayref( [ $keys[0], $keys[1], $keys[2] ] ),
833             [ $values[0], $values[2], undef ],
834             "get_multi_arrayref"
835             );
836 12         21367 cmp_deeply(
837             $cache->get_multi_hashref( [ $keys[0], $keys[1], $keys[2] ] ),
838             {
839             $keys[0] => $values[0],
840             $keys[1] => $values[2],
841             $keys[2] => undef
842             },
843             "get_multi_hashref"
844             );
845              
846 12         26454 $self->_test_logging_with_l1_cache( $cache, $l1_cache );
847              
848 12         4685 $self->_test_common_subcache_features( $cache, $l1_cache, 'l1_cache' );
849 6         40 };
850              
851             # Test with current cache in primary position...
852             #
853 6         39 $cache =
854             $self->new_cache( l1_cache => { driver => 'Memory', datastore => {} } );
855 6         159 $l1_cache = $cache->l1_cache;
856 6         756 isa_ok( $cache, $self->testing_driver_class, 'cache' );
857 6         4361 isa_ok( $l1_cache, 'CHI::Driver::Memory', 'l1_cache' );
858 6         2468 $test_l1_cache->();
859              
860             # and in l1 position
861             #
862 6         2638 $cache = $self->testing_chi_root_class->new(
863             driver => 'Memory',
864             datastore => {},
865             l1_cache => { $self->new_cache_options() }
866             );
867 6         170 $l1_cache = $cache->l1_cache;
868 6         888 isa_ok( $cache, 'CHI::Driver::Memory', 'cache' );
869 6         4532 isa_ok( $l1_cache, $self->testing_driver_class, 'l1_cache' );
870 6         2643 $test_l1_cache->();
871 9     9   7100 }
  9         19  
  9         105  
872              
873             sub test_mirror_cache : Tests {
874 6     6 0 4615 my $self = shift;
875 6         19 my ( $cache, $mirror_cache );
876 6         34 my ( $key, $value, $key2, $value2 ) = $self->kvpair(2);
877              
878 6 50       31 return "skipping - no support for clear" unless $self->supports_clear();
879              
880             my $test_mirror_cache = sub {
881              
882 12     12   111 is( $mirror_cache->subcache_type, "mirror_cache" );
883              
884             # Get on either cache should not populate the other, and should not be able to see
885             # mirror keys from regular cache
886             #
887 12         5292 $cache->set( $key, $value );
888 12         349 $mirror_cache->remove($key);
889 12         604 $cache->get($key);
890 12         79 ok( !$mirror_cache->get($key), "key not in mirror_cache" );
891              
892 12         5887 $mirror_cache->set( $key2, $value2 );
893 12         299 ok( !$cache->get($key2), "key2 not in cache" );
894              
895 12         5101 $self->_test_logging_with_mirror_cache( $cache, $mirror_cache );
896              
897 12         4893 $self->_test_common_subcache_features( $cache, $mirror_cache,
898             'mirror_cache' );
899 6         38 };
900              
901             my $file_cache_options = sub {
902 12     12   112 my $root_dir =
903             tempdir( "chi-test-mirror-cache-XXXX", TMPDIR => 1, CLEANUP => 1 );
904 12         6907 return ( driver => 'File', root_dir => $root_dir, depth => 3 );
905 6         26 };
906              
907             # Test with current cache in primary position...
908             #
909 6         24 $cache = $self->new_cache( mirror_cache => { $file_cache_options->() } );
910 6         177 $mirror_cache = $cache->mirror_cache;
911 6         722 isa_ok( $cache, $self->testing_driver_class );
912 6         3778 isa_ok( $mirror_cache, 'CHI::Driver::File' );
913 6         2578 $test_mirror_cache->();
914              
915             # and in mirror position
916             #
917 6         2493 $cache =
918             $self->testing_chi_root_class->new( $file_cache_options->(),
919             mirror_cache => { $self->new_cache_options() } );
920 6         166 $mirror_cache = $cache->mirror_cache;
921 6         866 isa_ok( $cache, 'CHI::Driver::File' );
922 6         3476 isa_ok( $mirror_cache, $self->testing_driver_class );
923 6         2628 $test_mirror_cache->();
924 9     9   5362 }
  9         21  
  9         50  
925              
926             sub test_subcache_overridable_params : Tests {
927 7     7 0 5245 my ($self) = @_;
928              
929 7         19 my $cache;
930             warning_like {
931 7     7   517 $cache = $self->new_cache(
932             l1_cache => {
933             driver => 'Memory',
934             on_get_error => 'log',
935             datastore => {},
936             expires_variance => 0.5,
937             serializer => 'Foo'
938             }
939             );
940             }
941 7         93 qr/cannot override these keys/, "non-overridable subcache keys";
942 7         3444 is( $cache->l1_cache->expires_variance, $cache->expires_variance );
943 7         2943 is( $cache->l1_cache->serializer, $cache->serializer );
944 7         2820 is( $cache->l1_cache->on_set_error, $cache->on_set_error );
945 7         2720 is( $cache->l1_cache->on_get_error, 'log' );
946 9     9   4121 }
  9         28  
  9         43  
947              
948             # Run logging tests for a cache with an l1_cache
949             #
950             sub _test_logging_with_l1_cache {
951 12     12   51 my ( $self, $cache ) = @_;
952              
953 12         447 $cache->clear();
954 12         2231 my $log = activate_test_logger();
955 12         71 my ( $key, $value ) = $self->kvpair();
956              
957 12         318 my $driver = $cache->label;
958              
959 12         118 my $miss_not_in_cache = 'MISS \(not in cache\)';
960 12         29 my $miss_expired = 'MISS \(expired\)';
961              
962 12         33 my $start_time = time();
963              
964 12         246 $cache->get($key);
965 12         708 $log->contains_ok(
966             qr/cache get for .* key='$key', cache='$driver', time='[-\d]+ms': $miss_not_in_cache/
967             );
968 12         6398 $log->contains_ok(
969             qr/cache get for .* key='$key', cache='.*l1.*', time='[-\d]+ms': $miss_not_in_cache/
970             );
971 12         5161 $log->empty_ok();
972              
973 12         4928 $cache->set( $key, $value, 81 );
974 12         533 $log->contains_ok(
975             qr/cache set for .* key='$key', size=\d+, expires='1m2[012]s', cache='$driver', time='[-\d]+ms'/
976             );
977              
978 12         5607 $log->contains_ok(
979             qr/cache set for .* key='$key', size=\d+, expires='1m2[012]s', cache='.*l1.*', time='[-\d]+ms'/
980             );
981 12         4967 $log->empty_ok();
982              
983 12         4786 $cache->get($key);
984 12         249 $log->contains_ok(
985             qr/cache get for .* key='$key', cache='.*l1.*', time='[-\d]+ms': HIT/);
986 12         5429 $log->empty_ok();
987              
988 12         4475 local $CHI::Driver::Test_Time = $start_time + 120;
989 12         343 $cache->get($key);
990 12         813 $log->contains_ok(
991             qr/cache get for .* key='$key', cache='$driver', time='[-\d]+ms': $miss_expired/
992             );
993 12         5427 $log->contains_ok(
994             qr/cache get for .* key='$key', cache='.*l1.*', time='[-\d]+ms': $miss_expired/
995             );
996 12         4804 $log->empty_ok();
997              
998 12         4745 $cache->remove($key);
999 12         573 $cache->get($key);
1000 12         411 $log->contains_ok(
1001             qr/cache get for .* key='$key', cache='$driver', time='[-\d]+ms': $miss_not_in_cache/
1002             );
1003 12         5400 $log->contains_ok(
1004             qr/cache get for .* key='$key', cache='.*l1.*', time='[-\d]+ms': $miss_not_in_cache/
1005             );
1006 12         4932 $log->empty_ok();
1007             }
1008              
1009             sub _test_logging_with_mirror_cache {
1010 12     12   42 my ( $self, $cache ) = @_;
1011              
1012 12         374 $cache->clear();
1013 12         2171 my $log = activate_test_logger();
1014 12         76 my ( $key, $value ) = $self->kvpair();
1015              
1016 12         332 my $driver = $cache->label;
1017              
1018 12         112 my $miss_not_in_cache = 'MISS \(not in cache\)';
1019 12         30 my $miss_expired = 'MISS \(expired\)';
1020              
1021 12         32 my $start_time = time();
1022              
1023 12         248 $cache->get($key);
1024 12         706 $log->contains_ok(
1025             qr/cache get for .* key='$key', cache='$driver', time='[-\d]+ms': $miss_not_in_cache/
1026             );
1027 12         6295 $log->empty_ok();
1028              
1029 12         4791 $cache->set( $key, $value, 81 );
1030 12         621 $log->contains_ok(
1031             qr/cache set for .* key='$key', size=\d+, expires='1m2[012]s', cache='$driver', time='[-\d]+ms'/
1032             );
1033              
1034 12         6222 $log->contains_ok(
1035             qr/cache set for .* key='$key', size=\d+, expires='1m2[012]s', cache='.*mirror.*', time='[-\d]+ms'/
1036             );
1037 12         5005 $log->empty_ok();
1038              
1039 12         4809 $cache->get($key);
1040 12         421 $log->contains_ok(
1041             qr/cache get for .* key='$key', cache='$driver', time='[-\d]+ms': HIT/);
1042 12         5070 $log->empty_ok();
1043              
1044 12         4334 local $CHI::Driver::Test_Time = $start_time + 120;
1045 12         390 $cache->get($key);
1046 12         423 $log->contains_ok(
1047             qr/cache get for .* key='$key', cache='$driver', time='[-\d]+ms': $miss_expired/
1048             );
1049 12         5163 $log->empty_ok();
1050              
1051 12         4698 $cache->remove($key);
1052 12         539 $cache->get($key);
1053 12         388 $log->contains_ok(
1054             qr/cache get for .* key='$key', cache='$driver', time='[-\d]+ms': $miss_not_in_cache/
1055             );
1056 12         5340 $log->empty_ok();
1057             }
1058              
1059             # Run tests common to l1_cache and mirror_cache
1060             #
1061             sub _test_common_subcache_features {
1062 24     24   104 my ( $self, $cache, $subcache, $subcache_type ) = @_;
1063 24         114 my ( $key, $value, $key2, $value2 ) = $self->kvpair(2);
1064              
1065 24         97 for ( $cache, $subcache ) { $_->clear() }
  48         3059  
1066              
1067             # Test informational methods
1068             #
1069 24         596 ok( !$cache->is_subcache, "is_subcache - false" );
1070 24         10009 ok( $subcache->is_subcache, "is_subcache - true" );
1071 24         9379 ok( $cache->has_subcaches, "has_subcaches - true" );
1072 24         10469 ok( !$subcache->has_subcaches, "has_subcaches - false" );
1073 24         9894 ok( !$cache->can('parent_cache'), "parent_cache - cannot" );
1074 24         8915 is( $subcache->parent_cache, $cache, "parent_cache - defined" );
1075 24         9765 ok( !$cache->can('subcache_type'), "subcache_type - cannot" );
1076 24         8674 is( $subcache->subcache_type, $subcache_type, "subcache_type - defined" );
1077 24         9855 cmp_deeply( $cache->subcaches, [$subcache], "subcaches - defined" );
1078 24         39414 ok( !$subcache->can('subcaches'), "subcaches - cannot" );
1079 24         9447 is( $cache->$subcache_type, $subcache, "$subcache_type - defined" );
1080 24         9856 ok( !$subcache->can($subcache_type), "$subcache_type - cannot" );
1081              
1082             # Test that sets and various kinds of removals and expirations are distributed to both
1083             # the primary cache and the subcache
1084             #
1085 24         8667 my ( $test_remove_method, $confirm_caches_empty,
1086             $confirm_caches_populated );
1087             $test_remove_method = sub {
1088 72     72   276 my ( $desc, $remove_code ) = @_;
1089 72         227 $desc = "testing $desc";
1090              
1091 72         321 $confirm_caches_empty->("$desc: before set");
1092              
1093 72         29442 $cache->set( $key, $value );
1094 72         1717 $cache->set( $key2, $value2 );
1095 72         727 $confirm_caches_populated->("$desc: after set");
1096 72         28226 $remove_code->();
1097              
1098 72         5429 $confirm_caches_empty->("$desc: before set_multi");
1099 72         28745 $cache->set_multi( { $key => $value, $key2 => $value2 } );
1100 72         1087 $confirm_caches_populated->("$desc: after set_multi");
1101 72         28488 $remove_code->();
1102              
1103 72         5338 $confirm_caches_empty->("$desc: before return");
1104 24         237 };
1105              
1106             $confirm_caches_empty = sub {
1107 216     216   559 my ($desc) = @_;
1108 216         4794 ok( !defined( $cache->get($key) ),
1109             "primary cache is not populated with '$key' - $desc" );
1110 216         87808 ok( !defined( $subcache->get($key) ),
1111             "subcache is not populated with '$key' - $desc" );
1112 216         87809 ok( !defined( $cache->get($key2) ),
1113             "primary cache is not populated #2 with '$key2' - $desc" );
1114 216         87257 ok( !defined( $subcache->get($key2) ),
1115             "subcache is not populated #2 with '$key2' - $desc" );
1116 24         142 };
1117              
1118             $confirm_caches_populated = sub {
1119 144     144   392 my ($desc) = @_;
1120 144         2881 is( $cache->get($key), $value,
1121             "primary cache is populated with '$key' - $desc" );
1122 144         64890 is( $subcache->get($key),
1123             $value, "subcache is populated with '$key' - $desc" );
1124 144         61058 is( $cache->get($key2), $value2,
1125             "primary cache is populated with '$key2' - $desc" );
1126 144         57935 is( $subcache->get($key2),
1127             $value2, "subcache is populated with '$key2' - $desc" );
1128 24         149 };
1129              
1130             $test_remove_method->(
1131 48     48   1518 'remove', sub { $cache->remove($key); $cache->remove($key2) }
  48         2032  
1132 24         150 );
1133             $test_remove_method->(
1134 48     48   1553 'expire', sub { $cache->expire($key); $cache->expire($key2) }
  48         1192  
1135 24         10329 );
1136 24     48   10477 $test_remove_method->( 'clear', sub { $cache->clear() } );
  48         1417  
1137             }
1138              
1139             sub _verify_cache_is_cleared {
1140 25     25   85 my ( $self, $cache, $desc ) = @_;
1141              
1142 25         212 cmp_deeply( [ $cache->get_keys ], [], "get_keys ($desc)" );
1143 25         103800 is( scalar( $cache->get_keys ), 0, "scalar(get_keys) = 0 ($desc)" );
1144 25         9372 while ( my ( $keyname, $key ) = each( %{ $self->{keys} } ) ) {
  350         114512  
1145 325         4918 ok( !defined $cache->get($key),
1146             "key '$keyname' no longer defined ($desc)" );
1147             }
1148             }
1149              
1150             sub process_keys {
1151 128     128 0 9525 my ( $self, $cache, @keys ) = @_;
1152 128         510 $self->process_key( $cache, 'foo' );
1153 128         336 return map { $self->process_key( $cache, $_ ) } @keys;
  680         1238  
1154             }
1155              
1156             sub process_key {
1157 808     808 0 1712 my ( $self, $cache, $key ) = @_;
1158 808         1722 return $cache->unescape_key(
1159             $cache->escape_key( $cache->transform_key($key) ) );
1160             }
1161              
1162             sub test_clear : Tests {
1163 8     8 0 7490 my $self = shift;
1164 8         38 my $cache = $self->new_cache( namespace => 'name' );
1165 8         43 my $cache2 = $self->new_cache( namespace => 'other' );
1166 8         43 my $cache3 = $self->new_cache( namespace => 'name' );
1167 8         149 $self->num_tests( $self->{key_count} * 2 + 5 );
1168              
1169 8 50       1067 if ( $self->supports_clear() ) {
1170 8         70 $self->set_some_keys($cache);
1171 8         43 $self->set_some_keys($cache2);
1172 8         86 $cache->clear();
1173              
1174 8         10373 $self->_verify_cache_is_cleared( $cache, 'cache after clear' );
1175 8         125 $self->_verify_cache_is_cleared( $cache3, 'cache3 after clear' );
1176             cmp_set(
1177             [ $cache2->get_keys ],
1178 8         56 [ $self->process_keys( $cache2, values( %{ $self->{keys} } ) ) ],
  8         9244  
1179             'cache2 untouched by clear'
1180             );
1181             }
1182             else {
1183             throws_ok(
1184 0     0   0 sub { $cache->clear() },
1185 0         0 qr/not supported/,
1186             "clear not supported"
1187             );
1188 0         0 SKIP: { skip "clear not supported", 9 }
  0         0  
1189             }
1190 9     9   18163 }
  9         24  
  9         85  
1191              
1192             sub test_logging : Tests {
1193 6     6 0 5067 my $self = shift;
1194 6         20 my $cache = $self->{cache};
1195              
1196 6         39 my $log = activate_test_logger();
1197 6         36 my ( $key, $value ) = $self->kvpair();
1198              
1199 6         177 my $driver = $cache->label;
1200              
1201 6         18 my $miss_not_in_cache = 'MISS \(not in cache\)';
1202 6         15 my $miss_expired = 'MISS \(expired\)';
1203              
1204 6         18 my $start_time = time();
1205              
1206 6         71 $cache->get($key);
1207 6         365 $log->contains_ok(
1208             qr/cache get for .* key='$key', cache='$driver', time='[-\d]+ms': $miss_not_in_cache/
1209             );
1210 6         3308 $log->empty_ok();
1211              
1212 6         2303 $cache->set( $key, $value );
1213 6         294 $log->contains_ok(
1214             qr/cache set for .* key='$key', size=\d+, expires='never', cache='$driver', time='[-\d]+ms'/
1215             );
1216 6         2778 $log->empty_ok();
1217 6         2271 $cache->set( $key, $value, 81 );
1218 6         248 $log->contains_ok(
1219             qr/cache set for .* key='$key', size=\d+, expires='1m2[012]s', cache='$driver', time='[-\d]+ms'/
1220             );
1221 6         2754 $log->empty_ok();
1222              
1223 6         2284 $cache->get($key);
1224 6         214 $log->contains_ok(
1225             qr/cache get for .* key='$key', cache='$driver', time='[-\d]+ms': HIT/);
1226 6         2660 $log->empty_ok();
1227              
1228 6         2301 local $CHI::Driver::Test_Time = $start_time + 120;
1229 6         85 $cache->get($key);
1230 6         219 $log->contains_ok(
1231             qr/cache get for .* key='$key', cache='$driver', time='[-\d]+ms': $miss_expired/
1232             );
1233 6         2459 $log->empty_ok();
1234              
1235 6         2317 $cache->remove($key);
1236 6         288 $cache->get($key);
1237 6         233 $log->contains_ok(
1238             qr/cache get for .* key='$key', cache='$driver', time='[-\d]+ms': $miss_not_in_cache/
1239             );
1240 6         2497 $log->empty_ok();
1241 9     9   5070 }
  9         21  
  9         101  
1242              
1243             sub test_stats : Tests {
1244 6     6 0 18440 my $self = shift;
1245              
1246             return 'author testing only - possible differences between JSON versions'
1247 6 50       46 unless ( $ENV{AUTHOR_TESTING} );
1248              
1249 0         0 my $stats = $self->testing_chi_root_class->stats;
1250 0         0 $stats->enable();
1251              
1252 0         0 my ( $key, $value ) = $self->kvpair();
1253 0         0 my $start_time = time();
1254              
1255 0         0 my $cache;
1256 0         0 $cache = $self->new_cache( namespace => 'Foo' );
1257 0         0 $cache->get($key);
1258 0         0 $cache->set( $key, $value, 80 );
1259 0         0 $cache->get($key);
1260 0         0 local $CHI::Driver::Test_Time = $start_time + 120;
1261 0         0 $cache->get($key);
1262 0         0 $cache->remove($key);
1263 0         0 $cache->get($key);
1264              
1265 0         0 $cache = $self->new_cache( namespace => 'Bar' );
1266 0         0 $cache->set( $key, scalar( $value x 3 ) );
1267 0         0 $cache->set( $key, $value );
1268              
1269 0         0 $cache = $self->new_cache( namespace => 'Baz' );
1270 0     0   0 my $code = sub { usleep(100000); scalar( $value x 5 ) };
  0         0  
  0         0  
1271 0         0 $cache->compute( $key, undef, $code );
1272 0         0 $cache->compute( $key, undef, $code );
1273 0         0 $cache->compute( $key, undef, $code );
1274              
1275 0         0 my $log = activate_test_logger();
1276 0         0 my $label = $cache->label;
1277 0         0 $log->empty_ok();
1278 0         0 $stats->flush();
1279 0         0 $log->contains_ok(
1280             qr/CHI stats: \{"absent_misses":2,"end_time":\d+,"expired_misses":1,"get_time_ms":\d+,"hits":1,"label":"$label","namespace":"Foo","root_class":"CHI","set_key_size":6,"set_time_ms":\d+,"set_value_size":20,"sets":1,"start_time":\d+\}/
1281             );
1282 0         0 $log->contains_ok(
1283             qr/CHI stats: \{"end_time":\d+,"label":"$label","namespace":"Bar","root_class":"CHI","set_key_size":12,"set_time_ms":\d+,"set_value_size":52,"sets":2,"start_time":\d+\}/
1284             );
1285 0         0 $log->contains_ok(
1286             qr/CHI stats: \{"absent_misses":1,"compute_time_ms":\d+,"computes":1,"end_time":\d+,"get_time_ms":\d+,"hits":2,"label":"$label","namespace":"Baz","root_class":"CHI","set_key_size":6,"set_time_ms":\d+,"set_value_size":44,"sets":1,"start_time":\d+\}/
1287             );
1288 0         0 $log->empty_ok();
1289              
1290 0         0 my @logs = (
1291             'CHI stats: {"root_class":"CHI","namespace":"Foo","label":"File","start_time":1338404896,"end_time":1338404899,"hits":3,"sets":5,"set_time_ms":10}',
1292             'CHI stats: {"root_class":"CHI","namespace":"Foo","label":"File","start_time":1338404896,"end_time":1338404899,"hits":1,"sets":7,"set_time_ms":14}',
1293             'CHI stats: {"root_class":"CHI","namespace":"Bar","label":"File","start_time":1338404896,"end_time":1338404899,"hits":4,"sets":9,"set_time_ms":18}',
1294             'CHI stats: {"root_class":"CHI","namespace":"Foo","label":"File","start_time":1338404896,"end_time":1338404899,"sets":3,"set_time_ms":6}',
1295             'CHI stats: {"root_class":"CHI","namespace":"Foo","label":"File","start_time":1338404896,"end_time":1338404899,"hits":8}',
1296             'CHI stats: {"root_class":"CHI","namespace":"Foo","label":"Memory","start_time":1338404896,"end_time":1338404899,"sets":2,"set_time_ms":4}',
1297             'CHI stats: {"root_class":"CHI","namespace":"Bar","label":"File","start_time":1338404896,"end_time":1338404899,"hits":10,"sets":1,"set_time_ms":2}',
1298             'CHI stats: {"root_class":"CHI","namespace":"Bar","label":"File","start_time":1338404896,"end_time":1338404899,"hits":3,"set_errors":2}',
1299             );
1300 0         0 my $log_dir = tempdir( "chi-test-stats-XXXX", TMPDIR => 1, CLEANUP => 1 );
1301 0         0 write_file( "$log_dir/log1", join( "\n", splice( @logs, 0, 4 ) ) . "\n" );
1302 0         0 write_file( "$log_dir/log2", join( "\n", @logs ) );
1303 0 0       0 open( my $fh2, "<", "$log_dir/log2" ) or die "cannot open $log_dir/log2";
1304 0         0 my $results = $stats->parse_stats_logs( "$log_dir/log1", $fh2 );
1305 0         0 close($fh2);
1306 0         0 cmp_deeply(
1307             $results,
1308             Test::Deep::bag(
1309             {
1310             avg_set_time_ms => '2',
1311             gets => 12,
1312             hit_rate => '1',
1313             hits => 12,
1314             label => 'File',
1315             namespace => 'Foo',
1316             root_class => 'CHI',
1317             set_time_ms => 30,
1318             sets => 15
1319             },
1320             {
1321             avg_set_time_ms => '2',
1322             gets => 17,
1323             hit_rate => '1',
1324             hits => 17,
1325             label => 'File',
1326             namespace => 'Bar',
1327             root_class => 'CHI',
1328             set_errors => 2,
1329             set_time_ms => 20,
1330             sets => 10
1331             },
1332             {
1333             avg_set_time_ms => '2',
1334             label => 'Memory',
1335             namespace => 'Foo',
1336             root_class => 'CHI',
1337             set_time_ms => 4,
1338             sets => 2
1339             },
1340             {
1341             avg_set_time_ms => '2',
1342             hits => '29',
1343             label => 'TOTALS',
1344             namespace => 'TOTALS',
1345             root_class => 'TOTALS',
1346             set_errors => '2',
1347             set_time_ms => 54,
1348             sets => 27
1349             }
1350             ),
1351             'parse_stats_logs'
1352             );
1353 9     9   7554 }
  9         27  
  9         51  
1354              
1355             sub test_cache_object : Tests {
1356 8     8 0 7380 my $self = shift;
1357 8         26 my $cache = $self->{cache};
1358 8         37 my ( $key, $value ) = $self->kvpair();
1359 8         27 my $start_time = time();
1360 8         128 $cache->set( $key, $value, { expires_at => $start_time + 10 } );
1361 8         57 is_between( $cache->get_object($key)->created_at,
1362             $start_time, $start_time + 2 );
1363 8         3457 is_between( $cache->get_object($key)->get_created_at,
1364             $start_time, $start_time + 2 );
1365 8         3077 is( $cache->get_object($key)->expires_at, $start_time + 10 );
1366 8         3220 is( $cache->get_object($key)->get_expires_at, $start_time + 10 );
1367              
1368 8         2992 local $CHI::Driver::Test_Time = $start_time + 50;
1369 8         117 $cache->set( $key, $value );
1370 8         43 is_between(
1371             $cache->get_object($key)->created_at,
1372             $start_time + 50,
1373             $start_time + 52
1374             );
1375 8         3110 is_between(
1376             $cache->get_object($key)->get_created_at,
1377             $start_time + 50,
1378             $start_time + 52
1379             );
1380 9     9   4148 }
  9         18  
  9         48  
1381              
1382             sub test_size_awareness : Tests {
1383 7     7 0 6791 my $self = shift;
1384 7         42 my ( $key, $value ) = $self->kvpair();
1385              
1386 7         42 ok( !$self->new_cleared_cache()->is_size_aware(),
1387             "not size aware by default" );
1388 7         3270 ok( $self->new_cleared_cache( is_size_aware => 1 )->is_size_aware(),
1389             "is_size_aware turns on size awareness" );
1390 7         3481 ok( $self->new_cleared_cache( max_size => 10 )->is_size_aware(),
1391             "max_size turns on size awareness" );
1392              
1393 7         3423 my $cache = $self->new_cleared_cache( is_size_aware => 1 );
1394 7         57 is( $cache->get_size(), 0, "size is 0 for empty" );
1395 7         3329 $cache->set( $key, $value );
1396 7         41 is_about( $cache->get_size, 20, "size is about 20 with one value" );
1397 7         3247 $cache->set( $key, scalar( $value x 5 ) );
1398 7         40 is_about( $cache->get_size, 45, "size is 45 after overwrite" );
1399 7         3156 $cache->set( $key, scalar( $value x 5 ) );
1400 7         41 is_about( $cache->get_size, 45, "size is still 45 after same overwrite" );
1401 7         3207 $cache->set( $key, scalar( $value x 2 ) );
1402 7         42 is_about( $cache->get_size, 26, "size is 26 after overwrite" );
1403 7         3300 $cache->remove($key);
1404 7         52 is( $cache->get_size, 0, "size is 0 again after removing key" );
1405 7         3131 $cache->set( $key, $value );
1406 7         40 is_about( $cache->get_size, 20, "size is about 20 with one value" );
1407 7         3208 $cache->clear();
1408 7         1362 is( $cache->get_size, 0, "size is 0 again after clear" );
1409              
1410 7         3040 my $time = time() + 10;
1411 7         123 $cache->set( $key, $value, { expires_at => $time } );
1412 7         213 is( $cache->get_expires_at($key),
1413             $time, "set options respected by size aware cache" );
1414 9     9   4984 }
  9         23  
  9         74  
1415              
1416             sub test_max_size : Tests {
1417 7     7 0 9038 my $self = shift;
1418              
1419 7         44 is( $self->new_cache( max_size => '30k' )->max_size,
1420             30 * 1024, 'max_size parsing' );
1421              
1422 7         3836 my $cache = $self->new_cleared_cache( max_size => 99 );
1423 7         211 ok( $cache->is_size_aware, "is size aware when max_size specified" );
1424 7         4469 my $value_20 = 'x' x 6;
1425              
1426 7         44 for ( my $i = 0 ; $i < 5 ; $i++ ) {
1427 35         559 $cache->set( "key$i", $value_20 );
1428             }
1429 7         90 for ( my $i = 0 ; $i < 10 ; $i++ ) {
1430 70         26822 $cache->set( "key" . int( rand(10) ), $value_20 );
1431 70         348 is_between( $cache->get_size, 60, 99,
1432             "after iteration $i, size = " . $cache->get_size );
1433 70         34202 is_between( scalar( $cache->get_keys ),
1434             3, 5, "after iteration $i, keys = " . scalar( $cache->get_keys ) );
1435             }
1436 9     9   4372 }
  9         32  
  9         102  
1437              
1438             sub test_max_size_with_l1_cache : Tests {
1439 8     8 0 12547 my $self = shift;
1440              
1441 8         75 my $cache = $self->new_cleared_cache(
1442             l1_cache => { driver => 'Memory', datastore => {}, max_size => 99 } );
1443 8         548 my $l1_cache = $cache->l1_cache;
1444 8         305 ok( $l1_cache->is_size_aware, "is size aware when max_size specified" );
1445 8         4596 my $value_20 = 'x' x 6;
1446              
1447 8         39 my @keys = map { "key$_" } ( 0 .. 9 );
  80         189  
1448 8         79 my @shuffle_keys = shuffle(@keys);
1449 8         44 for ( my $i = 0 ; $i < 5 ; $i++ ) {
1450 40         1073 $cache->set( "key$i", $value_20 );
1451             }
1452 8         114 for ( my $i = 0 ; $i < 10 ; $i++ ) {
1453 80         26298 my $key = $shuffle_keys[$i];
1454 80         2254 $cache->set( $key, $value_20 );
1455 80         612 is_between( $l1_cache->get_size, 60, 99,
1456             "after iteration $i, size = " . $l1_cache->get_size );
1457 80         34392 is_between( scalar( $l1_cache->get_keys ),
1458             3, 5,
1459             "after iteration $i, keys = " . scalar( $l1_cache->get_keys ) );
1460             }
1461 8         3013 cmp_deeply( [ sort $cache->get_keys ],
1462             \@keys, "primary cache still has all keys" );
1463              
1464             # Now test population by writeback
1465 8         21826 $l1_cache->clear();
1466 8         71 is( $l1_cache->get_size, 0, "l1 size is 0 after clear" );
1467 8         3145 for ( my $i = 0 ; $i < 5 ; $i++ ) {
1468 40         985 $cache->get("key$i");
1469             }
1470 8         64 for ( my $i = 0 ; $i < 10 ; $i++ ) {
1471 80         25366 my $key = $shuffle_keys[$i];
1472 80         2323 $cache->get($key);
1473 80         264 is_between( $l1_cache->get_size, 60, 99,
1474             "after iteration $i, size = " . $l1_cache->get_size );
1475 80         33043 is_between( scalar( $l1_cache->get_keys ),
1476             3, 5,
1477             "after iteration $i, keys = " . scalar( $l1_cache->get_keys ) );
1478             }
1479 9     9   6211 }
  9         23  
  9         53  
1480              
1481             sub test_custom_discard_policy : Tests {
1482 7     7 0 6260 my $self = shift;
1483 7         22 my $value_20 = 'x' x 6;
1484             my $highest_first = sub {
1485 70     70   153 my $c = shift;
1486 70         385 my @sorted_keys = sort( $c->get_keys );
1487 70         68708 return sub { pop(@sorted_keys) };
  315         1044  
1488 7         41 };
1489 7         57 my $cache = $self->new_cleared_cache(
1490             is_size_aware => 1,
1491             discard_policy => $highest_first
1492             );
1493 7         58 for ( my $j = 0 ; $j < 10 ; $j += 2 ) {
1494 35         44274 $cache->clear();
1495 35         5676 for ( my $i = 0 ; $i < 10 ; $i++ ) {
1496 350         1140 my $k = ( $i + $j ) % 10;
1497 350         4138 $cache->set( "key$k", $value_20 );
1498             }
1499 35         332 $cache->discard_to_size(100);
1500             cmp_set(
1501             [ $cache->get_keys ],
1502 35         169 [ map { "key$_" } ( 0 .. 4 ) ],
  175         30426  
1503             "5 lowest"
1504             );
1505 35         136960 $cache->discard_to_size(20);
1506 35         162 cmp_set( [ $cache->get_keys ], ["key0"], "1 lowest" );
1507             }
1508 9     9   4607 }
  9         20  
  9         51  
1509              
1510             sub test_discard_timeout : Tests {
1511 8     8 0 18629 my $self = shift;
1512 8 50       62 return 'author testing only' unless ( $ENV{AUTHOR_TESTING} );
1513              
1514             my $bad_policy = sub {
1515 0     0   0 return sub { '1' };
  0         0  
1516 0         0 };
1517 0         0 my $cache = $self->new_cleared_cache(
1518             is_size_aware => 1,
1519             discard_policy => $bad_policy
1520             );
1521 0   0     0 ok( defined( $cache->discard_timeout ) && $cache->discard_timeout > 1,
1522             "positive discard timeout" );
1523 0         0 $cache->discard_timeout(1);
1524 0         0 is( $cache->discard_timeout, 1, "can set timeout" );
1525 0         0 my $start_time = time;
1526 0         0 $cache->set( 2, 2 );
1527 0     0   0 throws_ok { $cache->discard_to_size(0) } qr/discard timeout .* reached/;
  0         0  
1528 0   0     0 ok(
1529             time >= $start_time && time <= $start_time + 4,
1530             sprintf(
1531             "time (%d) is between %d and %d",
1532             time, $start_time, $start_time + 4
1533             )
1534             );
1535 9     9   4527 }
  9         20  
  9         57  
1536              
1537             sub test_size_awareness_with_subcaches : Tests {
1538 7     7 0 8021 my $self = shift;
1539              
1540 7         24 my ( $cache, $l1_cache );
1541             my $set_values = sub {
1542 21     21   65 my $value_20 = 'x' x 6;
1543 21         95 for ( my $i = 0 ; $i < 20 ; $i++ ) {
1544 420         10962 $cache->set( "key$i", $value_20 );
1545             }
1546 21         564 $l1_cache = $cache->l1_cache;
1547 7         42 };
1548             my $is_size_aware = sub {
1549 28     28   75 my $c = shift;
1550 28         636 my $label = $c->label;
1551              
1552 28         586 ok( $c->is_size_aware, "$label is size aware" );
1553 28         13971 my $max_size = $c->max_size;
1554 28         306 ok( $max_size > 0, "$label has max size" );
1555 28         9381 is_between( $c->get_size, $max_size - 40,
1556             $max_size, "$label size = " . $c->get_size );
1557 28         10081 is_between(
1558             scalar( $c->get_keys ),
1559             ( $max_size + 1 ) / 20 - 2,
1560             ( $max_size + 1 ) / 20,
1561             "$label keys = " . scalar( $c->get_keys )
1562             );
1563 7         43 };
1564             my $is_not_size_aware = sub {
1565 7     7   17 my $c = shift;
1566 7         127 my $label = $c->label;
1567              
1568 7         246 ok( !$c->is_size_aware, "$label is not size aware" );
1569 7         3992 is( $c->get_keys, 20, "$label keys = 20" );
1570 7         34 };
1571              
1572 7         57 $cache = $self->new_cleared_cache(
1573             l1_cache => { driver => 'Memory', datastore => {}, max_size => 99 } );
1574 7         42 $set_values->();
1575 7         92 $is_not_size_aware->($cache);
1576 7         3046 $is_size_aware->($l1_cache);
1577              
1578 7         2303 $cache = $self->new_cleared_cache(
1579             l1_cache => { driver => 'Memory', datastore => {}, max_size => 99 },
1580             max_size => 199
1581             );
1582 7         42 $set_values->();
1583 7         258 $is_size_aware->($cache);
1584 7         2860 $is_size_aware->($l1_cache);
1585              
1586 7         2354 $cache = $self->new_cleared_cache(
1587             l1_cache => { driver => 'Memory', datastore => {} },
1588             max_size => 199
1589             );
1590 7         44 $set_values->();
1591 7         291 $is_size_aware->($cache);
1592              
1593             # Cannot call is_not_size_aware because the get_keys check will
1594             # fail. Keys will be removed from the l1_cache when they are removed
1595             # from the main cache, even though l1_cache does not have a max
1596             # size. Not sure if this is the correct behavior, but for now, we're not
1597             # going to test it. Normally, l1 caches will be more size limited than
1598             # their parent caches.
1599             #
1600 7         2757 ok( !$l1_cache->is_size_aware, $l1_cache->label . " is not size aware" );
1601 9     9   6206 }
  9         24  
  9         42  
1602              
1603             sub is_about {
1604 35     35 0 112 my ( $value, $expected, $msg ) = @_;
1605              
1606 35         116 my $margin = int( $expected * 0.1 );
1607 35 50       159 if ( abs( $value - $expected ) <= $margin ) {
1608 35         153 pass($msg);
1609             }
1610             else {
1611 0           fail("$msg - got $value, expected $expected");
1612             }
1613             }
1614              
1615             sub test_busy_lock : Tests {
1616 8     8 0 7529 my $self = shift;
1617 8         27 my $cache = $self->{cache};
1618              
1619 8         57 my ( $key, $value ) = $self->kvpair();
1620 8         34 my @bl = ( busy_lock => '30 sec' );
1621 8         21 my $start_time = time();
1622              
1623 8         21 local $CHI::Driver::Test_Time = $start_time;
1624 8         114 $cache->set( $key, $value, 100 );
1625 8         30 local $CHI::Driver::Test_Time = $start_time + 90;
1626 8         117 is( $cache->get( $key, @bl ), $value, "hit before expiration" );
1627 8         3623 is(
1628             $cache->get_expires_at($key),
1629             $start_time + 100,
1630             "expires_at before expiration"
1631             );
1632 8         3039 local $CHI::Driver::Test_Time = $start_time + 110;
1633 8         116 ok( !defined( $cache->get( $key, @bl ) ), "miss after expiration" );
1634 8         3048 is(
1635             $cache->get_expires_at($key),
1636             $start_time + 140,
1637             "expires_at after busy lock"
1638             );
1639 8         3199 is( $cache->get( $key, @bl ), $value, "hit after busy lock" );
1640 9     9   4908 }
  9         27  
  9         44  
1641              
1642             sub test_obj_ref : Tests {
1643 8     8 0 6055 my $self = shift;
1644              
1645             # Make sure obj_ref works in conjunction with subcaches too
1646 8         64 my $cache =
1647             $self->new_cache( l1_cache => { driver => 'Memory', datastore => {} } );
1648 8         28 my $obj;
1649 8         41 my ( $key, $value ) = ( 'medium', [ a => 5, b => 6 ] );
1650              
1651             my $validate_obj = sub {
1652 16     16   86 isa_ok( $obj, 'CHI::CacheObject' );
1653 16         8062 is( $obj->key, $key, "keys match" );
1654 16         6137 cmp_deeply( $obj->value, $value, "values match" );
1655 8         63 };
1656              
1657 8         230 $cache->get( $key, obj_ref => \$obj );
1658 8         56 ok( !defined($obj), "obj not defined on miss" );
1659 8         4684 $cache->set( $key, $value, { obj_ref => \$obj } );
1660 8         86 $validate_obj->();
1661 8         3824 undef $obj;
1662 8         52 ok( !defined($obj), "obj not defined before get" );
1663 8         3100 $cache->get( $key, obj_ref => \$obj );
1664 8         32 $validate_obj->();
1665 9     9   4235 }
  9         18  
  9         40  
1666              
1667             sub test_metacache : Tests {
1668 8     8 0 17153 my $self = shift;
1669 8         31 my $cache = $self->{cache};
1670              
1671 8         66 ok( !defined( $cache->{metacache} ), "metacache is lazy" );
1672 8         3729 $cache->metacache->set( 'foo', 5 );
1673 8         1393 ok( defined( $cache->{metacache} ), "metacache autovivified" );
1674 8         3586 is( $cache->metacache->get('foo'), 5 );
1675 9     9   3253 }
  9         23  
  9         52  
1676              
1677             sub test_scalar_return_values : Tests {
1678 7     7 0 22846 my $self = shift;
1679 7         22 my $cache = $self->{cache};
1680              
1681             my $check = sub {
1682 35     35   90 my ($code) = @_;
1683 35         86 my $scalar_result = $code->();
1684 35         840 my @list = $code->();
1685 35         935 cmp_deeply( \@list, [$scalar_result] );
1686 7         40 };
1687              
1688 7     14   46 $check->( sub { $cache->fetch('a') } );
  14         54  
1689 7     14   11285 $check->( sub { $cache->get('a') } );
  14         207  
1690 7     14   9872 $check->( sub { $cache->set( 'a', 5 ) } );
  14         229  
1691 7     14   10807 $check->( sub { $cache->fetch('a') } );
  14         64  
1692 7     14   9805 $check->( sub { $cache->get('a') } );
  14         209  
1693 9     9   4381 }
  9         22  
  9         50  
1694              
1695             sub test_no_leak : Tests {
1696 8     8 0 6317 my ($self) = @_;
1697              
1698 8         26 my $weakref;
1699             {
1700 8         20 my $cache = $self->new_cache();
  8         37  
1701 8         30 $weakref = $cache;
1702 8         50 weaken($weakref);
1703 8   33     132 ok( defined($weakref) && $weakref->isa('CHI::Driver'),
1704             "weakref is defined" );
1705             }
1706 8         3727 ok( !defined($weakref), "weakref is no longer defined - cache was freed" );
1707 9     9   3407 }
  9         21  
  9         47  
1708              
1709             {
1710             package My::CHI;
1711             $My::CHI::VERSION = '0.61';
1712             our @ISA = qw(CHI);
1713             }
1714              
1715             sub test_driver_properties : Tests {
1716 8     8 0 6155 my $self = shift;
1717 8         28 my $cache = $self->{cache};
1718              
1719 8         94 is( $cache->chi_root_class, 'CHI', 'chi_root_class=CHI' );
1720 8         3870 my $cache2 = My::CHI->new( $self->new_cache_options() );
1721 8         71 is( $cache2->chi_root_class, 'My::CHI', 'chi_root_class=My::CHI' );
1722 9     9   3440 }
  9         21  
  9         40  
1723              
1724             sub test_missing_params : Tests {
1725 8     8 0 6285 my $self = shift;
1726 8         28 my $cache = $self->{cache};
1727              
1728             # These methods require a key
1729 8         33 foreach my $method (
1730             qw(get get_object get_expires_at exists_and_is_expired is_valid set expire compute get_multi_arrayref get_multi_hashref set_multi remove_multi)
1731             )
1732             {
1733             throws_ok(
1734 96     96   4037 sub { $cache->$method() },
1735 96         69267 qr/must specify key/,
1736             "$method throws error when no key passed"
1737             );
1738             }
1739 9     9   3577 }
  9         20  
  9         51  
1740              
1741             sub test_compute : Tests {
1742 8     8 0 8224 my $self = shift;
1743 8         25 my $cache = $self->{cache};
1744              
1745             # Test current arg order and pre-0.40 arg order
1746 8         42 foreach my $iter ( 0 .. 1 ) {
1747 16         3007 my $count = 5;
1748 16         43 my $expire_time = time + 10;
1749 16     16   99 my @args1 = ( { expires_at => $expire_time }, sub { $count++ } );
  16         50  
1750             my @args2 = (
1751             {
1752 18     18   67 expire_if => sub { 1 }
1753             },
1754 16     16   43 sub { $count++ }
1755 16         95 );
1756 16 100       62 if ($iter) {
1757 8         24 @args1 = reverse(@args1);
1758 8         23 @args2 = reverse(@args2);
1759             }
1760 16         119 $cache->clear;
1761 16         1612 is( $cache->get('foo'), undef, "miss" );
1762 16         7744 is( $cache->compute( 'foo', @args1 ), 5, "compute - 5" );
1763 16         6318 is( $cache->get('foo'), 5, "hit - 5" );
1764 16         5978 is( $cache->get_object('foo')->expires_at, $expire_time,
1765             "expire time" );
1766 16         6092 is( $cache->compute( 'foo', @args2 ), 6, "compute - 6" );
1767 16         6352 is( $cache->get('foo'), 6, "hit - 6" );
1768             }
1769              
1770             # Test wantarray
1771 8         3074 $cache->clear();
1772             my $compute_list = sub {
1773 16     16   131 $cache->compute( 'foo', {}, sub { ( int( rand(10000) ) ) x 5 } );
  8         187  
1774 8         1267 };
1775 8         38 my @list1 = $compute_list->();
1776 8         53 my @list2 = $compute_list->();
1777 8         65 is( scalar(@list1), 5, "list has 5 items" );
1778 8         3143 cmp_deeply( \@list1, \@list2, "lists are the same" );
1779 9     9   5588 }
  9         31  
  9         56  
1780              
1781             sub test_compress_threshold : Tests {
1782 7     7 0 5239 my $self = shift;
1783 7         21 my $cache = $self->{cache};
1784              
1785 7         21 my $s0 = 'x' x 180;
1786 7         23 my $s1 = 'x' x 200;
1787 7         129 $cache->set( 'key0', $s0 );
1788 7         97 $cache->set( 'key1', $s1 );
1789 7         40 is_between( $cache->get_object('key0')->size, 180, 220 );
1790 7         2963 is_between( $cache->get_object('key1')->size, 200, 240 );
1791              
1792 7         2524 my $cache2 = $self->new_cache( compress_threshold => 190 );
1793 7         101 $cache2->set( 'key0', $s0 );
1794 7         96 $cache2->set( 'key1', $s1 );
1795 7         55 is_between( $cache2->get_object('key0')->size, 180, 220 );
1796 7         4792 ok( $cache2->get_object('key1')->size < 100 );
1797 7         2738 is( $cache2->get('key0'), $s0 );
1798 7         2866 is( $cache2->get('key1'), $s1 );
1799 9     9   4180 }
  9         23  
  9         51  
1800              
1801             sub test_expires_on_backend : Tests {
1802 8     8 0 7989 my $self = shift;
1803              
1804 8 100       66 return "skipping - no support for expires_on_backend"
1805             unless $self->supports_expires_on_backend();
1806 1         4 foreach my $expires_on_backend ( 0, 1 ) {
1807 2         474 my $cache =
1808             $self->new_cache( expires_on_backend => $expires_on_backend );
1809 2         14 $cache->set( 'key0', 5, '2s' );
1810 2         14 $cache->set( 'key1', 6, { expires_at => time + 2 } );
1811 2         13 is( $cache->get('key0'), 5, 'hit key0 before expire' );
1812 2         1358 is( $cache->get('key1'), 6, 'hit key1 before expire' );
1813 2         6001403 sleep(3);
1814 2         88 ok( !defined( $cache->get('key0') ), 'miss key0 after expire' );
1815 2         1943 ok( !defined( $cache->get('key1') ), 'miss key1 after expire' );
1816              
1817 2 100       914 if ($expires_on_backend) {
1818 1         6 ok(
1819             !defined( $cache->get_object('key0') ),
1820             'cannot get_object(key0) after expire'
1821             );
1822 1         610 ok(
1823             !defined( $cache->get_object('key1') ),
1824             'cannot get_object(key1) after expire'
1825             );
1826             }
1827             else {
1828 1         7 ok(
1829             $cache->get_object('key0')->is_expired(),
1830             'can get_object(key0) after expire'
1831             );
1832 1         463 ok(
1833             $cache->get_object('key1')->is_expired(),
1834             'can get_object(key1) after expire'
1835             );
1836             }
1837             }
1838 9     9   4314 }
  9         22  
  9         50  
1839              
1840             sub test_append : Tests {
1841 6     6 0 6116 my $self = shift;
1842 6         18 my $cache = $self->{cache};
1843             my ( $key, $value ) =
1844 6         30 ( $self->{keys}->{arrayref}, $self->{values}->{medium} );
1845              
1846             # Appending to non-existent key has no effect
1847             #
1848 6         223 $cache->append( $key, $value );
1849 6         307 ok( !$cache->get($key) );
1850              
1851 6         2651 ok( $cache->set( $key, $value ) );
1852 6         2491 $cache->append( $key, $value );
1853 6         70 is( $cache->get($key), $value . $value );
1854 6         2629 $cache->append( $key, $value );
1855 6         72 is( $cache->get($key), $value . $value . $value );
1856 9     9   3643 }
  9         21  
  9         39  
1857              
1858             sub test_add : Tests {
1859 8     8 0 35153 my $self = shift;
1860 8         26 my $cache = $self->{cache};
1861             my ( $key, $value ) =
1862 8         42 ( $self->{keys}->{arrayref}, $self->{values}->{medium} );
1863              
1864 8         36 my $t = time();
1865              
1866 8         125 $cache->add( $key, $value, { expires_at => $t + 100 } );
1867 8         143 is( $cache->get($key), $value, "get" );
1868 8         4882 is( $cache->get_object($key)->expires_at, $t + 100, "expires_at" );
1869              
1870 8         2855 $cache->add( $key, $value . $value, { expires_at => $t + 200 } );
1871 8         116 is( $cache->get($key), $value, "get (after add)" );
1872 8         3036 is( $cache->get_object($key)->expires_at,
1873             $t + 100, "expires_at (after add)" );
1874              
1875 8         3111 $cache->remove($key);
1876 8         741 $cache->add( $key, $value . $value, { expires_at => $t + 200 } );
1877 8         105 is( $cache->get($key), $value . $value, "get (after expire and add)" );
1878 8         3086 is( $cache->get_object($key)->expires_at,
1879             $t + 200, "expires_at (after expire and add)" );
1880 9     9   4132 }
  9         19  
  9         50  
1881              
1882             sub test_replace : Tests {
1883 8     8 0 16274 my $self = shift;
1884 8         25 my $cache = $self->{cache};
1885             my ( $key, $value ) =
1886 8         51 ( $self->{keys}->{arrayref}, $self->{values}->{medium} );
1887              
1888 8         25 my $t = time();
1889              
1890 8         187 $cache->replace( $key, $value, { expires_at => $t + 100 } );
1891 8         45 ok( !$cache->get_object($key), "get" );
1892              
1893 8         3933 $cache->set( $key, $value . $value, { expires_at => $t + 200 } );
1894 8         64 $cache->replace( $key, $value, { expires_at => $t + 100 } );
1895 8         108 is( $cache->get($key), $value, "get (after replace)" );
1896 8         4499 is( $cache->get_object($key)->expires_at,
1897             $t + 100, "expires_at (after replace)" );
1898 9     9   3715 }
  9         22  
  9         45  
1899              
1900             sub test_max_key_length : Tests {
1901 6     6 0 5580 my $self = shift;
1902              
1903             # Test max_key_length and also that key does not get transformed twice in mirror_cache
1904             #
1905 6         21 my $mirror_store = {};
1906 6         48 my $cache = $self->new_cleared_cache(
1907             max_key_length => 10,
1908             mirror_cache => { driver => 'Memory', datastore => $mirror_store }
1909             );
1910              
1911 6         25 foreach my $keyname ( 'medium', 'large' ) {
1912             my ( $key, $value ) =
1913 12         2456 ( $self->{keys}->{$keyname}, $self->{values}->{$keyname} );
1914 12         312 $cache->set( $key, $value );
1915 12         363 is( $cache->get($key), $value, $keyname );
1916 12         6363 is( $cache->mirror_cache->get($key), $value, $keyname );
1917 12 100       4712 if ( $keyname eq 'medium' ) {
1918 6         33 is( $cache->get_object($key)->key(), $key, "medium key stored" );
1919             }
1920             else {
1921 6         36 isnt( $cache->get_object($key)->key(), $key, "md5 key stored" );
1922 6         2476 is( length( $cache->get_object($key)->key() ),
1923             32, "md5 key stored" );
1924             }
1925             }
1926 9     9   4073 }
  9         25  
  9         47  
1927              
1928             # Test that cache does not get corrupted with multiple concurrent processes writing
1929             #
1930             sub test_multiple_processes : Tests {
1931 5     5 0 3952 my $self = shift;
1932 5 50       40 return "author test only" unless $ENV{AUTHOR_TESTING};
1933 0 0       0 return "does not pass on file driver"
1934             if $self->new_cache->short_driver_name eq 'File';
1935              
1936 0         0 my ( @values, @pids, %valid_values );
1937 0         0 my $shared_key = $self->{keys}->{medium};
1938 0         0 my $num_procs = 4;
1939              
1940 0         0 local $SIG{CHLD} = 'IGNORE';
1941              
1942             # Each child continuously writes a unique 10000 byte string to a single shared key
1943             #
1944             my $child_action = sub {
1945 0     0   0 my $p = shift;
1946 0         0 my $value = $values[$p];
1947 0         0 my $child_cache = $self->new_cache();
1948              
1949 0         0 sleep(1); # Wait for parent to be ready
1950 0         0 my $child_end_time = time() + 5;
1951 0         0 while ( time < $child_end_time ) {
1952 0         0 $child_cache->set( $shared_key, $value );
1953             }
1954 0         0 $child_cache->set( "done$p", 1 );
1955 0         0 };
1956              
1957 0         0 foreach my $p ( 0 .. $num_procs ) {
1958 0         0 $values[$p] = random_string(10000);
1959 0         0 $valid_values{ $values[$p] } = $p;
1960 0 0       0 if ( my $pid = fork() ) {
1961 0         0 $pids[$p] = $pid;
1962             }
1963             else {
1964 0         0 $child_action->($p);
1965 0         0 exit;
1966             }
1967             }
1968              
1969             # Parent continuously gets shared key, makes sure it is one of the valid values.
1970             # Loop until we see done flag for each child process, or until 10 secs pass.
1971             # At end make sure we saw each process's value once.
1972             #
1973 0         0 my ( %seen, $error );
1974 0         0 my $parent_end_time = time() + 10;
1975 0         0 my $parent_cache = $self->new_cache();
1976 0         0 while ( !$error ) {
1977 0         0 for ( my $i = 0 ; $i < 100 ; $i++ ) {
1978 0         0 my $value = $parent_cache->get($shared_key);
1979 0 0       0 if ( defined($value) ) {
1980 0 0       0 if ( defined( my $p = $valid_values{$value} ) ) {
1981 0         0 $seen{$p} = 1;
1982             }
1983             else {
1984 0         0 $error = "got invalid value '$value' from shared key";
1985 0         0 last;
1986             }
1987             }
1988             }
1989 0 0       0 if ( !grep { !$parent_cache->get("done$_") } ( 0 .. $num_procs ) ) {
  0         0  
1990 0         0 last;
1991             }
1992 0 0       0 if ( time() >= $parent_end_time ) {
1993 0         0 $error = "did not see all done flags after 10 secs";
1994             }
1995             }
1996              
1997 0 0       0 if ( !$error ) {
1998 0 0       0 if ( my ($p) = grep { !$seen{$_} } ( 0 .. $num_procs ) ) {
  0         0  
1999 0         0 $error = "never saw value from process $p";
2000             }
2001             }
2002              
2003 0 0       0 if ($error) {
2004 0         0 ok( 0, $error );
2005             }
2006             else {
2007 0         0 ok( 1, "passed" );
2008             }
2009 9     9   6989 }
  9         30  
  9         45  
2010              
2011             1;