File Coverage

blib/lib/Test/RedisServer.pm
Criterion Covered Total %
statement 97 132 73.4
branch 23 48 47.9
condition 3 16 18.7
subroutine 16 19 84.2
pod 7 7 100.0
total 146 222 65.7


line stmt bran cond sub pod time code
1             package Test::RedisServer;
2 14     14   3076810 use strict;
  14         30  
  14         558  
3 14     14   64 use warnings;
  14         58  
  14         922  
4 14     14   6752 use Mouse;
  14         441494  
  14         68  
5              
6             our $VERSION = '0.24';
7              
8 14     14   6302 use Carp;
  14         54  
  14         1326  
9 14     14   9360 use File::Temp;
  14         228642  
  14         1304  
10 14     14   3396 use POSIX qw(SIGTERM WNOHANG);
  14         47744  
  14         126  
11 14     14   11910 use Time::HiRes qw(sleep);
  14         42  
  14         156  
12 14     14   896 use Errno ();
  14         38  
  14         326  
13 14     14   6212 use Redis;
  14         1056324  
  14         1598  
14              
15             has auto_start => (
16             is => 'rw',
17             default => 1,
18             );
19              
20             has [qw/pid _owner_pid/] => (
21             is => 'rw',
22             );
23              
24             has conf => (
25             is => 'rw',
26             isa => 'HashRef',
27             default => sub { {} },
28             );
29              
30             has timeout => (
31             is => 'rw',
32             default => 3,
33             );
34              
35             has tmpdir => (
36             is => 'rw',
37             lazy_build => 1,
38             );
39              
40             has _redis => (
41             is => 'rw',
42             isa => 'Redis',
43             );
44              
45 14     14   138 no Mouse;
  14         24  
  14         158  
