File Coverage

blib/lib/Test/Memcached.pm
Criterion Covered Total %
statement 44 120 36.6
branch 10 64 15.6
condition 2 28 7.1
subroutine 11 17 64.7
pod 4 4 100.0
total 71 233 30.4


line stmt bran cond sub pod time code
1             package Test::Memcached;
2 2     2   8510 use strict;
  2         6  
  2         95  
3 2     2   12 use warnings;
  2         5  
  2         74  
4              
5 2     2   2548 use Class::Accessor::Lite;
  2         14750  
  2         20  
6 2     2   125 use Cwd;
  2         4  
  2         259  
7 2     2   9306 use File::Temp qw(tempdir);
  2         85791  
  2         293  
8 2     2   3301 use IO::Socket::INET;
  2         57852  
  2         20  
9 2     2   4139 use Time::HiRes ();
  2         5005  
  2         6230  
10              
11             # process does not die when received SIGTERM, on win32.
12             my $TERMSIG = $^O eq 'MSWin32' ? 'KILL' : 'TERM';
13              
14             our $VERSION = '0.00004';
15             our $errstr;
16             our %OPTIONS_MAP = (
17             # perl name => [ $option_name, $boolean, $default ]
18             tcp_port => [ 'p', 0, 11211 ],
19             udp_port => [ 'U', 0, 11211 ],
20             unix_socket => [ 's', 0, undef ],
21             unix_socket_mask => [ 'a', 0, undef ],
22             bind => [ 'l', 0, undef ],
23             # no -d, cause we don't run as daemon
24             max_core_limit => [ 'r', 0, undef ],
25             user => [ 'u', 0, undef ],
26             max_memory => [ 'm', 0, undef ],
27             error_on_exhausted_memory => [ 'M', 1, undef ],
28             max_connections => [ 'c', 0, undef ],
29             lock_down => [ 'k', 1, undef ],
30             verbose => [ 'v', 1, undef ],
31             pidfile => [ 'P', 0, undef ],
32             chunk_size_factor => [ 'f', 0, undef ],
33             minimum_space => [ 'n', 0, undef ],
34             use_large_memory_pages => [ 'L', 1, undef ],
35             delimiter => [ 'D', 0, undef ],
36             threads => [ 't', 0, undef ],
37             requests_per_event => [ 'R', 0, undef ],
38             disable_cas => [ 'C', 1, undef ],
39             backlog_limit => [ 'b', 0, undef ],
40             bind_protocol => [ 'B', 0, undef ],
41             item_size => [ 'I', 0, undef ],
42             );
43              
44             our @SEARCH_PATHS = qw(/usr/local /opt/local /usr);
45              
46             my %DEFAULTS = (
47             options => undef,
48             base_dir => undef,
49             memcached => undef,
50             pid => undef,
51             _owner_pid => undef,
52             memcached_version => undef,
53             memcached_major_version => undef,
54             memcached_minor_version => undef,
55             memcached_micro_version => undef,
56             );
57              
58             Class::Accessor::Lite->mk_accessors(keys %DEFAULTS);
59              
60             sub new {
61 1     1 1 27 my $class = shift;
62 0         0 my $self = bless {
63             %DEFAULTS,
64 1 50       19 @_ == 1 ? %{ $_[0] } : @_,
65             _owner_pid => $$,
66             }, $class;
67              
68 1   50     9 $self->{options} ||= {};
69              
70 1 50       7 if (defined $self->base_dir) {
71 0 0       0 $self->base_dir(cwd . '/' . $self->base_dir)
72             if $self->base_dir !~ m|^/|;
73             } else {
74 1 50       15 $self->base_dir(
75             tempdir(
76             CLEANUP => $ENV{TEST_MEMCACHED_PRESERVE} ? undef : 1,
77             ),
78             );
79             }
80              
81 1 50       13807 if (! $self->memcached) {
82 1 50       12 my $prog = _find_program( 'memcached' )
83             or return;
84 0         0 $self->memcached( $prog );
85             }
86             # run memcached -h, and find out the version string
87 0         0 my $cmd = join(' ', $self->memcached, '-h');
88 0         0 my $output = qx/$cmd/;
89 0 0       0 if ($output =~ /^memcached\s+((\d+)\.(\d+)\.(\d+))/) {
90 0         0 $self->memcached_version($1);
91 0         0 $self->memcached_major_version($2);
92 0         0 $self->memcached_minor_version($3);
93 0         0 $self->memcached_micro_version($4);
94             } else {
95 0         0 warn "Could not parse memcached version";
96             }
97              
98 0         0 return $self;
99             }
100              
101             sub start {
102 0     0 1 0 my ($self, %args) = @_;
103              
104 0 0       0 return if defined $self->pid;
105              
106 0 0 0     0 if ($> == 0 && ! $self->options->{user}) {
107             # if you're root, then you need to do something about it
108 0         0 die "You may not run memcached as root: Please specify the user via `user` option";
109             }
110              
111 0 0 0     0 if (! $self->options->{unix_socket} &&
      0        
112             ! $self->options->{udp_port} &&
113             ! $self->options->{tcp_port}
114             ) {
115 0   0     0 my $port = $args{tcp_port} || 10000;
116 0 0 0     0 $port = 19000 unless $port =~ /^[0-9]+$/ && $port < 19000;
117              
118 0         0 my $sock;
119 0         0 while ( $port++ < 20000 ) {
120 0 0       0 $sock = IO::Socket::INET->new(
121             Listen => 5,
122             LocalAddr => '127.0.0.1',
123             LocalPort => $port,
124             Proto => 'tcp',
125             (($^O eq 'MSWin32') ? () : (ReuseAddr => 1)),
126             );
127 0 0       0 last if $sock;
128             }
129 0 0       0 if (! $sock) {
130 0         0 die "empty port not found";
131             }
132 0         0 $sock->close;
133            
134 0         0 $self->options->{tcp_port} = $port;
135             }
136              
137 0 0 0     0 if ($self->options->{tcp_port} && ! $self->options->{bind}) {
138 0         0 $self->options->{bind} = '127.0.0.1';
139             }
140              
141 0 0       0 open my $logfh, '>>', $self->base_dir . '/memcached.log'
142             or die 'failed to create log file:' . $self->base_dir
143             . "/memcached.log:$!";
144 0         0 my $pid = fork;
145 0 0       0 die "fork(2) failed:$!"
146             unless defined $pid;
147 0 0       0 if ($pid == 0) {
148 0 0       0 open STDOUT, '>&', $logfh
149             or die "dup(2) failed:$!";
150 0 0       0 open STDERR, '>&', $logfh
151             or die "dup(2) failed:$!";
152              
153 0         0 my @cmd = ($self->memcached, $self->_format_options());
154 0         0 print STDERR "Executing @cmd\n";
155 0         0 exec( @cmd );
156 0         0 exit;
157             }
158              
159             # wait until the port opens
160 0 0       0 if (my $port = $self->option('tcp_port')) {
161 0         0 _wait_port( $port );
162             }
163              
164 0         0 $self->pid($pid);
165             }
166              
167             sub _check_port {
168 0     0   0 my ($port) = @_;
169              
170 0         0 my $remote = IO::Socket::INET->new(
171             Proto => 'tcp',
172             PeerAddr => '127.0.0.1',
173             PeerPort => $port,
174             );
175 0 0       0 if ($remote) {
176 0         0 close $remote;
177 0         0 return 1;
178             }
179             else {
180 0         0 return 0;
181             }
182             }
183              
184             sub _wait_port {
185 0     0   0 my $port = shift;
186              
187 0         0 my $retry = 100;
188 0         0 while ( $retry-- ) {
189 0 0       0 return if _check_port($port);
190 0         0 Time::HiRes::sleep(0.1);
191             }
192 0         0 die "cannot open port: $port";
193             }
194              
195             sub option {
196 0     0 1 0 my ($self, $name) = @_;
197 0         0 return $self->options->{$name};
198             }
199              
200             sub _format_options {
201 0     0   0 my $self = shift;
202 0         0 my $options = $self->options;
203 0         0 my @options;
204 0         0 while (my ($name, $value) = each %$options) {
205 0 0       0 if ($name eq 'verbose') {
206 0         0 push @options, '-' . ('v' x $value);
207             } else {
208 0         0 my $data = $OPTIONS_MAP{ $name };
209 0 0       0 push @options,
210             ("-$data->[0]", $data->[1] ? !(!$value) : $value)
211             }
212             }
213 0         0 return @options;
214             }
215              
216             sub stop {
217 0     0 1 0 my ($self, $sig) = @_;
218             return
219 0 0       0 unless defined $self->pid;
220 0   0     0 $sig ||= $TERMSIG;
221 0         0 kill $sig, $self->pid;
222              
223 0         0 local $?;
224 0         0 while (waitpid($self->pid, 0) <= 0) {
225             }
226              
227 0         0 $self->pid(undef);
228             }
229              
230             sub DESTROY {
231 1     1   6 my $self = shift;
232 1 50 33     27 $self->stop
233             if defined $self->pid && $$ == $self->_owner_pid;
234             }
235              
236             sub _find_program {
237 1     1   4 my ($prog, @subdirs) = @_;
238 1         5 undef $errstr;
239 1         5 my $path = _get_path_of($prog);
240 1 50       11 return $path
241             if $path;
242 1         14 for my $memcached (_get_path_of('memcached'),
  3         37  
243             map { "$_/bin/memcached" } @SEARCH_PATHS) {
244 4 50       108 if (-x $memcached) {
245 0         0 for my $subdir (@subdirs) {
246 0         0 $path = $memcached;
247 0 0 0     0 if ($path =~ s|/bin/memcached$|/$subdir/$prog|
248             and -x $path) {
249 0         0 return $path;
250             }
251             }
252             }
253             }
254 1         16 $errstr = "could not find $prog, please set appropriate PATH";
255 1         37 return;
256             }
257              
258             sub _get_path_of {
259 2     2   8 my $prog = shift;
260 2         40968 my $path = `which $prog 2> /dev/null`;
261 2 50       71 chomp $path
262             if $path;
263 2 50       53 $path = ''
264             unless -x $path;
265 2         60 $path;
266             }
267              
268             1;
269              
270             =head1 NAME
271              
272             Test::Memcached - Memcached Runner For Tests
273              
274             =head1 SYNOPSIS
275              
276             use Test::Memcached;
277              
278             my $memd = Test::Memcached->new(
279             options => {
280             user => 'memcached-user',
281             }
282             );
283              
284             $memd->start;
285              
286             my $port = $memd->option( 'tcp_port' );
287              
288             my $client = Cache::Memcached->new({
289             servers => [ "127.0.0.1:$port" ]
290             });
291             $client->get(...);
292              
293             $memd->stop;
294              
295             =head1 DESCRIPTION
296              
297             Test::Memcached automatically sets up a memcached instance, and destroys it
298             when the perl script exists.
299              
300             =head1 HACKING Makefile
301              
302             This is not for the faint of heart, but you can actually hack your CPAN style
303             Makefile to start your memcached server once per "make test". Do something like this in your Makefile.PL:
304              
305             # After you generated your Makefile (that's after your "WriteMakeffile()"
306             # or "WriteAll()" statements):
307              
308             if (-f 'Makefile') {
309             open (my $fh, '<', 'Makefile') or die "Could not open Makefile: $!";
310             my $makefile = do { local $/; <$fh> };
311             close $fh or die $!;
312              
313             $makefile =~ s/"-e" "(test_harness\(\$\(TEST_VERBOSE\), )/"-I\$(INST_LIB)" "-I\$(INST_ARCHLIB)" "-It\/lib" "-MTest::Memcached" "-e" "\\\$\$SIG{INT} = sub { CORE::exit }; my \\\$\$memd; if ( ! \\\$\$ENV{TEST_MEMCACHED_SERVERS}) { \\\$\$memd = Test::Memcached->new(); if (\\\$\$memd) { \\\$\$memd->start(); \\\$\$ENV{TEST_MEMCACHED_SERVERS} = '127.0.0.1:' . \\\$\$memd->option('tcp_port'); } } $1/;
314              
315             open (my $fh, '>', 'Makefile') or die "Could not open Makefile: $!";
316             print $fh $makefile;
317             close $fh or die $!;
318             }
319              
320             Then you can just rely on TEST_MEMCACHED_SERVERS in your .t files.
321             When make test ends, then the memcached instance will automatically stop.
322              
323             It's ugly, but it works
324              
325             =head1 METHODS
326              
327             =head2 new
328              
329             Creates a new instance. you can set the location of memcached by explicitly setting it, or it will attempt to find it.
330              
331             You can speficy a set of options to pass to memcached. Below table shows the values that you can use, and the option name that will be mapped to:
332              
333             tcp_port : 'p'
334             udp_port : 'U'
335             unix_socket : 's'
336             unix_socket_mask : 'a'
337             bind : 'l'
338             max_core_limit : 'r'
339             user : 'u'
340             max_memory : 'm'
341             error_on_exhausted_memory : 'M'
342             max_connections : 'c'
343             lock_down : 'k'
344             verbose : 'v'
345             pidfile : 'P'
346             chunk_size_factor : 'f'
347             minimum_space : 'n'
348             use_large_memory_pages : 'L'
349             delimiter : 'D'
350             threads : 't'
351             requests_per_event : 'R'
352             disable_cas : 'C'
353             backlog_limit : 'b'
354             bind_protocol : 'B'
355             item_size : 'I'
356              
357             =head2 option
358              
359             Gets the current value of the named option
360              
361             my $port = $memd->option('tcp_port');
362              
363             =head2 start
364              
365             If no unix_socket, udp_port is set, automatically looks for an empty port to listen on, and starts memcached.
366              
367             =head2 stop
368              
369             stops memcached. by sending TERM signal
370              
371             =head2 DESTROY
372              
373             When the object goes out of scope, stop gets called.
374              
375             =head1 AUTHORS
376              
377             Kazuho Oku wrote Test::mysqld, which I shamelessly stole from.
378              
379             Tokuhiro Matsuno wrote Test::TCP, which I also shamelessly stole from
380              
381             Daisuke Maki C<< >>
382              
383             =head1 LICENSE
384              
385             This program is free software; you can redistribute it and/or modify it
386             under the same terms as Perl itself.
387              
388             See http://www.perl.com/perl/misc/Artistic.html
389              
390             =cut