c6a28f83f56c4b66d6513230c165907aa65203fc
2 use lib
(split(/:/, $ENV{GITPERLLIB
}));
8 use Fcntl
qw(:DEFAULT);
16 use lib
$ENV{GITWEBLIBDIR
} || "$ENV{GIT_BUILD_DIR}/gitweb/lib";
19 # Test creating a cache
21 BEGIN { use_ok
('GitwebCache::FileCacheWithLocking'); }
22 diag
("Using lib '$INC[0]'");
23 diag
("Testing '$INC{'GitwebCache/FileCacheWithLocking.pm'}'");
25 my $cache = new_ok
('GitwebCache::FileCacheWithLocking');
26 isa_ok
($cache, 'GitwebCache::SimpleFileCache');
28 # Test that default values are defined
30 ok
(defined $GitwebCache::SimpleFileCache
::DEFAULT_CACHE_ROOT
,
31 '$DEFAULT_CACHE_ROOT defined');
32 ok
(defined $GitwebCache::SimpleFileCache
::DEFAULT_CACHE_DEPTH
,
33 '$DEFAULT_CACHE_DEPTH defined');
35 # Test accessors and default values for cache
38 skip
'default values not defined', 3
39 unless ($GitwebCache::SimpleFileCache
::DEFAULT_CACHE_ROOT
&&
40 $GitwebCache::SimpleFileCache
::DEFAULT_CACHE_DEPTH
);
42 is
($cache->get_namespace(), '', "default namespace is ''");
43 cmp_ok
($cache->get_root(), 'eq', $GitwebCache::SimpleFileCache
::DEFAULT_CACHE_ROOT
,
44 "default cache root is '$GitwebCache::SimpleFileCache::DEFAULT_CACHE_ROOT'");
45 cmp_ok
($cache->get_depth(), '==', $GitwebCache::SimpleFileCache
::DEFAULT_CACHE_DEPTH
,
46 "default cache depth is $GitwebCache::SimpleFileCache::DEFAULT_CACHE_DEPTH");
49 # ----------------------------------------------------------------------
52 # Test the getting, setting, and removal of a cached value
53 # (Cache::Cache interface)
56 my $value = 'Test Value';
58 subtest
'Cache::Cache interface' => sub {
59 foreach my $method (qw(get set remove)) {
60 can_ok
($cache, $method);
63 $cache->set($key, $value);
64 cmp_ok
($cache->get_size($key), '>', 0, 'get_size after set, is greater than 0');
65 is
($cache->get($key), $value, 'get after set, returns cached value');
67 ok
(!defined($cache->get($key)), 'get after remove, is undefined');
69 eval { $cache->remove('Not-Existent Key'); };
70 ok
(!$@
, 'remove on non-existent key doesn\'t die');
76 # Test the getting and setting of a cached value
84 subtest
'CHI interface' => sub {
85 can_ok
($cache, qw(compute));
87 is
($cache->compute($key, \
&get_value
), $value, "compute 1st time (set) returns '$value'");
88 is
($cache->compute($key, \
&get_value
), $value, "compute 2nd time (get) returns '$value'");
89 is
($cache->compute($key, \
&get_value
), $value, "compute 3rd time (get) returns '$value'");
90 cmp_ok
($call_count, '==', 1, 'get_value() is called once from compute');
95 # Test the getting and setting of a cached value
96 # (compute_fh interface)
104 subtest
'compute_fh interface' => sub {
105 can_ok
($cache, qw(compute_fh));
107 $cache->remove($key);
108 is
(cache_compute_fh
($cache, $key, \
&write_value
), $value,
109 "compute_fh 1st time (set) returns '$value'");
110 is
(cache_compute_fh
($cache, $key, \
&write_value
), $value,
111 "compute_fh 2nd time (get) returns '$value'");
112 is
(cache_compute_fh
($cache, $key, \
&write_value
), $value,
113 "compute_fh 3rd time (get) returns '$value'");
114 cmp_ok
($call_count, '==', 1, 'write_value() is called once from compute_fh');
119 # Test cache expiration
121 subtest
'cache expiration' => sub {
122 $cache->set_expires_in(60*60*24); # set expire time to 1 day
123 cmp_ok
($cache->get_expires_in(), '>', 0, '"expires in" is greater than 0');
124 is
($cache->get($key), $value, 'get returns cached value (not expired in 1d)');
126 $cache->set_expires_in(-1); # set expire time to never expire
127 is
($cache->get_expires_in(), -1, '"expires in" is set to never (-1)');
128 is
($cache->get($key), $value, 'get returns cached value (not expired)');
130 $cache->set_expires_in(0);
131 is
($cache->get_expires_in(), 0, '"expires in" is set to now (0)');
132 $cache->set($key, $value);
133 ok
(!defined($cache->get($key)), 'cache is expired');
138 # Test assertions for adaptive cache expiration
141 sub load
{ return $load; }
142 my $expires_min = 10;
143 my $expires_max = 30;
144 $cache->set_expires_in(-1);
145 $cache->set_expires_min($expires_min);
146 $cache->set_expires_max($expires_max);
147 $cache->set_check_load(\
&load
);
148 subtest
'adaptive cache expiration' => sub {
149 cmp_ok
($cache->get_expires_min(), '==', $expires_min,
150 '"expires min" set correctly');
151 cmp_ok
($cache->get_expires_max(), '==', $expires_max,
152 '"expires max" set correctly');
155 cmp_ok
($cache->get_expires_in(), '>=', $expires_min,
156 '"expires in" bound from down for load=0');
157 cmp_ok
($cache->get_expires_in(), '<=', $expires_max,
158 '"expires in" bound from up for load=0');
161 cmp_ok
($cache->get_expires_in(), '>=', $expires_min,
162 '"expires in" bound from down for heavy load');
163 cmp_ok
($cache->get_expires_in(), '<=', $expires_max,
164 '"expires in" bound from up for heavy load');
169 $cache->set_expires_in(-1);
171 # ----------------------------------------------------------------------
173 sub parallel_run
(&); # forward declaration of prototype
175 # Test 'stampeding herd' / 'cache miss stampede' problem
178 my $slow_time = 1; # how many seconds to sleep in mockup of slow generation
184 sub get_value_slow_fh
{
194 die "get_value_die\n";
197 my $lock_file = "$0.$$.lock";
198 sub get_value_die_once
{
199 if (sysopen my $fh, $lock_file, (O_WRONLY
| O_CREAT
| O_EXCL
)) {
201 die "get_value_die_once\n";
212 note
("Following tests contain artifical delay of $slow_time seconds");
213 subtest
'parallel access' => sub {
214 $cache->remove($key);
215 @output = parallel_run
{
217 my $data = cache_get_set
($cache, $key, \
&get_value_slow
);
218 print "$data$sep$call_count";
222 my ($child_out, $child_count) = split(quotemeta $sep, $_);
223 #is($child_out, $value, "get/set (parallel) returns '$value'");
224 $total_count += $child_count;
226 cmp_ok
($total_count, '==', 2, 'parallel get/set: get_value_slow() called twice');
228 $cache->remove($key);
229 @output = parallel_run
{
231 my $data = cache_compute
($cache, $key, \
&get_value_slow
);
232 print "$data$sep$call_count";
236 my ($child_out, $child_count) = split(quotemeta $sep, $_);
237 #is($child_out, $value, "compute (parallel) returns '$value'");
238 $total_count += $child_count;
240 cmp_ok
($total_count, '==', 1, 'parallel compute: get_value_slow() called once');
242 $cache->remove($key);
243 @output = parallel_run
{
245 my $data = cache_compute_fh
($cache, $key, \
&get_value_slow_fh
);
246 print "$data$sep$call_count";
250 my ($child_out, $child_count) = split(quotemeta $sep, $_);
251 #is($child_out, $value, "compute_fh (parallel) returns '$value'");
252 $total_count += $child_count;
254 cmp_ok
($total_count, '==', 1, 'parallel compute_fh: get_value_slow_fh() called once');
257 local $SIG{ALRM
} = sub { die "alarm\n"; };
260 @output = parallel_run
{
262 my $data = eval { cache_compute
($cache, 'No Key', \
&get_value_die
); };
264 print "$data" if defined $data;
266 print "$eval_error" if defined $eval_error;
270 [ ( "${sep}get_value_die\n" ) x
2 ],
271 'parallel compute: get_value_die() died in both'
276 ok
(!$@
, 'parallel compute: no alarm call (neither process hung)');
279 $cache->remove($key);
281 @output = parallel_run
{
282 my $data = eval { cache_compute
($cache, $key, \
&get_value_die_once
); };
284 print "$data" if defined $data;
286 print "$eval_error" if defined $eval_error;
290 [sort ("$value$sep", "${sep}get_value_die_once\n")],
291 'parallel compute: return correct value even if other process died'
301 #######################################################################
302 #######################################################################
303 #######################################################################
305 # use ->get($key) and ->set($key, $data) interface
307 my ($cache, $key, $code) = @_;
309 my $data = $cache->get($key);
310 if (!defined $data) {
312 $cache->set($key, $data);
318 # use ->compute($key, $code) interface
320 my ($cache, $key, $code) = @_;
322 my $data = $cache->compute($key, $code);
325 # use ->compute_fh($key, $code_fh) interface
326 sub cache_compute_fh
{
327 my ($cache, $key, $code_fh) = @_;
329 my ($fh, $filename) = $cache->compute_fh($key, $code_fh);
335 # from http://aaroncrane.co.uk/talks/pipes_and_processes/
337 my ($child_process_code) = @_;
340 die "Failed to fork: $!\n" if !defined $pid;
342 return $pid if $pid != 0;
344 # Now we're in the new child process
345 $child_process_code->();
349 sub parallel_run
(&) {
350 my $child_code = shift;
354 my (%pid_for_child, %fd_for_child);
355 my $sel = IO
::Select
->new();
356 foreach my $child_idx (1..$nchildren) {
357 my $pipe = IO
::Pipe
->new()
358 or die "Failed to create pipe: $!\n";
360 my $pid = fork_child
{
362 or die "$$: Child \$pipe->writer(): $!\n";
363 dup2
(fileno($pipe), fileno(STDOUT
))
364 or die "$$: Child $child_idx failed to reopen stdout to pipe: $!\n";
366 or die "$$: Child $child_idx failed to close pipe: $!\n";
368 # From Test-Simple-0.96/t/subtest/fork.t
370 # Force all T::B output into the pipe (redirected to STDOUT),
371 # for the parent builder as well as the current subtest builder.
373 no warnings
'redefine';
374 *Test
::Builder
::output
= sub { *STDOUT
};
375 *Test
::Builder
::failure_output
= sub { *STDOUT
};
376 *Test
::Builder
::todo_output
= sub { *STDOUT
};
385 $pid_for_child{$pid} = $child_idx;
387 or die "Failed to \$pipe->reader(): $!\n";
388 $fd_for_child{$pipe} = $child_idx;
391 $children{$child_idx} = {
398 while (my @ready = $sel->can_read()) {
399 foreach my $fh (@ready) {
401 my $nread = sysread($fh, $buf, 1024);
403 exists $fd_for_child{$fh}
404 or die "Cannot find child for fd: $fh\n";
407 $children{$fd_for_child{$fh}}{'output'} .= $buf;
414 while (%pid_for_child) {
415 my $pid = waitpid -1, 0;
416 warn "Child $pid_for_child{$pid} ($pid) failed with status: $?\n"
418 delete $pid_for_child{$pid};
421 return map { $children{$_}{'output'} } keys %children;