1package MemcachedTest; 2use strict; 3use IO::Socket::INET; 4use IO::Socket::UNIX; 5use Exporter 'import'; 6use Carp qw(croak); 7use vars qw(@EXPORT); 8 9# Instead of doing the substitution with Autoconf, we assume that 10# cwd == builddir. 11use Cwd; 12my $builddir = getcwd; 13 14 15@EXPORT = qw(new_memcached sleep mem_get_is mem_gets mem_gets_is mem_stats 16 supports_sasl free_port); 17 18sub sleep { 19 my $n = shift; 20 select undef, undef, undef, $n; 21} 22 23sub mem_stats { 24 my ($sock, $type) = @_; 25 $type = $type ? " $type" : ""; 26 print $sock "stats$type\r\n"; 27 my $stats = {}; 28 while (<$sock>) { 29 last if /^(\.|END)/; 30 /^(STAT|ITEM) (\S+)\s+([^\r\n]+)/; 31 #print " slabs: $_"; 32 $stats->{$2} = $3; 33 } 34 return $stats; 35} 36 37sub mem_get_is { 38 # works on single-line values only. no newlines in value. 39 my ($sock_opts, $key, $val, $msg) = @_; 40 my $opts = ref $sock_opts eq "HASH" ? $sock_opts : {}; 41 my $sock = ref $sock_opts eq "HASH" ? $opts->{sock} : $sock_opts; 42 43 my $expect_flags = $opts->{flags} || 0; 44 my $dval = defined $val ? "'$val'" : "<undef>"; 45 $msg ||= "$key == $dval"; 46 47 print $sock "get $key\r\n"; 48 if (! defined $val) { 49 my $line = scalar <$sock>; 50 if ($line =~ /^VALUE/) { 51 $line .= scalar(<$sock>) . scalar(<$sock>); 52 } 53 Test::More::is($line, "END\r\n", $msg); 54 } else { 55 my $len = length($val); 56 my $body = scalar(<$sock>); 57 my $expected = "VALUE $key $expect_flags $len\r\n$val\r\nEND\r\n"; 58 if (!$body || $body =~ /^END/) { 59 Test::More::is($body, $expected, $msg); 60 return; 61 } 62 $body .= scalar(<$sock>) . scalar(<$sock>); 63 Test::More::is($body, $expected, $msg); 64 } 65} 66 67sub mem_gets { 68 # works on single-line values only. no newlines in value. 69 my ($sock_opts, $key) = @_; 70 my $opts = ref $sock_opts eq "HASH" ? $sock_opts : {}; 71 my $sock = ref $sock_opts eq "HASH" ? $opts->{sock} : $sock_opts; 72 my $val; 73 my $expect_flags = $opts->{flags} || 0; 74 75 print $sock "gets $key\r\n"; 76 my $response = <$sock>; 77 if ($response =~ /^END/) { 78 return "NOT_FOUND"; 79 } 80 else 81 { 82 $response =~ /VALUE (.*) (\d+) (\d+) (\d+)/; 83 my $flags = $2; 84 my $len = $3; 85 my $identifier = $4; 86 read $sock, $val , $len; 87 # get the END 88 $_ = <$sock>; 89 $_ = <$sock>; 90 91 return ($identifier,$val); 92 } 93 94} 95sub mem_gets_is { 96 # works on single-line values only. no newlines in value. 97 my ($sock_opts, $identifier, $key, $val, $msg) = @_; 98 my $opts = ref $sock_opts eq "HASH" ? $sock_opts : {}; 99 my $sock = ref $sock_opts eq "HASH" ? $opts->{sock} : $sock_opts; 100 101 my $expect_flags = $opts->{flags} || 0; 102 my $dval = defined $val ? "'$val'" : "<undef>"; 103 $msg ||= "$key == $dval"; 104 105 print $sock "gets $key\r\n"; 106 if (! defined $val) { 107 my $line = scalar <$sock>; 108 if ($line =~ /^VALUE/) { 109 $line .= scalar(<$sock>) . scalar(<$sock>); 110 } 111 Test::More::is($line, "END\r\n", $msg); 112 } else { 113 my $len = length($val); 114 my $body = scalar(<$sock>); 115 my $expected = "VALUE $key $expect_flags $len $identifier\r\n$val\r\nEND\r\n"; 116 if (!$body || $body =~ /^END/) { 117 Test::More::is($body, $expected, $msg); 118 return; 119 } 120 $body .= scalar(<$sock>) . scalar(<$sock>); 121 Test::More::is($body, $expected, $msg); 122 } 123} 124 125sub free_port { 126 my $type = shift || "tcp"; 127 my $sock; 128 my $port; 129 while (!$sock) { 130 $port = int(rand(20000)) + 30000; 131 $sock = IO::Socket::INET->new(LocalAddr => '127.0.0.1', 132 LocalPort => $port, 133 Proto => $type, 134 ReuseAddr => 1); 135 } 136 return $port; 137} 138 139sub supports_udp { 140 my $output = `$builddir/memcached-debug -h`; 141 return 0 if $output =~ /^memcached 1\.1\./; 142 return 1; 143} 144 145sub supports_sasl { 146 my $output = `$builddir/memcached-debug -h`; 147 return 1 if $output =~ /sasl/i; 148 return 0; 149} 150 151sub new_memcached { 152 my ($args, $passed_port) = @_; 153 my $port = $passed_port || free_port(); 154 my $host = '127.0.0.1'; 155 156 if ($ENV{T_MEMD_USE_DAEMON}) { 157 my ($host, $port) = ($ENV{T_MEMD_USE_DAEMON} =~ m/^([^:]+):(\d+)$/); 158 my $conn = IO::Socket::INET->new(PeerAddr => "$host:$port"); 159 if ($conn) { 160 return Memcached::Handle->new(conn => $conn, 161 host => $host, 162 port => $port); 163 } 164 croak("Failed to connect to specified memcached server.") unless $conn; 165 } 166 167 my $udpport = free_port("udp"); 168 $args .= " -p $port"; 169 if (supports_udp()) { 170 $args .= " -U $udpport"; 171 } 172 if ($< == 0) { 173 $args .= " -u root"; 174 } 175 176 my $childpid = fork(); 177 178 my $exe = "$builddir/memcached-debug"; 179 croak("memcached binary doesn't exist. Haven't run 'make' ?\n") unless -e $exe; 180 croak("memcached binary not executable\n") unless -x _; 181 182 unless ($childpid) { 183 exec "$builddir/timedrun 600 $exe $args"; 184 exit; # never gets here. 185 } 186 187 # unix domain sockets 188 if ($args =~ /-s (\S+)/) { 189 sleep 1; 190 my $filename = $1; 191 my $conn = IO::Socket::UNIX->new(Peer => $filename) || 192 croak("Failed to connect to unix domain socket: $! '$filename'"); 193 194 return Memcached::Handle->new(pid => $childpid, 195 conn => $conn, 196 domainsocket => $filename, 197 host => $host, 198 port => $port); 199 } 200 201 # try to connect / find open port, only if we're not using unix domain 202 # sockets 203 204 for (1..20) { 205 my $conn = IO::Socket::INET->new(PeerAddr => "127.0.0.1:$port"); 206 if ($conn) { 207 return Memcached::Handle->new(pid => $childpid, 208 conn => $conn, 209 udpport => $udpport, 210 host => $host, 211 port => $port); 212 } 213 select undef, undef, undef, 0.10; 214 } 215 croak("Failed to startup/connect to memcached server."); 216} 217 218############################################################################ 219package Memcached::Handle; 220sub new { 221 my ($class, %params) = @_; 222 return bless \%params, $class; 223} 224 225sub DESTROY { 226 my $self = shift; 227 kill 2, $self->{pid}; 228} 229 230sub stop { 231 my $self = shift; 232 kill 15, $self->{pid}; 233} 234 235sub host { $_[0]{host} } 236sub port { $_[0]{port} } 237sub udpport { $_[0]{udpport} } 238 239sub sock { 240 my $self = shift; 241 242 if ($self->{conn} && ($self->{domainsocket} || getpeername($self->{conn}))) { 243 return $self->{conn}; 244 } 245 return $self->new_sock; 246} 247 248sub new_sock { 249 my $self = shift; 250 if ($self->{domainsocket}) { 251 return IO::Socket::UNIX->new(Peer => $self->{domainsocket}); 252 } else { 253 return IO::Socket::INET->new(PeerAddr => "$self->{host}:$self->{port}"); 254 } 255} 256 257sub new_udp_sock { 258 my $self = shift; 259 return IO::Socket::INET->new(PeerAddr => '127.0.0.1', 260 PeerPort => $self->{udpport}, 261 Proto => 'udp', 262 LocalAddr => '127.0.0.1', 263 LocalPort => MemcachedTest::free_port('udp'), 264 ); 265 266} 267 2681; 269