46              
47             sub BUILD {
48 14     14 1 54 my ($self) = @_;
49              
50 14         168 $self->_owner_pid($$);
51              
52 14         156 my $tmpdir = $self->tmpdir;
53 14 50 33     10070 unless (defined $self->conf->{port} or defined $self->conf->{unixsocket}) {
54 14         540 $self->conf->{unixsocket} = "$tmpdir/redis.sock";
55 14         228 $self->conf->{port} = '0';
56             }
57              
58 14 50       130 unless (defined $self->conf->{dir}) {
59 14         88 $self->conf->{dir} = "$tmpdir/";
60             }
61              
62 14 50 33     158 if ($self->conf->{loglevel} and $self->conf->{loglevel} eq 'warning') {
63 0         0 warn "Test::RedisServer does not support \"loglevel warning\", using \"notice\" instead.\n";
64 0         0 $self->conf->{loglevel} = 'notice';
65             }
66              
67 14 50       64 if ($self->auto_start) {
68 14         54 $self->start;
69             }
70             }
71              
72             sub DEMOLISH {
73 14     14 1 10703 my ($self) = @_;
74 14 50 33     1760 $self->stop if defined $self->pid && $$ == $self->_owner_pid;
75             }
76              
77             sub start {
78 14     14 1 38 my ($self) = @_;
79              
80 14 50       74 return if defined $self->pid;
81              
82 14         46 my $tmpdir = $self->tmpdir;
83 14 50       88 open my $logfh, '>>', "$tmpdir/redis-server.log"
84             or croak "failed to create log file: $tmpdir/redis-server.log";
85              
86 14         49148 my $pid = fork;
87 14 50       2274 croak "fork(2) failed:$!" unless defined $pid;
88              
89 14 100       1048 if ($pid == 0) {
90 7 50       1830 open STDOUT, '>&', $logfh or croak "dup(2) failed:$!";
91 7 50       531 open STDERR, '>&', $logfh or croak "dup(2) failed:$!";
92 7         737 $self->exec;
93             }
94 7         493 close $logfh;
95              
96 7         211 my $ready;
97 7         174 my $elapsed = 0;
98 7         701 $self->pid($pid);
99              
100 7         476 while ($elapsed <= $self->timeout) {
101 26 100       943 if (waitpid($pid, WNOHANG) > 0) {
102 7         150 $self->pid(undef);
103 7         37 last;
104             }
105             else {
106 19         298 my $log = q[];
107 19 50       1088 if (open $logfh, '<', "$tmpdir/redis-server.log") {
108 19         2160 $log = do { local $/; <$logfh> };
  19         541  
  19         1100  
109 19         427 close $logfh;
110             }
111              
112             # confirmed this message is included from v1.3.6 (older version in git repo)
113             # to current HEAD (2012-07-30)
114             # The message has changed a bit with Redis 4.x, make regexp a bit more flexible
115 19 50       476 if ( $log =~ /[Rr]eady to accept connections/ ) {
116 0         0 $ready = 1;
117 0         0 last;
118             }
119             }
120              
121 19         3609878 sleep $elapsed += 0.1;
122             }
123              
124 7 50       92 unless ($ready) {
125 7 50       98 if ($self->pid) {
126 0         0 $self->pid(undef);
127 0         0 kill SIGTERM, $pid;
128 0         0 while (waitpid($pid, WNOHANG) >= 0) {
129             }
130             }
131              
132 7         35 croak "*** failed to launch redis-server ***\n" . do {
133 7         97 my $log = q[];
134 7 50       112 if (open $logfh, '<', "$tmpdir/redis-server.log") {
135 7         579 $log = do { local $/; <$logfh> };
  7         64  
  7         214  
136 7         122 close $logfh;
137             }
138 7         3863 $log;
139             };
140             }
141              
142             # This is sometimes needed to send commands to RedisServer during the stop process.
143             # Generally, we would like to generate it lazily and not have it as a property
144             # of the object. However, if you try to create the object at the stop,
145             # the object generation may fail, such as missing the socket file. Therefore,
146             # we will make the object and store it as property here.
147 0         0 $self->_redis( Redis->new($self->connect_info) );
148              
149 0         0 $self->pid($pid);
150             }
151              
152             sub exec {
153 7     7 1 159 my ($self) = @_;
154              
155 7         336 my $tmpdir = $self->tmpdir;
156              
157 7 50       1032 open my $conffh, '>', "$tmpdir/redis.conf" or croak "cannot write conf: $!";
158 7         2622 print $conffh $self->_conf_string;
159 7         1063 close $conffh;
160              
161             exec 'redis-server', "$tmpdir/redis.conf"
162 7 50       157 or do {
163 7 50       3571 if ($! == Errno::ENOENT) {
164 7         336 print STDERR "exec failed: no such file or directory\n";
165             }
166             else {
167 0         0 print STDERR "exec failed: unexpected error: $!\n";
168             }
169 7         1488 exit($?);
170             };
171             }
172              
173             sub stop {
174 0     0 1 0 my ($self, $sig) = @_;
175              
176 0         0 local $?; # waitpid may change this value :/
177 0 0       0 return unless defined $self->pid;
178              
179             # If the tmpdir has disappeared, clear the save config to prevent saving
180             # in the server terminating process. The newer Redis will save on stop
181             # for robustness, but will keep blocking if the directory is missing.
182             #
183             # It is unlikely that tmpdir will disappear first, but if both the RedisServer
184             # object and the tmpdir are defined globally, it may happen because the order
185             # in which they are DESTLOYed is uncertain.
186 0 0       0 if (! -f $self->tmpdir) {
187 0         0 $self->_redis->config_set('appendonly', 'no');
188 0         0 $self->_redis->config_set('save', '');
189             }
190              
191 0   0     0 $sig ||= SIGTERM;
192              
193 0         0 kill $sig, $self->pid;
194 0         0 while (waitpid($self->pid, WNOHANG) >= 0) {
195             }
196              
197 0         0 $self->pid(undef);
198             }
199              
200             sub wait_exit {
201 0     0 1 0 my ($self) = @_;
202              
203 0         0 local $?;
204              
205 0         0 my $kid;
206 0         0 my $pid = $self->pid;
207 0         0 do {
208 0         0 $kid = waitpid($pid, WNOHANG);
209 0         0 sleep 0.1;
210             } while $kid >= 0;
211              
212 0         0 $self->pid(undef);
213             }
214              
215             sub connect_info {
216 0     0 1 0 my ($self) = @_;
217              
218 0   0     0 my $host = $self->conf->{bind} || '0.0.0.0';
219 0         0 my $port = $self->conf->{port};
220 0         0 my $sock = $self->conf->{unixsocket};
221              
222 0 0 0     0 if ($port && $port > 0) {
223 0         0 return (server => $host . ':' . $port);
224             }
225             else {
226 0         0 return (sock => $sock);
227             }
228             }
229              
230             sub _build_tmpdir {
231 14     14   276 File::Temp->newdir( CLEANUP => 1 );
232             }
233              
234             sub _conf_string {
235 7     7   94 my ($self) = @_;
236              
237 7         160 my $conf = q[];
238 7         58 my %conf = %{ $self->conf };
  7         999  
239 7         170 while (my ($k, $v) = each %conf) {
240 21 50       249 next unless defined $v;
241 21         216 $conf .= "$k $v\n";
242             }
243              
244 7         231 $conf;
245             }
246              
247             __PACKAGE__->meta->make_immutable;
248              
249             __END__