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