File Coverage

blib/lib/CHI/t/Driver.pm
Criterion Covered Total %
statement 1024 1230 83.2
branch 32 74 43.2
condition 8 20 40.0
subroutine 169 182 92.8
pod 0 65 0.0
total 1233 1571 78.4


line stmt bran cond sub pod time code
1             package CHI::t::Driver;
2             $CHI::t::Driver::VERSION = '0.60';
3 9     9   48 use strict;
  9         12  
  9         299  
4 9     9   37 use warnings;
  9         12  
  9         253  
5 9     9   37 use CHI::Test;
  9         13  
  9         61  
6             use CHI::Test::Util
7 9     9   49 qw(activate_test_logger cmp_bool is_between random_string skip_until);
  9         14  
  9         717  
8 9     9   82 use CHI::Util qw(can_load dump_one_line write_file);
  9         19  
  9         509  
9 9     9   44 use Encode;
  9         13  
  9         798  
10 9     9   53 use File::Spec::Functions qw(tmpdir);
  9         12  
  9         559  
11 9     9   2136 use File::Temp qw(tempdir);
  9         49716  
  9         451  
12 9     9   57 use List::Util qw(shuffle);
  9         15  
  9         572  
13 9     9   41 use Scalar::Util qw(weaken);
  9         14  
  9         372  
14 9     9   39 use Storable qw(dclone);
  9         12  
  9         501  
15 9     9   3536 use Test::Warn;
  9         11883  
  9         521  
16 9     9   51 use Time::HiRes qw(usleep);
  9         13  
  9         78  
17 9     9   1375 use base qw(CHI::Test::Class);
  9         15  
  9         3773  
18              
19             # Flags indicating what each test driver supports
20 384     384 0 5537 sub supports_clear { 1 }
21 7     7 0 44 sub supports_expires_on_backend { 0 }
22 5     5 0 22 sub supports_get_namespaces { 1 }
23              
24             sub standard_keys_and_values : Test(startup) {
25 7     7 0 14269 my ($self) = @_;
26              
27 7         71 my ( $keys_ref, $values_ref ) = $self->set_standard_keys_and_values();
28 7         28 $self->{keys} = $keys_ref;
29 7         21 $self->{values} = $values_ref;
30 7         15 $self->{keynames} = [ keys( %{$keys_ref} ) ];
  7         47  
31 7         21 $self->{key_count} = scalar( @{ $self->{keynames} } );
  7         26  
32 7         103 $self->{all_test_keys} = [ values(%$keys_ref), $self->extra_test_keys() ];
33 7         212 my $cache = $self->new_cache();
34 7         31 push(
35 7         118 @{ $self->{all_test_keys} },
36 7         25 $self->process_keys( $cache, @{ $self->{all_test_keys} } )
37             );
38 644         1268 $self->{all_test_keys_hash} =
39 7         34 { map { ( $_, 1 ) } @{ $self->{all_test_keys} } };
  7         34  
40 9     9   87 }
  9         13  
  9         52  
41              
42             sub kvpair {
43 151     151 0 281 my $self = shift;
44 151   100     782 my $count = shift || 1;
45              
46 176 100       1673 return map {
    100          
47 151         487 (
48             $self->{keys}->{medium} . ( $_ == 1 ? '' : $_ ),
49             $self->{values}->{medium} . ( $_ == 1 ? '' : $_ )
50             )
51             } ( 1 .. $count );
52             }
53              
54             sub setup : Test(setup) {
55 367     367 0 610511 my $self = shift;
56              
57 367         2194 $self->{cache} = $self->new_cache();
58 367 50       12457 $self->{cache}->clear() if $self->supports_clear();
59 9     9   3724 }
  9         27  
  9         43  
60              
61             sub testing_driver_class {
62 292     292 0 419 my $self = shift;
63 292         556 my $class = ref($self);
64              
65             # By default, take the last part of the classname and use it as driver
66 292         1570 my $driver_class = 'CHI::Driver::' . ( split( '::', $class ) )[-1];
67 292         2489 return $driver_class;
68             }
69              
70             sub testing_chi_root_class {
71 358     358 0 1875 return 'CHI';
72             }
73              
74             sub new_cache {
75 347     347 0 590 my $self = shift;
76              
77 347         1653 return $self->testing_chi_root_class->new( $self->new_cache_options(), @_ );
78             }
79              
80             sub new_cleared_cache {
81 82     82 0 1027 my $self = shift;
82              
83 82         360 my $cache = $self->new_cache(@_);
84 82         2111 $cache->clear();
85 82         1473 return $cache;
86             }
87              
88             sub new_cache_options {
89 631     631 0 968 my $self = shift;
90              
91             return (
92 631         2617 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 7     7 0 18 my $self = shift;
100              
101 7         12 my ( %keys, %values );
102 7         47 my @mixed_chars = ( 32 .. 48, 57 .. 65, 90 .. 97, 122 .. 126, 240 );
103              
104 280         405 %keys = (
105             'space' => ' ',
106             'newline' => "\n",
107             'char' => 'a',
108             'zero' => 0,
109             'one' => 1,
110             'medium' => 'medium',
111 889         1342 'mixed' => join( "", map { chr($_) } @mixed_chars ),
112 7         26 'binary' => join( "", map { chr($_) } ( 129 .. 255 ) ),
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 91 100       351 %values = map {
121 7         102 ( $_, ref( $keys{$_} ) ? $keys{$_} : scalar( reverse( $keys{$_} ) ) )
122             } keys(%keys);
123 7         31 $values{empty} = '';
124              
125 7         42 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 7     7 0 18 my ($class) = @_;
135             return (
136 21         68 '', '2',
137             'medium2', 'foo',
138             'hashref', 'test_namespace_types',
139             "utf8", "encoded",
140 147         296 "binary", ( map { "done$_" } ( 0 .. 2 ) ),
141 7         28 ( map { "key$_" } ( 0 .. 20 ) )
142             );
143             }
144              
145             sub set_some_keys {
146 50     50 0 118 my ( $self, $c ) = @_;
147              
148 50         88 foreach my $keyname ( @{ $self->{keynames} } ) {
  50         199  
149 590         8780 $c->set( $self->{keys}->{$keyname}, $self->{values}->{$keyname} );
150             }
151             }
152              
153             sub test_encode : Tests {
154 7     7 0 1302 my $self = shift;
155 7         41 my $cache = $self->new_cleared_cache();
156              
157 7         31 my $utf8 = $self->{keys}->{utf8};
158 7         37 my $encoded = encode( utf8 => $utf8 );
159 7         262 my $binary_off = $self->{keys}->{binary};
160 7         64 my $binary_on = substr( $binary_off . $utf8, 0, length($binary_off) );
161              
162 7         52 ok( $binary_off eq $binary_on, "binary_off eq binary_on" );
163 7         3225 ok( !Encode::is_utf8($binary_off), "!is_utf8(binary_off)" );
164 7         2680 ok( Encode::is_utf8($binary_on), "is_utf8(binary_on)" );
165              
166             # Key maps to same thing whether encoded or non-encoded
167             #
168 7         3344 my $value = time;
169 7         191 $cache->set( $utf8, $value );
170 7         127 is( $cache->get($utf8), $value, "get" );
171 7         3031 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 7         3011 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 7         140 $cache->set( "utf8", $utf8 );
191 7         98 is( $cache->get("utf8"), $utf8, "utf8 in scalar" );
192 7         3419 $cache->set( "utf8", [$utf8] );
193 7         105 is( $cache->get("utf8")->[0], $utf8, "utf8 in arrayref" );
194              
195 7         4091 $cache->set( "encoded", $encoded );
196 7         92 is( $cache->get("encoded"), $encoded, "encoded in scalar" );
197 7         3243 $cache->set( "encoded", [$encoded] );
198 7         100 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 7         5418 $cache->set( "binary", $binary_off );
203 7         91 is( $cache->get("binary"), $binary_on, "stored binary_off = binary_on" );
204 7         3021 $cache->set( "binary", $binary_on );
205 7         94 is( $cache->get("binary"), $binary_off, "stored binary_on = binary_off" );
206 9     9   9323 }
  9         21  
  9         45  
207              
208             sub test_simple : Tests {
209 11     11 0 1395 my $self = shift;
210 11   33     76 my $cache = shift || $self->{cache};
211              
212 11         220 ok( $cache->set( $self->{keys}->{medium}, $self->{values}->{medium} ) );
213 9         4881 is( $cache->get( $self->{keys}->{medium} ), $self->{values}->{medium} );
214 9     9   2672 }
  9         23  
  9         39  
215              
216             sub test_driver_class : Tests {
217 7     7 0 1484 my $self = shift;
218 7         24 my $cache = $self->{cache};
219              
220 7         44 isa_ok( $cache, 'CHI::Driver' );
221 7         4629 isa_ok( $cache, $cache->driver_class );
222 7         3256 can_ok( $cache, 'get', 'set', 'remove', 'clear', 'expire' );
223 9     9   2508 }
  9         19  
  9         46  
224              
225             sub test_key_types : Tests {
226 7     7 0 1850 my $self = shift;
227 7         24 my $cache = $self->{cache};
228 7         91 $self->num_tests( $self->{key_count} * 9 + 1 );
229              
230 7         715 my @keys_set;
231             my $check_keys_set = sub {
232 149     149   573 my $desc = shift;
233 149         690 cmp_set( [ $cache->get_keys ], \@keys_set, "checking keys $desc" );
234 7         47 };
235              
236 7         21 $check_keys_set->("before sets");
237 7         5840 foreach my $keyname ( @{ $self->{keynames} } ) {
  7         37  
238 79         65381 my $key = $self->{keys}->{$keyname};
239 79         316 my $value = $self->{values}->{$keyname};
240 79         1594 ok( !defined $cache->get($key), "miss for key '$keyname'" );
241 79         36017 is( $cache->set( $key, $value ), $value, "set for key '$keyname'" );
242 77         35039 push( @keys_set, $self->process_keys( $cache, $key ) );
243 77         355 $check_keys_set->("after set of key '$keyname'");
244 77         489026 cmp_deeply( $cache->get($key), $value, "hit for key '$keyname'" );
245             }
246              
247 5         2317 foreach my $keyname ( reverse @{ $self->{keynames} } ) {
  5         21  
248 65         305877 my $key = $self->{keys}->{$keyname};
249 65         2292 $cache->remove($key);
250 65         954 ok( !defined $cache->get($key),
251             "miss after remove for key '$keyname'" );
252 65         21760 pop(@keys_set);
253 65         278 $check_keys_set->("after removal of key '$keyname'");
254             }
255              
256             # Confirm that transform_key is idempotent
257             #
258 5         2429 foreach my $keyname ( @{ $self->{keynames} } ) {
  5         20  
259 65         30312 my $key = $self->{keys}->{$keyname};
260 65         146 my $value = $self->{values}->{$keyname};
261 65         205 is(
262             $cache->transform_key( $cache->transform_key($key) ),
263             $cache->transform_key($key),
264             "transform_key is idempotent for '$keyname'"
265             );
266 65         19991 $cache->clear();
267 65         848 $cache->set( $key, $value );
268 65         173 is( scalar( $cache->get_keys() ), 1, "exactly one key" );
269 65         20469 cmp_deeply( $cache->get( ( $cache->get_keys )[0] ),
270             $value, "get with get_keys[0] got same value" );
271             }
272 9     9   5779 }
  9         18  
  9         40  
273              
274             sub test_deep_copy : Tests {
275 6     6 0 1388 my $self = shift;
276 6         18 my $cache = $self->{cache};
277              
278 6         40 $self->set_some_keys($cache);
279 4         11 foreach my $keyname (qw(arrayref hashref)) {
280 8         1246 my $key = $self->{keys}->{$keyname};
281 8         20 my $value = $self->{values}->{$keyname};
282 8         132 cmp_deeply( $cache->get($key), $value,
283             "get($key) returns original data structure" );
284 8         35154 cmp_deeply( $cache->get($key), $cache->get($key),
285             "multiple get($key) return same data structure" );
286 8         13678 isnt( $cache->get($key), $value,
287             "get($key) does not return original reference" );
288 8         3496 isnt( $cache->get($key), $cache->get($key),
289             "multiple get($key) do not return same reference" );
290             }
291              
292 4         1570 my $struct = { a => [ 1, 2 ], b => [ 4, 5 ] };
293 4         202 my $struct2 = dclone($struct);
294 4         80 $cache->set( 'hashref', $struct );
295 4         7 push( @{ $struct->{a} }, 3 );
  4         14  
296 4         11 delete( $struct->{b} );
297 4         64 cmp_deeply( $cache->get('hashref'),
298             $struct2,
299             "altering original set structure does not affect cached copy" );
300 9     9   3965 }
  9         17  
  9         36  
301              
302             sub test_expires_immediately : Tests {
303 7     7 0 1231 my $self = shift;
304              
305 7 50       44 return 'author testing only - timing is unreliable'
306             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   3934 }
  9         15  
  9         46  
334              
335             sub test_expires_shortly : Tests {
336 7     7 0 1591 my $self = shift;
337              
338 7 50       47 return 'author testing only - timing is unreliable'
339             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   4169 }
  9         16  
  9         48  
373              
374             sub test_expires_later : Tests {
375 7     7 0 1436 my $self = shift;
376              
377 7 50       45 return 'author testing only - timing is unreliable'
378             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   4692 }
  9         19  
  9         35  
411              
412             sub test_expires_never : Tests {
413 7     7 0 1677 my $self = shift;
414 7         15 my $cache;
415              
416             # Expires never (will fail in 2037)
417 7         42 my ( $key, $value ) = $self->kvpair();
418             my $test_expires_never = sub {
419 14     14   37 my (@set_options) = @_;
420 14         393 $cache->set( $key, $value, @set_options );
421 14         114 ok(
422             $cache->get_expires_at($key) >
423             time + Time::Duration::Parse::parse_duration('1 year'),
424             "expires never"
425             );
426 14         7834 ok( !$cache->exists_and_is_expired($key), "not expired" );
427 14         5370 ok( $cache->is_valid($key), "valid" );
428 7         52 };
429              
430             # never is default
431 7         43 $cache = $self->new_cache();
432 7         36 $test_expires_never->();
433              
434             # expires_in default should be ignored when never passed to set (RT #67970)
435 7         2677 $cache = $self->new_cache( expires_in => '1s' );
436 7         243 $test_expires_never->('never');
437 9     9   3226 }
  9         21  
  9         42  
438              
439             sub test_expires_defaults : Tests {
440 7     7 0 1701 my $self = shift;
441              
442 7         21 my $start_time = time();
443 7         20 local $CHI::Driver::Test_Time = $start_time;
444 7         14 my $cache;
445              
446             my $set_and_confirm_expires_at = sub {
447 28     28   60 my ( $expected_expires_at, $desc ) = @_;
448 28         123 my ( $key, $value ) = $self->kvpair();
449 28         472 $cache->set( $key, $value );
450 28         188 is( $cache->get_expires_at($key), $expected_expires_at, $desc );
451 28         14044 $cache->clear();
452 7         45 };
453              
454 7         41 $cache = $self->new_cache( expires_in => 10 );
455 7         43 $set_and_confirm_expires_at->(
456             $start_time + 10,
457             "after expires_in constructor option"
458             );
459 7         215 $cache->expires_in(20);
460 7         336 $set_and_confirm_expires_at->( $start_time + 20,
461             "after expires_in method" );
462              
463 7         60 $cache = $self->new_cache( expires_at => $start_time + 30 );
464 7         278 $set_and_confirm_expires_at->(
465             $start_time + 30,
466             "after expires_at constructor option"
467             );
468 7         53 $cache->expires_at( $start_time + 40 );
469 7         28 $set_and_confirm_expires_at->( $start_time + 40,
470             "after expires_at method" );
471 9     9   3054 }
  9         14  
  9         37  
472              
473             sub test_expires_manually : Tests {
474 7     7 0 1235 my $self = shift;
475 7         19 my $cache = $self->{cache};
476              
477 7         40 my ( $key, $value ) = $self->kvpair();
478 7         17 my $desc = "expires manually";
479 7         148 $cache->set( $key, $value );
480 7         130 is( $cache->get($key), $value, "hit ($desc)" );
481 7         3595 $cache->expire($key);
482 7         124 ok( !defined $cache->get($key), "miss after expire ($desc)" );
483 7         2856 ok( !$cache->is_valid($key), "invalid after expire ($desc)" );
484 9     9   2884 }
  9         16  
  9         47  
485              
486             sub test_expires_conditionally : Tests {
487 7     7 0 1893 my $self = shift;
488 7         26 my $cache = $self->{cache};
489              
490             # Expires conditionally
491             my $test_expires_conditionally = sub {
492 28     28   68 my ( $code, $cond_desc, $expect_expire ) = @_;
493              
494 28         168 my ( $key, $value ) = $self->kvpair();
495 28         79 my $desc = "expires conditionally ($cond_desc)";
496 28         514 $cache->set( $key, $value );
497 28 100       404 is(
498             $cache->get( $key, expire_if => $code ),
499             $expect_expire ? undef : $value,
500             "get result ($desc)"
501             );
502              
503 28         15384 is( $cache->get($key), $value, "hit after expire_if ($desc)" );
504              
505 7         49 };
506 7         21 my $time = time();
507 7     8   39 $test_expires_conditionally->( sub { 1 }, 'true', 1 );
  8         29  
508 7     7   3139 $test_expires_conditionally->( sub { 0 }, 'false', 0 );
  7         28  
509             $test_expires_conditionally->(
510 8     8   41 sub { $_[0]->created_at >= $time },
511 7         3235 'created_at >= now', 1
512             );
513             $test_expires_conditionally->(
514 7     7   32 sub { $_[0]->created_at < $time },
515 7         3484 'created_at < now', 0
516             );
517 9     9   3919 }
  9         16  
  9         40  
518              
519             sub test_expires_variance : Tests {
520 7     7 0 1393 my $self = shift;
521 7         21 my $cache = $self->{cache};
522              
523 7         16 my $start_time = time();
524 7         18 my $expires_at = $start_time + 10;
525 7         45 my ( $key, $value ) = $self->kvpair();
526 7         188 $cache->set( $key, $value,
527             { expires_at => $expires_at, expires_variance => 0.5 } );
528 7         91 is( $cache->get_object($key)->expires_at(),
529             $expires_at, "expires_at = $start_time" );
530 7         3516 is(
531             $cache->get_object($key)->early_expires_at(),
532             $start_time + 5,
533             "early_expires_at = $start_time + 5"
534             );
535              
536 7         3068 my %expire_count;
537 7         43 for ( my $time = $start_time + 3 ; $time <= $expires_at + 1 ; $time++ ) {
538 63         129 local $CHI::Driver::Test_Time = $time;
539 63         155 for ( my $i = 0 ; $i < 100 ; $i++ ) {
540 6300 100       71755 if ( !defined $cache->get($key) ) {
541 2755         7722 $expire_count{$time}++;
542             }
543             }
544             }
545 7         41 for ( my $time = $start_time + 3 ; $time <= $start_time + 5 ; $time++ ) {
546 21         7595 ok( !$expire_count{$time}, "got no expires at $time" );
547             }
548 7         2647 for ( my $time = $start_time + 7 ; $time <= $start_time + 8 ; $time++ ) {
549 14   33     2829 ok( $expire_count{$time} > 0 && $expire_count{$time} < 100,
550             "got some expires at $time" );
551             }
552 7         2718 for ( my $time = $expires_at ; $time <= $expires_at + 1 ; $time++ ) {
553 14         2772 ok( $expire_count{$time} == 100, "got all expires at $time" );
554             }
555 9     9   4233 }
  9         16  
  9         39  
556              
557             sub test_not_in_cache : Tests {
558 7     7 0 1510 my $self = shift;
559 7         22 my $cache = $self->{cache};
560              
561 7         91 ok( !defined $cache->get_object('not in cache') );
562 7         3343 ok( !defined $cache->get_expires_at('not in cache') );
563 7         2326 ok( !$cache->is_valid('not in cache') );
564 9     9   2691 }
  9         18  
  9         70  
565              
566             sub test_serialize : Tests {
567 6     6 0 1381 my $self = shift;
568 6         19 my $cache = $self->{cache};
569 6         76 $self->num_tests( $self->{key_count} );
570              
571 6         647 $self->set_some_keys($cache);
572 4         8 foreach my $keyname ( @{ $self->{keynames} } ) {
  4         15  
573 52 100 100     16365 my $expect_transformed =
    100          
574             ( $keyname eq 'arrayref' || $keyname eq 'hashref' ) ? 1
575             : ( $keyname eq 'utf8' ) ? 2
576             : 0;
577 52         245 is(
578             $cache->get_object( $self->{keys}->{$keyname} )->_is_transformed(),
579             $expect_transformed,
580             "is_transformed = $expect_transformed ($keyname)"
581             );
582             }
583 9     9   2825 }
  9         16  
  9         82  
584              
585             {
586             package DummySerializer;
587             $DummySerializer::VERSION = '0.60';
588 0     0   0 sub serialize { }
589 0     0   0 sub deserialize { }
590             }
591              
592             sub test_serializers : Tests {
593 6     6 0 1455 my ($self) = @_;
594              
595 6 50       35 unless ( can_load('Data::Serializer') ) {
596 6         87 $self->num_tests(1);
597 6         748 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   5749 }
  9         17  
  9         54  
653              
654             sub test_namespaces : Tests {
655 7     7 0 1464 my $self = shift;
656 7         25 my $cache = $self->{cache};
657              
658 7         36 my $cache0 = $self->new_cache();
659 7         127 is( $cache0->namespace, 'Default', 'namespace defaults to "Default"' );
660              
661 7         2868 my ( $ns1, $ns2, $ns3 ) = ( 'ns1', 'ns2', 'ns3' );
662 28         86 my ( $cache1, $cache1a, $cache2, $cache3 ) =
663 7         25 map { $self->new_cache( namespace => $_ ) } ( $ns1, $ns1, $ns2, $ns3 );
664 28         125 cmp_deeply(
665 7         26 [ map { $_->namespace } ( $cache1, $cache1a, $cache2, $cache3 ) ],
666             [ $ns1, $ns1, $ns2, $ns3 ],
667             'cache->namespace()'
668             );
669 7         11101 $self->set_some_keys($cache1);
670 5         109 cmp_deeply(
671             $cache1->dump_as_hash(),
672             $cache1a->dump_as_hash(),
673             'cache1 and cache1a are same cache'
674             );
675 5         18243 cmp_deeply( [ $cache2->get_keys() ],
676             [], 'cache2 empty after setting keys in cache1' );
677 5         5655 $cache3->set( $self->{keys}->{medium}, 'different' );
678 5         69 is(
679             $cache1->get('medium'),
680             $self->{values}->{medium},
681             'cache1{medium} = medium'
682             );
683 5         2116 is( $cache3->get('medium'), 'different', 'cache1{medium} = different' );
684              
685 5 50       1583 if ( $self->supports_get_namespaces() ) {
686              
687             # get_namespaces may or may not automatically include empty namespaces
688 5         211 cmp_deeply(
689             [ $cache1->get_namespaces() ],
690             supersetof( $ns1, $ns3 ),
691             "get_namespaces contains $ns1 and $ns3"
692             );
693              
694 5         7141 foreach my $c ( $cache0, $cache1, $cache1a, $cache2, $cache3 ) {
695 25         54258 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   4361 }
  9         18  
  9         37  
711              
712             sub test_persist : Tests {
713 7     7 0 1691 my $self = shift;
714 7         23 my $cache = $self->{cache};
715              
716 7         13 my $hash;
717             {
718 7         13 my $cache1 = $self->new_cache();
  7         36  
719 7         66 $self->set_some_keys($cache1);
720 5         75 $hash = $cache1->dump_as_hash();
721             }
722 5         31 my $cache2 = $self->new_cache();
723 5         32 cmp_deeply(
724             $hash,
725             $cache2->dump_as_hash(),
726             'cache persisted between cache object creations'
727             );
728 9     9   2371 }
  9         17  
  9         58  
729              
730             sub test_multi : Tests {
731 7     7 0 1557 my $self = shift;
732 7         26 my $cache = $self->{cache};
733              
734 7         39 my ( $keys, $values, $keynames ) =
735             ( $self->{keys}, $self->{values}, $self->{keynames} );
736              
737 7         14 my @ordered_keys = map { $keys->{$_} } @{$keynames};
  91         167  
  7         22  
738 91         153 my @ordered_values =
739 7         22 map { $values->{$_} } @{$keynames};
  7         18  
740 77         194 my %ordered_scalar_key_values =
741 91         126 map { ( $keys->{$_}, $values->{$_} ) }
742 7         21 grep { !ref( $keys->{$_} ) } @{$keynames};
  7         18  
743              
744 7         141 cmp_deeply( $cache->get_multi_arrayref( ['foo'] ),
745             [undef], "get_multi_arrayref before set" );
746              
747 7         9902 $cache->set_multi( \%ordered_scalar_key_values );
748 5         79 $cache->set( $keys->{arrayref}, $values->{arrayref} );
749 5         55 $cache->set( $keys->{hashref}, $values->{hashref} );
750              
751 5         29 cmp_deeply( $cache->get_multi_arrayref( \@ordered_keys ),
752             \@ordered_values, "get_multi_arrayref" );
753 5         15479 cmp_deeply( $cache->get( $ordered_keys[0] ),
754             $ordered_values[0], "get one after set_multi" );
755 5         2078 cmp_deeply(
756             $cache->get_multi_arrayref( [ reverse @ordered_keys ] ),
757             [ reverse @ordered_values ],
758             "get_multi_arrayref"
759             );
760 65         207 cmp_deeply(
761 5         15451 $cache->get_multi_hashref( [ grep { !ref($_) } @ordered_keys ] ),
762             \%ordered_scalar_key_values, "get_multi_hashref" );
763 5         6791 cmp_set(
764             [ $cache->get_keys ],
765             [ $self->process_keys( $cache, @ordered_keys ) ],
766             "get_keys after set_multi"
767             );
768              
769 5         65808 $cache->remove_multi( \@ordered_keys );
770 5         33 cmp_deeply(
771             $cache->get_multi_arrayref( \@ordered_keys ),
772             [ (undef) x scalar(@ordered_values) ],
773             "get_multi_arrayref after remove_multi"
774             );
775 5         6316 cmp_set( [ $cache->get_keys ], [], "get_keys after remove_multi" );
776 9     9   4320 }
  9         14  
  9         76  
777              
778             sub test_multi_no_keys : Tests {
779 7     7 0 1577 my $self = shift;
780 7         21 my $cache = $self->{cache};
781              
782 7         125 cmp_deeply( $cache->get_multi_arrayref( [] ),
783             [], "get_multi_arrayref (no args)" );
784 7         9189 cmp_deeply( $cache->get_multi_hashref( [] ),
785             {}, "get_multi_hashref (no args)" );
786 7     7   12913 lives_ok { $cache->set_multi( {} ) } "set_multi (no args)";
  7         332  
787 7     7   2113 lives_ok { $cache->remove_multi( [] ) } "remove_multi (no args)";
  7         203  
788 9     9   2850 }
  9         20  
  9         38  
789              
790             sub test_l1_cache : Tests {
791 5     5 0 1114 my $self = shift;
792 5         18 my @keys = map { "key$_" } ( 0 .. 2 );
  15         50  
793 5         18 my @values = map { "value$_" } ( 0 .. 2 );
  15         41  
794 5         12 my ( $cache, $l1_cache );
795              
796 5 50       31 return "skipping - no support for clear" unless $self->supports_clear();
797              
798             my $test_l1_cache = sub {
799              
800 10     10   175 is( $l1_cache->subcache_type, "l1_cache", "subcache_type = l1_cache" );
801              
802             # Get on cache should populate l1 cache
803             #
804 10         3348 $cache->set( $keys[0], $values[0] );
805 10         171 $l1_cache->clear();
806 10         152 ok( !$l1_cache->get( $keys[0] ), "l1 miss after clear" );
807 10         4171 is( $cache->get( $keys[0] ),
808             $values[0], "primary hit after primary set" );
809 10         3491 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 10         3032 $l1_cache->set( $keys[0], $values[1] );
815 10         208 is( $cache->get( $keys[0] ),
816             $values[1], "got new value set explicitly in l1 cache" );
817 10         3042 $l1_cache->remove( $keys[0] );
818 10         244 is( $cache->get( $keys[0] ), $values[0], "got old value again" );
819              
820 10         3056 $cache->clear();
821 10         292 ok( !$cache->get( $keys[0] ), "miss after clear" );
822 10         2798 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 10         2744 $cache->set( $keys[0], $values[0] );
827 10         271 $cache->set( $keys[1], $values[1] );
828 10         250 $l1_cache->remove( $keys[0] );
829 10         43 $l1_cache->set( $keys[1], $values[2] );
830              
831 10         323 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 10         11978 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 10         16580 $self->_test_logging_with_l1_cache( $cache, $l1_cache );
847              
848 10         2817 $self->_test_common_subcache_features( $cache, $l1_cache, 'l1_cache' );
849 5         32 };
850              
851             # Test with current cache in primary position...
852             #
853 5         43 $cache =
854             $self->new_cache( l1_cache => { driver => 'Memory', datastore => {} } );
855 5         58 $l1_cache = $cache->l1_cache;
856 5         647 isa_ok( $cache, $self->testing_driver_class, 'cache' );
857 5         2990 isa_ok( $l1_cache, 'CHI::Driver::Memory', 'l1_cache' );
858 5         1668 $test_l1_cache->();
859              
860             # and in l1 position
861             #
862 5         1797 $cache = $self->testing_chi_root_class->new(
863             driver => 'Memory',
864             datastore => {},
865             l1_cache => { $self->new_cache_options() }
866             );
867 5         265 $l1_cache = $cache->l1_cache;
868 5         677 isa_ok( $cache, 'CHI::Driver::Memory', 'cache' );
869 5         2297 isa_ok( $l1_cache, $self->testing_driver_class, 'l1_cache' );
870 5         1408 $test_l1_cache->();
871 9     9   5299 }
  9         18  
  9         39  
872              
873             sub test_mirror_cache : Tests {
874 5     5 0 814 my $self = shift;
875 5         10 my ( $cache, $mirror_cache );
876 5         29 my ( $key, $value, $key2, $value2 ) = $self->kvpair(2);
877              
878 5 50       23 return "skipping - no support for clear" unless $self->supports_clear();
879              
880             my $test_mirror_cache = sub {
881              
882 10     10   114 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 10         3539 $cache->set( $key, $value );
888 10         323 $mirror_cache->remove($key);
889 10         303 $cache->get($key);
890 10         99 ok( !$mirror_cache->get($key), "key not in mirror_cache" );
891              
892 10         4232 $mirror_cache->set( $key2, $value2 );
893 10         201 ok( !$cache->get($key2), "key2 not in cache" );
894              
895 10         3313 $self->_test_logging_with_mirror_cache( $cache, $mirror_cache );
896              
897 10         3105 $self->_test_common_subcache_features( $cache, $mirror_cache,
898             'mirror_cache' );
899 5         24 };
900              
901             my $file_cache_options = sub {
902 10     10   66 my $root_dir =
903             tempdir( "chi-test-mirror-cache-XXXX", TMPDIR => 1, CLEANUP => 1 );
904 10         4660 return ( driver => 'File', root_dir => $root_dir, depth => 3 );
905 5         19 };
906              
907             # Test with current cache in primary position...
908             #
909 5         17 $cache = $self->new_cache( mirror_cache => { $file_cache_options->() } );
910 5         44 $mirror_cache = $cache->mirror_cache;
911 5         591 isa_ok( $cache, $self->testing_driver_class );
912 5         2791 isa_ok( $mirror_cache, 'CHI::Driver::File' );
913 5         1806 $test_mirror_cache->();
914              
915             # and in mirror position
916             #
917 5         1703 $cache =
918             $self->testing_chi_root_class->new( $file_cache_options->(),
919             mirror_cache => { $self->new_cache_options() } );
920 5         253 $mirror_cache = $cache->mirror_cache;
921 5         662 isa_ok( $cache, 'CHI::Driver::File' );
922 5         2404 isa_ok( $mirror_cache, $self->testing_driver_class );
923 5         1678 $test_mirror_cache->();
924 9     9   3929 }
  9         13  
  9         63  
925              
926             sub test_subcache_overridable_params : Tests {
927 6     6 0 1236 my ($self) = @_;
928              
929 6         16 my $cache;
930             warning_like {
931 6     6   332 $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 6         68 qr/cannot override these keys/, "non-overridable subcache keys";
942 6         3887 is( $cache->l1_cache->expires_variance, $cache->expires_variance );
943 6         3098 is( $cache->l1_cache->serializer, $cache->serializer );
944 6         2308 is( $cache->l1_cache->on_set_error, $cache->on_set_error );
945 6         2374 is( $cache->l1_cache->on_get_error, 'log' );
946 9     9   3250 }
  9         14  
  9         77  
947              
948             # Run logging tests for a cache with an l1_cache
949             #
950             sub _test_logging_with_l1_cache {
951 10     10   24 my ( $self, $cache ) = @_;
952              
953 10         274 $cache->clear();
954 10         94 my $log = activate_test_logger();
955 10         68 my ( $key, $value ) = $self->kvpair();
956              
957 10         242 my $driver = $cache->label;
958              
959 10         582 my $miss_not_in_cache = 'MISS \(not in cache\)';
960 10         21 my $miss_expired = 'MISS \(expired\)';
961              
962 10         21 my $start_time = time();
963              
964 10         199 $cache->get($key);
965 10         446 $log->contains_ok(
966             qr/cache get for .* key='$key', cache='$driver', time='[-\d]+ms': $miss_not_in_cache/
967             );
968 10         3353 $log->contains_ok(
969             qr/cache get for .* key='$key', cache='.*l1.*', time='[-\d]+ms': $miss_not_in_cache/
970             );
971 10         3001 $log->empty_ok();
972              
973 10         2646 $cache->set( $key, $value, 81 );
974 10         364 $log->contains_ok(
975             qr/cache set for .* key='$key', size=\d+, expires='1m2[012]s', cache='$driver', time='[-\d]+ms'/
976             );
977              
978 10         3248 $log->contains_ok(
979             qr/cache set for .* key='$key', size=\d+, expires='1m2[012]s', cache='.*l1.*', time='[-\d]+ms'/
980             );
981 10         2730 $log->empty_ok();
982              
983 10         2632 $cache->get($key);
984 10         182 $log->contains_ok(
985             qr/cache get for .* key='$key', cache='.*l1.*', time='[-\d]+ms': HIT/);
986 10         2821 $log->empty_ok();
987              
988 10         2411 local $CHI::Driver::Test_Time = $start_time + 120;
989 10         294 $cache->get($key);
990 10         281 $log->contains_ok(
991             qr/cache get for .* key='$key', cache='$driver', time='[-\d]+ms': $miss_expired/
992             );
993 10         3051 $log->contains_ok(
994             qr/cache get for .* key='$key', cache='.*l1.*', time='[-\d]+ms': $miss_expired/
995             );
996 10         2791 $log->empty_ok();
997              
998 10         3711 $cache->remove($key);
999 10         252 $cache->get($key);
1000 10         319 $log->contains_ok(
1001             qr/cache get for .* key='$key', cache='$driver', time='[-\d]+ms': $miss_not_in_cache/
1002             );
1003 10         3125 $log->contains_ok(
1004             qr/cache get for .* key='$key', cache='.*l1.*', time='[-\d]+ms': $miss_not_in_cache/
1005             );
1006 10         2666 $log->empty_ok();
1007             }
1008              
1009             sub _test_logging_with_mirror_cache {
1010 10     10   26 my ( $self, $cache ) = @_;
1011              
1012 10         337 $cache->clear();
1013 10         102 my $log = activate_test_logger();
1014 10         62 my ( $key, $value ) = $self->kvpair();
1015              
1016 10         242 my $driver = $cache->label;
1017              
1018 10         73 my $miss_not_in_cache = 'MISS \(not in cache\)';
1019 10         21 my $miss_expired = 'MISS \(expired\)';
1020              
1021 10         17 my $start_time = time();
1022              
1023 10         197 $cache->get($key);
1024 10         396 $log->contains_ok(
1025             qr/cache get for .* key='$key', cache='$driver', time='[-\d]+ms': $miss_not_in_cache/
1026             );
1027 10         3993 $log->empty_ok();
1028              
1029 10         3245 $cache->set( $key, $value, 81 );
1030 10         372 $log->contains_ok(
1031             qr/cache set for .* key='$key', size=\d+, expires='1m2[012]s', cache='$driver', time='[-\d]+ms'/
1032             );
1033              
1034 10         4009 $log->contains_ok(
1035             qr/cache set for .* key='$key', size=\d+, expires='1m2[012]s', cache='.*mirror.*', time='[-\d]+ms'/
1036             );
1037 10         3279 $log->empty_ok();
1038              
1039 10         3199 $cache->get($key);
1040 10         282 $log->contains_ok(
1041             qr/cache get for .* key='$key', cache='$driver', time='[-\d]+ms': HIT/);
1042 10         3440 $log->empty_ok();
1043              
1044 10         3082 local $CHI::Driver::Test_Time = $start_time + 120;
1045 10         296 $cache->get($key);
1046 10         299 $log->contains_ok(
1047             qr/cache get for .* key='$key', cache='$driver', time='[-\d]+ms': $miss_expired/
1048             );
1049 10         3725 $log->empty_ok();
1050              
1051 10         4649 $cache->remove($key);
1052 10         257 $cache->get($key);
1053 10         376 $log->contains_ok(
1054             qr/cache get for .* key='$key', cache='$driver', time='[-\d]+ms': $miss_not_in_cache/
1055             );
1056 10         3639 $log->empty_ok();
1057             }
1058              
1059             # Run tests common to l1_cache and mirror_cache
1060             #
1061             sub _test_common_subcache_features {
1062 20     20   60 my ( $self, $cache, $subcache, $subcache_type ) = @_;
1063 20         88 my ( $key, $value, $key2, $value2 ) = $self->kvpair(2);
1064              
1065 20         62 for ( $cache, $subcache ) { $_->clear() }
  40         715  
1066              
1067             # Test informational methods
1068             #
1069 20         418 ok( !$cache->is_subcache, "is_subcache - false" );
1070 20         6412 ok( $subcache->is_subcache, "is_subcache - true" );
1071 20         6308 ok( $cache->has_subcaches, "has_subcaches - true" );
1072 20         7380 ok( !$subcache->has_subcaches, "has_subcaches - false" );
1073 20         6817 ok( !$cache->can('parent_cache'), "parent_cache - cannot" );
1074 20         5574 is( $subcache->parent_cache, $cache, "parent_cache - defined" );
1075 20         6476 ok( !$cache->can('subcache_type'), "subcache_type - cannot" );
1076 20         5974 is( $subcache->subcache_type, $subcache_type, "subcache_type - defined" );
1077 20         6185 cmp_deeply( $cache->subcaches, [$subcache], "subcaches - defined" );
1078 20         24948 ok( !$subcache->can('subcaches'), "subcaches - cannot" );
1079 20         6053 is( $cache->$subcache_type, $subcache, "$subcache_type - defined" );
1080 20         6455 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 20         5814 my ( $test_remove_method, $confirm_caches_empty,
1086             $confirm_caches_populated );
1087             $test_remove_method = sub {
1088 60     60   188 my ( $desc, $remove_code ) = @_;
1089 60         185 $desc = "testing $desc";
1090              
1091 60         228 $confirm_caches_empty->("$desc: before set");
1092              
1093 60         20830 $cache->set( $key, $value );
1094 60         1471 $cache->set( $key2, $value2 );
1095 60         503 $confirm_caches_populated->("$desc: after set");
1096 60         18664 $remove_code->();
1097              
1098 60         709 $confirm_caches_empty->("$desc: before set_multi");
1099 60         20239 $cache->set_multi( { $key => $value, $key2 => $value2 } );
1100 60         826 $confirm_caches_populated->("$desc: after set_multi");
1101 60         18407 $remove_code->();
1102              
1103 60         579 $confirm_caches_empty->("$desc: before return");
1104 20         170 };
1105              
1106             $confirm_caches_empty = sub {
1107 180     180   352 my ($desc) = @_;
1108 180         4257 ok( !defined( $cache->get($key) ),
1109             "primary cache is not populated with '$key' - $desc" );
1110 180         61514 ok( !defined( $subcache->get($key) ),
1111             "subcache is not populated with '$key' - $desc" );
1112 180         59818 ok( !defined( $cache->get($key2) ),
1113             "primary cache is not populated #2 with '$key2' - $desc" );
1114 180         58986 ok( !defined( $subcache->get($key2) ),
1115             "subcache is not populated #2 with '$key2' - $desc" );
1116 20         122 };
1117              
1118             $confirm_caches_populated = sub {
1119 120     120   263 my ($desc) = @_;
1120 120         2895 is( $cache->get($key), $value,
1121             "primary cache is populated with '$key' - $desc" );
1122 120         46922 is( $subcache->get($key),
1123             $value, "subcache is populated with '$key' - $desc" );
1124 120         41370 is( $cache->get($key2), $value2,
1125             "primary cache is populated with '$key2' - $desc" );
1126 120         37764 is( $subcache->get($key2),
1127             $value2, "subcache is populated with '$key2' - $desc" );
1128 20         122 };
1129              
1130             $test_remove_method->(
1131 40     40   1215 'remove', sub { $cache->remove($key); $cache->remove($key2) }
  40         996  
1132 20         122 );
1133             $test_remove_method->(
1134 40     40   1600 'expire', sub { $cache->expire($key); $cache->expire($key2) }
  40         1116  
1135 20         6807 );
1136 20     40   6418 $test_remove_method->( 'clear', sub { $cache->clear() } );
  40         1221  
1137             }
1138              
1139             sub _verify_cache_is_cleared {
1140 19     19   46 my ( $self, $cache, $desc ) = @_;
1141              
1142 19         186 cmp_deeply( [ $cache->get_keys ], [], "get_keys ($desc)" );
1143 19         67157 is( scalar( $cache->get_keys ), 0, "scalar(get_keys) = 0 ($desc)" );
1144 19         5803 while ( my ( $keyname, $key ) = each( %{ $self->{keys} } ) ) {
  266         77501  
1145 247         4124 ok( !defined $cache->get($key),
1146             "key '$keyname' no longer defined ($desc)" );
1147             }
1148             }
1149              
1150             sub process_keys {
1151 94     94 0 376 my ( $self, $cache, @keys ) = @_;
1152 94         382 $self->process_key( $cache, 'foo' );
1153 94         236 return map { $self->process_key( $cache, $_ ) } @keys;
  529         784  
1154             }
1155              
1156             sub process_key {
1157 623     623 0 700 my ( $self, $cache, $key ) = @_;
1158 623         1249 return $cache->unescape_key(
1159             $cache->escape_key( $cache->transform_key($key) ) );
1160             }
1161              
1162             sub test_clear : Tests {
1163 7     7 0 1195 my $self = shift;
1164 7         35 my $cache = $self->new_cache( namespace => 'name' );
1165 7         32 my $cache2 = $self->new_cache( namespace => 'other' );
1166 7         35 my $cache3 = $self->new_cache( namespace => 'name' );
1167 7         115 $self->num_tests( $self->{key_count} * 2 + 5 );
1168              
1169 7 50       709 if ( $self->supports_clear() ) {
1170 7         49 $self->set_some_keys($cache);
1171 5         22 $self->set_some_keys($cache2);
1172 5         105 $cache->clear();
1173              
1174 5         107 $self->_verify_cache_is_cleared( $cache, 'cache after clear' );
1175 5         28 $self->_verify_cache_is_cleared( $cache3, 'cache3 after clear' );
1176 5         57 cmp_set(
1177             [ $cache2->get_keys ],
1178 5         32 [ $self->process_keys( $cache2, values( %{ $self->{keys} } ) ) ],
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   13370 }
  9         17  
  9         102  
1191              
1192             sub test_logging : Tests {
1193 5     5 0 961 my $self = shift;
1194 5         15 my $cache = $self->{cache};
1195              
1196 5         30 my $log = activate_test_logger();
1197 5         41 my ( $key, $value ) = $self->kvpair();
1198              
1199 5         170 my $driver = $cache->label;
1200              
1201 5         11 my $miss_not_in_cache = 'MISS \(not in cache\)';
1202 5         11 my $miss_expired = 'MISS \(expired\)';
1203              
1204 5         10 my $start_time = time();
1205              
1206 5         67 $cache->get($key);
1207 5         205 $log->contains_ok(
1208             qr/cache get for .* key='$key', cache='$driver', time='[-\d]+ms': $miss_not_in_cache/
1209             );
1210 5         1882 $log->empty_ok();
1211              
1212 5         1593 $cache->set( $key, $value );
1213 5         144 $log->contains_ok(
1214             qr/cache set for .* key='$key', size=\d+, expires='never', cache='$driver', time='[-\d]+ms'/
1215             );
1216 5         1687 $log->empty_ok();
1217 5         1555 $cache->set( $key, $value, 81 );
1218 5         157 $log->contains_ok(
1219             qr/cache set for .* key='$key', size=\d+, expires='1m2[012]s', cache='$driver', time='[-\d]+ms'/
1220             );
1221 5         1688 $log->empty_ok();
1222              
1223 5         1721 $cache->get($key);
1224 5         150 $log->contains_ok(
1225             qr/cache get for .* key='$key', cache='$driver', time='[-\d]+ms': HIT/);
1226 5         1484 $log->empty_ok();
1227              
1228 5         1447 local $CHI::Driver::Test_Time = $start_time + 120;
1229 5         89 $cache->get($key);
1230 5         134 $log->contains_ok(
1231             qr/cache get for .* key='$key', cache='$driver', time='[-\d]+ms': $miss_expired/
1232             );
1233 5         1450 $log->empty_ok();
1234              
1235 5         1600 $cache->remove($key);
1236 5         66 $cache->get($key);
1237 5         150 $log->contains_ok(
1238             qr/cache get for .* key='$key', cache='$driver', time='[-\d]+ms': $miss_not_in_cache/
1239             );
1240 5         1579 $log->empty_ok();
1241 9     9   4012 }
  9         25  
  9         44  
1242              
1243             sub test_stats : Tests {
1244 5     5 0 1308 my $self = shift;
1245              
1246 5 50       38 return 'author testing only - possible differences between JSON versions'
1247             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   6428 }
  9         16  
  9         37  
1354              
1355             sub test_cache_object : Tests {
1356 7     7 0 1380 my $self = shift;
1357 7         20 my $cache = $self->{cache};
1358 7         39 my ( $key, $value ) = $self->kvpair();
1359 7         20 my $start_time = time();
1360 7         139 $cache->set( $key, $value, { expires_at => $start_time + 10 } );
1361 7         71 is_between( $cache->get_object($key)->created_at,
1362             $start_time, $start_time + 2 );
1363 7         2919 is_between( $cache->get_object($key)->get_created_at,
1364             $start_time, $start_time + 2 );
1365 7         2387 is( $cache->get_object($key)->expires_at, $start_time + 10 );
1366 7         2488 is( $cache->get_object($key)->get_expires_at, $start_time + 10 );
1367              
1368 7         2384 local $CHI::Driver::Test_Time = $start_time + 50;
1369 7         100 $cache->set( $key, $value );
1370 7         39 is_between(
1371             $cache->get_object($key)->created_at,
1372             $start_time + 50,
1373             $start_time + 52
1374             );
1375 7         2329 is_between(
1376             $cache->get_object($key)->get_created_at,
1377             $start_time + 50,
1378             $start_time + 52
1379             );
1380 9     9   3310 }
  9         18  
  9         66  
1381              
1382             sub test_size_awareness : Tests {
1383 6     6 0 1619 my $self = shift;
1384 6         45 my ( $key, $value ) = $self->kvpair();
1385              
1386 6         48 ok( !$self->new_cleared_cache()->is_size_aware(),
1387             "not size aware by default" );
1388 6         3731 ok( $self->new_cleared_cache( is_size_aware => 1 )->is_size_aware(),
1389             "is_size_aware turns on size awareness" );
1390 6         4163 ok( $self->new_cleared_cache( max_size => 10 )->is_size_aware(),
1391             "max_size turns on size awareness" );
1392              
1393 6         3254 my $cache = $self->new_cleared_cache( is_size_aware => 1 );
1394 6         95 is( $cache->get_size(), 0, "size is 0 for empty" );
1395 6         3451 $cache->set( $key, $value );
1396 6         38 is_about( $cache->get_size, 20, "size is about 20 with one value" );
1397 6         2976 $cache->set( $key, scalar( $value x 5 ) );
1398 6         34 is_about( $cache->get_size, 45, "size is 45 after overwrite" );
1399 6         2820 $cache->set( $key, scalar( $value x 5 ) );
1400 6         35 is_about( $cache->get_size, 45, "size is still 45 after same overwrite" );
1401 6         2917 $cache->set( $key, scalar( $value x 2 ) );
1402 6         40 is_about( $cache->get_size, 26, "size is 26 after overwrite" );
1403 6         3160 $cache->remove($key);
1404 6         42 is( $cache->get_size, 0, "size is 0 again after removing key" );
1405 6         2852 $cache->set( $key, $value );
1406 6         35 is_about( $cache->get_size, 20, "size is about 20 with one value" );
1407 6         2876 $cache->clear();
1408 6         62 is( $cache->get_size, 0, "size is 0 again after clear" );
1409              
1410 6         2641 my $time = time() + 10;
1411 6         124 $cache->set( $key, $value, { expires_at => $time } );
1412 6         200 is( $cache->get_expires_at($key),
1413             $time, "set options respected by size aware cache" );
1414 9     9   3811 }
  9         17  
  9         60  
1415              
1416             sub test_max_size : Tests {
1417 6     6 0 1395 my $self = shift;
1418              
1419 6         38 is( $self->new_cache( max_size => '30k' )->max_size,
1420             30 * 1024, 'max_size parsing' );
1421              
1422 6         12450 my $cache = $self->new_cleared_cache( max_size => 99 );
1423 6         135 ok( $cache->is_size_aware, "is size aware when max_size specified" );
1424 6         2649 my $value_20 = 'x' x 6;
1425              
1426 6         42 for ( my $i = 0 ; $i < 5 ; $i++ ) {
1427 30         9993 $cache->set( "key$i", $value_20 );
1428             }
1429 6         46 for ( my $i = 0 ; $i < 10 ; $i++ ) {
1430 60         21250 $cache->set( "key" . int( rand(10) ), $value_20 );
1431 60         381 is_between( $cache->get_size, 60, 99,
1432             "after iteration $i, size = " . $cache->get_size );
1433 60         34847 is_between( scalar( $cache->get_keys ),
1434             3, 5, "after iteration $i, keys = " . scalar( $cache->get_keys ) );
1435             }
1436 9     9   3471 }
  9         17  
  9         42  
1437              
1438             sub test_max_size_with_l1_cache : Tests {
1439 7     7 0 1808 my $self = shift;
1440              
1441 7         80 my $cache = $self->new_cleared_cache(
1442             l1_cache => { driver => 'Memory', datastore => {}, max_size => 99 } );
1443 7         54 my $l1_cache = $cache->l1_cache;
1444 7         407 ok( $l1_cache->is_size_aware, "is size aware when max_size specified" );
1445 7         4364 my $value_20 = 'x' x 6;
1446              
1447 7         31 my @keys = map { "key$_" } ( 0 .. 9 );
  70         153  
1448 7         139 my @shuffle_keys = shuffle(@keys);
1449 7         46 for ( my $i = 0 ; $i < 5 ; $i++ ) {
1450 35         1197 $cache->set( "key$i", $value_20 );
1451             }
1452 7         101 for ( my $i = 0 ; $i < 10 ; $i++ ) {
1453 70         27856 my $key = $shuffle_keys[$i];
1454 70         2332 $cache->set( $key, $value_20 );
1455 70         565 is_between( $l1_cache->get_size, 60, 99,
1456             "after iteration $i, size = " . $l1_cache->get_size );
1457 70         35048 is_between( scalar( $l1_cache->get_keys ),
1458             3, 5,
1459             "after iteration $i, keys = " . scalar( $l1_cache->get_keys ) );
1460             }
1461 7         3151 cmp_deeply( [ sort $cache->get_keys ],
1462             \@keys, "primary cache still has all keys" );
1463              
1464             # Now test population by writeback
1465 7         12644 $l1_cache->clear();
1466 7         69 is( $l1_cache->get_size, 0, "l1 size is 0 after clear" );
1467 7         2938 for ( my $i = 0 ; $i < 5 ; $i++ ) {
1468 35         1137 $cache->get("key$i");
1469             }
1470 7         48 for ( my $i = 0 ; $i < 10 ; $i++ ) {
1471 70         23135 my $key = $shuffle_keys[$i];
1472 70         2293 $cache->get($key);
1473 70         250 is_between( $l1_cache->get_size, 60, 99,
1474             "after iteration $i, size = " . $l1_cache->get_size );
1475 70         30908 is_between( scalar( $l1_cache->get_keys ),
1476             3, 5,
1477             "after iteration $i, keys = " . scalar( $l1_cache->get_keys ) );
1478             }
1479 9     9   5161 }
  9         18  
  9         40  
1480              
1481             sub test_custom_discard_policy : Tests {
1482 6     6 0 1008 my $self = shift;
1483 6         13 my $value_20 = 'x' x 6;
1484             my $highest_first = sub {
1485 60     60   84 my $c = shift;
1486 60         278 my @sorted_keys = sort( $c->get_keys );
1487 60         459 return sub { pop(@sorted_keys) };
  270         769  
1488 6         27 };
1489 6         40 my $cache = $self->new_cleared_cache(
1490             is_size_aware => 1,
1491             discard_policy => $highest_first
1492             );
1493 6         35 for ( my $j = 0 ; $j < 10 ; $j += 2 ) {
1494 30         18228 $cache->clear();
1495 30         255 for ( my $i = 0 ; $i < 10 ; $i++ ) {
1496 300         888 my $k = ( $i + $j ) % 10;
1497 300         3727 $cache->set( "key$k", $value_20 );
1498             }
1499 30         213 $cache->discard_to_size(100);
1500 150         441 cmp_set(
1501             [ $cache->get_keys ],
1502 30         119 [ map { "key$_" } ( 0 .. 4 ) ],
1503             "5 lowest"
1504             );
1505 30         86672 $cache->discard_to_size(20);
1506 30         131 cmp_set( [ $cache->get_keys ], ["key0"], "1 lowest" );
1507             }
1508 9     9   3527 }
  9         19  
  9         38  
1509              
1510             sub test_discard_timeout : Tests {
1511 7     7 0 1799 my $self = shift;
1512 7 50       81 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   3756 }
  9         15  
  9         38  
1536              
1537             sub test_size_awareness_with_subcaches : Tests {
1538 6     6 0 1635 my $self = shift;
1539              
1540 6         14 my ( $cache, $l1_cache );
1541             my $set_values = sub {
1542 18     18   51 my $value_20 = 'x' x 6;
1543 18         90 for ( my $i = 0 ; $i < 20 ; $i++ ) {
1544 360         10351 $cache->set( "key$i", $value_20 );
1545             }
1546 18         232 $l1_cache = $cache->l1_cache;
1547 6         43 };
1548             my $is_size_aware = sub {
1549 24     24   64 my $c = shift;
1550 24         648 my $label = $c->label;
1551              
1552 24         644 ok( $c->is_size_aware, "$label is size aware" );
1553 24         13156 my $max_size = $c->max_size;
1554 24         968 ok( $max_size > 0, "$label has max size" );
1555 24         9161 is_between( $c->get_size, $max_size - 40,
1556             $max_size, "$label size = " . $c->get_size );
1557 24         8979 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 6         40 };
1564             my $is_not_size_aware = sub {
1565 6     6   12 my $c = shift;
1566 6         108 my $label = $c->label;
1567              
1568 6         234 ok( !$c->is_size_aware, "$label is not size aware" );
1569 6         4246 is( $c->get_keys, 20, "$label keys = 20" );
1570 6         33 };
1571              
1572 6         79 $cache = $self->new_cleared_cache(
1573             l1_cache => { driver => 'Memory', datastore => {}, max_size => 99 } );
1574 6         27 $set_values->();
1575 6         236 $is_not_size_aware->($cache);
1576 6         2892 $is_size_aware->($l1_cache);
1577              
1578 6         2160 $cache = $self->new_cleared_cache(
1579             l1_cache => { driver => 'Memory', datastore => {}, max_size => 99 },
1580             max_size => 199
1581             );
1582 6         276 $set_values->();
1583 6         751 $is_size_aware->($cache);
1584 6         3136 $is_size_aware->($l1_cache);
1585              
1586 6         2235 $cache = $self->new_cleared_cache(
1587             l1_cache => { driver => 'Memory', datastore => {} },
1588             max_size => 199
1589             );
1590 6         511 $set_values->();
1591 6         357 $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 6         3394 ok( !$l1_cache->is_size_aware, $l1_cache->label . " is not size aware" );
1601 9     9   4800 }
  9         15  
  9         42  
1602              
1603             sub is_about {
1604 30     30 0 61 my ( $value, $expected, $msg ) = @_;
1605              
1606 30         88 my $margin = int( $expected * 0.1 );
1607 30 50       120 if ( abs( $value - $expected ) <= $margin ) {
1608 30         118 pass($msg);
1609             }
1610             else {
1611 0           fail("$msg - got $value, expected $expected");
1612             }
1613             }
1614              
1615             sub test_busy_lock : Tests {
1616 7     7 0 1292 my $self = shift;
1617 7         19 my $cache = $self->{cache};
1618              
1619 7         61 my ( $key, $value ) = $self->kvpair();
1620 7         23 my @bl = ( busy_lock => '30 sec' );
1621 7         13 my $start_time = time();
1622              
1623 7         16 local $CHI::Driver::Test_Time = $start_time;
1624 7         142 $cache->set( $key, $value, 100 );
1625 7         20 local $CHI::Driver::Test_Time = $start_time + 90;
1626 7         115 is( $cache->get( $key, @bl ), $value, "hit before expiration" );
1627 7         3187 is(
1628             $cache->get_expires_at($key),
1629             $start_time + 100,
1630             "expires_at before expiration"
1631             );
1632 7         2304 local $CHI::Driver::Test_Time = $start_time + 110;
1633 7         95 ok( !defined( $cache->get( $key, @bl ) ), "miss after expiration" );
1634 7         2393 is(
1635             $cache->get_expires_at($key),
1636             $start_time + 140,
1637             "expires_at after busy lock"
1638             );
1639 7         2385 is( $cache->get( $key, @bl ), $value, "hit after busy lock" );
1640 9     9   3925 }
  9         16  
  9         35  
1641              
1642             sub test_obj_ref : Tests {
1643 7     7 0 1508 my $self = shift;
1644              
1645             # Make sure obj_ref works in conjunction with subcaches too
1646 7         58 my $cache =
1647             $self->new_cache( l1_cache => { driver => 'Memory', datastore => {} } );
1648 7         24 my $obj;
1649 7         36 my ( $key, $value ) = ( 'medium', [ a => 5, b => 6 ] );
1650              
1651             my $validate_obj = sub {
1652 14     14   70 isa_ok( $obj, 'CHI::CacheObject' );
1653 14         6107 is( $obj->key, $key, "keys match" );
1654 14         5251 cmp_deeply( $obj->value, $value, "values match" );
1655 7         47 };
1656              
1657 7         212 $cache->get( $key, obj_ref => \$obj );
1658 7         48 ok( !defined($obj), "obj not defined on miss" );
1659 7         3747 $cache->set( $key, $value, { obj_ref => \$obj } );
1660 7         73 $validate_obj->();
1661 7         3209 undef $obj;
1662 7         59 ok( !defined($obj), "obj not defined before get" );
1663 7         2448 $cache->get( $key, obj_ref => \$obj );
1664 7         26 $validate_obj->();
1665 9     9   3261 }
  9         23  
  9         38  
1666              
1667             sub test_metacache : Tests {
1668 7     7 0 1633 my $self = shift;
1669 7         20 my $cache = $self->{cache};
1670              
1671 7         61 ok( !defined( $cache->{metacache} ), "metacache is lazy" );
1672 7         3629 $cache->metacache->set( 'foo', 5 );
1673 7         60 ok( defined( $cache->{metacache} ), "metacache autovivified" );
1674 7         2896 is( $cache->metacache->get('foo'), 5 );
1675 9     9   2456 }
  9         17  
  9         39  
1676              
1677             sub test_scalar_return_values : Tests {
1678 6     6 0 1411 my $self = shift;
1679 6         16 my $cache = $self->{cache};
1680              
1681             my $check = sub {
1682 30     30   76 my ($code) = @_;
1683 30         59 my $scalar_result = $code->();
1684 30         83 my @list = $code->();
1685 30         222 cmp_deeply( \@list, [$scalar_result] );
1686 6         36 };
1687              
1688 6     12   30 $check->( sub { $cache->fetch('a') } );
  12         86  
1689 6     12   9226 $check->( sub { $cache->get('a') } );
  12         245  
1690 6     12   7084 $check->( sub { $cache->set( 'a', 5 ) } );
  12         253  
1691 6     12   8039 $check->( sub { $cache->fetch('a') } );
  12         47  
1692 6     12   7039 $check->( sub { $cache->get('a') } );
  12         210  
1693 9     9   3353 }
  9         14  
  9         38  
1694              
1695             sub test_no_leak : Tests {
1696 7     7 0 1518 my ($self) = @_;
1697              
1698 7         19 my $weakref;
1699             {
1700 7         12 my $cache = $self->new_cache();
  7         34  
1701 7         19 $weakref = $cache;
1702 7         39 weaken($weakref);
1703 7   33     160 ok( defined($weakref) && $weakref->isa('CHI::Driver'),
1704             "weakref is defined" );
1705             }
1706 7         3352 ok( !defined($weakref), "weakref is no longer defined - cache was freed" );
1707 9     9   2599 }
  9         17  
  9         35  
1708              
1709             {
1710             package My::CHI;
1711             $My::CHI::VERSION = '0.60';
1712             our @ISA = qw(CHI);
1713             }
1714              
1715             sub test_driver_properties : Tests {
1716 7     7 0 1591 my $self = shift;
1717 7         18 my $cache = $self->{cache};
1718              
1719 7         103 is( $cache->chi_root_class, 'CHI', 'chi_root_class=CHI' );
1720 7         3424 my $cache2 = My::CHI->new( $self->new_cache_options() );
1721 7         57 is( $cache2->chi_root_class, 'My::CHI', 'chi_root_class=My::CHI' );
1722 9     9   2608 }
  9         15  
  9         36  
1723              
1724             sub test_missing_params : Tests {
1725 7     7 0 1448 my $self = shift;
1726 7         21 my $cache = $self->{cache};
1727              
1728             # These methods require a key
1729 7         25 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 84     84   3504 sub { $cache->$method() },
1735 84         57189 qr/must specify key/,
1736             "$method throws error when no key passed"
1737             );
1738             }
1739 9     9   2753 }
  9         16  
  9         51  
1740              
1741             sub test_compute : Tests {
1742 7     7 0 1417 my $self = shift;
1743 7         17 my $cache = $self->{cache};
1744              
1745             # Test current arg order and pre-0.40 arg order
1746 7         25 foreach my $iter ( 0 .. 1 ) {
1747 14         2383 my $count = 5;
1748 14         33 my $expire_time = time + 10;
1749 14     14   115 my @args1 = ( { expires_at => $expire_time }, sub { $count++ } );
  14         41  
1750             my @args2 = (
1751             {
1752 16     16   46 expire_if => sub { 1 }
1753             },
1754 14     14   35 sub { $count++ }
1755 14         90 );
1756 14 100       43 if ($iter) {
1757 7         16 @args1 = reverse(@args1);
1758 7         16 @args2 = reverse(@args2);
1759             }
1760 14         140 $cache->clear;
1761 14         235 is( $cache->get('foo'), undef, "miss" );
1762 14         6177 is( $cache->compute( 'foo', @args1 ), 5, "compute - 5" );
1763 14         4946 is( $cache->get('foo'), 5, "hit - 5" );
1764 14         4897 is( $cache->get_object('foo')->expires_at, $expire_time,
1765             "expire time" );
1766 14         4473 is( $cache->compute( 'foo', @args2 ), 6, "compute - 6" );
1767 14         4723 is( $cache->get('foo'), 6, "hit - 6" );
1768             }
1769              
1770             # Test wantarray
1771 7         2489 $cache->clear();
1772             my $compute_list = sub {
1773 14     14   77 $cache->compute( 'foo', {}, sub { ( int( rand(10000) ) ) x 5 } );
  7         278  
1774 7         44 };
1775 7         26 my @list1 = $compute_list->();
1776 7         38 my @list2 = $compute_list->();
1777 7         52 is( scalar(@list1), 5, "list has 5 items" );
1778 7         2500 cmp_deeply( \@list1, \@list2, "lists are the same" );
1779 9     9   4682 }
  9         14  
  9         39  
1780              
1781             sub test_compress_threshold : Tests {
1782 6     6 0 1057 my $self = shift;
1783 6         15 my $cache = $self->{cache};
1784              
1785 6         16 my $s0 = 'x' x 180;
1786 6         13 my $s1 = 'x' x 200;
1787 6         129 $cache->set( 'key0', $s0 );
1788 6         79 $cache->set( 'key1', $s1 );
1789 6         72 is_between( $cache->get_object('key0')->size, 180, 220 );
1790 6         2461 is_between( $cache->get_object('key1')->size, 200, 240 );
1791              
1792 6         1891 my $cache2 = $self->new_cache( compress_threshold => 190 );
1793 6         90 $cache2->set( 'key0', $s0 );
1794 6         83 $cache2->set( 'key1', $s1 );
1795 6         34 is_between( $cache2->get_object('key0')->size, 180, 220 );
1796 6         3024 ok( $cache2->get_object('key1')->size < 100 );
1797 6         2172 is( $cache2->get('key0'), $s0 );
1798 6         1988 is( $cache2->get('key1'), $s1 );
1799 9     9   3142 }
  9         17  
  9         36  
1800              
1801             sub test_expires_on_backend : Tests {
1802 7     7 0 1388 my $self = shift;
1803              
1804 7 50       56 return "skipping - no support for expires_on_backend"
1805             unless $self->supports_expires_on_backend();
1806 0         0 foreach my $expires_on_backend ( 0, 1 ) {
1807 0         0 my $cache =
1808             $self->new_cache( expires_on_backend => $expires_on_backend );
1809 0         0 $cache->set( 'key0', 5, '2s' );
1810 0         0 $cache->set( 'key1', 6, { expires_at => time + 2 } );
1811 0         0 is( $cache->get('key0'), 5, 'hit key0 before expire' );
1812 0         0 is( $cache->get('key1'), 6, 'hit key1 before expire' );
1813 0         0 sleep(3);
1814 0         0 ok( !defined( $cache->get('key0') ), 'miss key0 after expire' );
1815 0         0 ok( !defined( $cache->get('key1') ), 'miss key1 after expire' );
1816              
1817 0 0       0 if ($expires_on_backend) {
1818 0         0 ok(
1819             !defined( $cache->get_object('key0') ),
1820             'cannot get_object(key0) after expire'
1821             );
1822 0         0 ok(
1823             !defined( $cache->get_object('key1') ),
1824             'cannot get_object(key1) after expire'
1825             );
1826             }
1827             else {
1828 0         0 ok(
1829             $cache->get_object('key0')->is_expired(),
1830             'can get_object(key0) after expire'
1831             );
1832 0         0 ok(
1833             $cache->get_object('key1')->is_expired(),
1834             'can get_object(key1) after expire'
1835             );
1836             }
1837             }
1838 9     9   3543 }
  9         13  
  9         37  
1839              
1840             sub test_append : Tests {
1841 5     5 0 950 my $self = shift;
1842 5         14 my $cache = $self->{cache};
1843 5         27 my ( $key, $value ) =
1844             ( $self->{keys}->{arrayref}, $self->{values}->{medium} );
1845              
1846             # Appending to non-existent key has no effect
1847             #
1848 5         174 $cache->append( $key, $value );
1849 5         155 ok( !$cache->get($key) );
1850              
1851 5         2391 ok( $cache->set( $key, $value ) );
1852 5         2040 $cache->append( $key, $value );
1853 5         98 is( $cache->get($key), $value . $value );
1854 5         1954 $cache->append( $key, $value );
1855 5         54 is( $cache->get($key), $value . $value . $value );
1856 9     9   2754 }
  9         17  
  9         35  
1857              
1858             sub test_add : Tests {
1859 7     7 0 20426 my $self = shift;
1860 7         22 my $cache = $self->{cache};
1861 7         37 my ( $key, $value ) =
1862             ( $self->{keys}->{arrayref}, $self->{values}->{medium} );
1863              
1864 7         17 my $t = time();
1865              
1866 7         119 $cache->add( $key, $value, { expires_at => $t + 100 } );
1867 7         147 is( $cache->get($key), $value, "get" );
1868 7         4364 is( $cache->get_object($key)->expires_at, $t + 100, "expires_at" );
1869              
1870 7         2571 $cache->add( $key, $value . $value, { expires_at => $t + 200 } );
1871 7         118 is( $cache->get($key), $value, "get (after add)" );
1872 7         2885 is( $cache->get_object($key)->expires_at,
1873             $t + 100, "expires_at (after add)" );
1874              
1875 7         2799 $cache->remove($key);
1876 7         62 $cache->add( $key, $value . $value, { expires_at => $t + 200 } );
1877 7         99 is( $cache->get($key), $value . $value, "get (after expire and add)" );
1878 7         2658 is( $cache->get_object($key)->expires_at,
1879             $t + 200, "expires_at (after expire and add)" );
1880 9     9   3114 }
  9         18  
  9         34  
1881              
1882             sub test_replace : Tests {
1883 7     7 0 1913 my $self = shift;
1884 7         23 my $cache = $self->{cache};
1885 7         49 my ( $key, $value ) =
1886             ( $self->{keys}->{arrayref}, $self->{values}->{medium} );
1887              
1888 7         19 my $t = time();
1889              
1890 7         133 $cache->replace( $key, $value, { expires_at => $t + 100 } );
1891 7         44 ok( !$cache->get_object($key), "get" );
1892              
1893 7         3634 $cache->set( $key, $value . $value, { expires_at => $t + 200 } );
1894 7         60 $cache->replace( $key, $value, { expires_at => $t + 100 } );
1895 7         193 is( $cache->get($key), $value, "get (after replace)" );
1896 7         3356 is( $cache->get_object($key)->expires_at,
1897             $t + 100, "expires_at (after replace)" );
1898 9     9   2831 }
  9         18  
  9         34  
1899              
1900             sub test_max_key_length : Tests {
1901 5     5 0 878 my $self = shift;
1902              
1903             # Test max_key_length and also that key does not get transformed twice in mirror_cache
1904             #
1905 5         12 my $mirror_store = {};
1906 5         40 my $cache = $self->new_cleared_cache(
1907             max_key_length => 10,
1908             mirror_cache => { driver => 'Memory', datastore => $mirror_store }
1909             );
1910              
1911 5         15 foreach my $keyname ( 'medium', 'large' ) {
1912 10         1666 my ( $key, $value ) =
1913             ( $self->{keys}->{$keyname}, $self->{values}->{$keyname} );
1914 10         272 $cache->set( $key, $value );
1915 10         254 is( $cache->get($key), $value, $keyname );
1916 10         3636 is( $cache->mirror_cache->get($key), $value, $keyname );
1917 10 100       3521 if ( $keyname eq 'medium' ) {
1918 5         27 is( $cache->get_object($key)->key(), $key, "medium key stored" );
1919             }
1920             else {
1921 5         30 isnt( $cache->get_object($key)->key(), $key, "md5 key stored" );
1922 5         1610 is( length( $cache->get_object($key)->key() ),
1923             32, "md5 key stored" );
1924             }
1925             }
1926 9     9   3176 }
  9         15  
  9         37  
1927              
1928             # Test that cache does not get corrupted with multiple concurrent processes writing
1929             #
1930             sub test_multiple_processes : Tests {
1931 4     4 0 776 my $self = shift;
1932 4 50       27 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   5410 }
  9         16  
  9         37  
2010              
2011             1;