1#!/usr/bin/env perl 2 3use strict; 4use warnings; 5use Test::More; 6use FindBin qw($Bin); 7use lib "$Bin/lib"; 8use Carp qw(croak); 9use MemcachedTest; 10use IO::Socket qw(AF_INET SOCK_STREAM); 11use IO::Select; 12 13if (!supports_proxy()) { 14 plan skip_all => 'proxy not enabled'; 15 exit 0; 16} 17 18# Don't want to write two distinct set of tests, and extstore is a default. 19if (!supports_extstore()) { 20 plan skip_all => 'extstore not enabled'; 21 exit 0; 22} 23 24my $ext_path = "/tmp/proxyinternal.$$"; 25 26# Set up some server sockets. 27sub mock_server { 28 my $port = shift; 29 my $srv = IO::Socket->new( 30 Domain => AF_INET, 31 Type => SOCK_STREAM, 32 Proto => 'tcp', 33 LocalHost => '127.0.0.1', 34 LocalPort => $port, 35 ReusePort => 1, 36 Listen => 5) || die "IO::Socket: $@"; 37 return $srv; 38} 39 40# Put a version command down the pipe to ensure the socket is clear. 41# client version commands skip the proxy code 42sub check_version { 43 my $ps = shift; 44 print $ps "version\r\n"; 45 like(<$ps>, qr/VERSION /, "version received"); 46} 47 48my $p_srv = new_memcached("-o proxy_config=./t/proxyinternal.lua,ext_item_size=500,ext_item_age=1,ext_path=$ext_path:64m,ext_max_sleep=100000 -t 1"); 49my $ps = $p_srv->sock; 50$ps->autoflush(1); 51 52{ 53 print $ps "ms /b/a 2\r\nhi\r\n"; 54 is(scalar <$ps>, "HD\r\n", "bare ms command works"); 55 56 print $ps "ms /b/a 2 T100\r\nhi\r\n"; 57 is(scalar <$ps>, "HD\r\n", "set ms with a TTL"); 58 print $ps "mg /b/a t\r\n"; 59 isnt(scalar <$ps>, "HD t-1\r\n"); 60} 61 62note "ascii multiget"; 63{ 64 # First test all miss. 65 my @keys = (); 66 for (0 .. 50) { 67 push(@keys, "/b/" . $_); 68 } 69 print $ps "get ", join(' ', @keys), "\r\n"; 70 is(scalar <$ps>, "END\r\n", "all misses from multiget"); 71 # No extra END's after the solitary one. 72 check_version($ps); 73 74 for (@keys) { 75 print $ps "set $_ 0 0 2\r\nhi\r\n"; 76 is(scalar <$ps>, "STORED\r\n", "successful set"); 77 } 78 check_version($ps); 79 print $ps "get ", join(' ', @keys), "\r\n"; 80 for (@keys) { 81 is(scalar <$ps>, "VALUE $_ 0 2\r\n", "resline matches"); 82 is(scalar <$ps>, "hi\r\n", "value matches"); 83 } 84 is(scalar <$ps>, "END\r\n", "final END from multiget"); 85 check_version($ps); 86} 87 88note "ascii basic"; 89{ 90 # Ensure all of that END removal we do in multiget doesn't apply to 91 # non-multiget get mode. 92 print $ps "get /b/miss\r\n"; 93 is(scalar <$ps>, "END\r\n", "basic miss"); 94 check_version($ps); 95} 96 97#diag "object too large" 98{ 99 my $data = 'x' x 2000000; 100 print $ps "set /b/toolarge 0 0 2000000\r\n$data\r\n"; 101 is(scalar <$ps>, "SERVER_ERROR object too large for cache\r\n", "set too large"); 102 103 print $ps "ms /b/toolarge 2000000 T30\r\n$data\r\n"; 104 is(scalar <$ps>, "SERVER_ERROR object too large for cache\r\n", "ms too large"); 105} 106 107#diag "basic tests" 108{ 109 print $ps "set /b/foo 0 0 2\r\nhi\r\n"; 110 is(scalar <$ps>, "STORED\r\n", "int set"); 111 print $ps "get /b/foo\r\n"; 112 is(scalar <$ps>, "VALUE /b/foo 0 2\r\n", "get response"); 113 is(scalar <$ps>, "hi\r\n", "get value"); 114 is(scalar <$ps>, "END\r\n", "get END"); 115 check_version($ps); 116} 117 118subtest 'basic large item' => sub { 119 my $data = 'x' x 500000; 120 print $ps "set /b/beeeg 0 0 500000\r\n$data\r\n"; 121 is(scalar <$ps>, "STORED\r\n", "big item stored"); 122 123 print $ps "get /b/beeeg\r\n"; 124 is(scalar <$ps>, "VALUE /b/beeeg 0 500000\r\n", "got large response"); 125 is(scalar <$ps>, "$data\r\n", "got data portion back"); 126 is(scalar <$ps>, "END\r\n", "saw END"); 127 128 print $ps "delete /b/beeeg\r\n"; 129 is(scalar <$ps>, "DELETED\r\n"); 130 check_version($ps); 131}; 132 133subtest 'basic chunked item' => sub { 134 my $data = 'x' x 900000; 135 print $ps "set /b/chunked 0 0 900000\r\n$data\r\n"; 136 is(scalar <$ps>, "STORED\r\n", "big item stored"); 137 138 print $ps "get /b/chunked\r\n"; 139 is(scalar <$ps>, "VALUE /b/chunked 0 900000\r\n", "got large response"); 140 is(scalar <$ps>, "$data\r\n", "got data portion back"); 141 is(scalar <$ps>, "END\r\n", "saw END"); 142 143 print $ps "delete /b/chunked\r\n"; 144 is(scalar <$ps>, "DELETED\r\n"); 145 check_version($ps); 146}; 147 148subtest 'fetch from extstore' => sub { 149 my $data = 'x' x 1000; 150 print $ps "set /b/ext 0 0 1000\r\n$data\r\n"; 151 is(scalar <$ps>, "STORED\r\n", "int set for extstore"); 152 wait_ext_flush($ps); 153 154 print $ps "get /b/ext\r\n"; 155 is(scalar <$ps>, "VALUE /b/ext 0 1000\r\n", "get response from extstore"); 156 is(scalar <$ps>, "$data\r\n", "got data from extstore"); 157 is(scalar <$ps>, "END\r\n", "get END"); 158}; 159 160#diag "flood memory" 161{ 162 # ensure we don't have a basic reference counter leak 163 my $data = 'x' x 500000; 164 for (1 .. 200) { 165 print $ps "set /b/$_ 0 0 500000\r\n$data\r\n"; 166 is(scalar <$ps>, "STORED\r\n", "flood set"); 167 } 168 for (1 .. 200) { 169 print $ps "ms /b/$_ 500000 T30\r\n$data\r\n"; 170 is(scalar <$ps>, "HD\r\n", "flood ms"); 171 } 172} 173 174subtest 'watch deletions' => sub { 175 my $watcher = $p_srv->new_sock; 176 print $watcher "watch deletions\n"; 177 is(<$watcher>, "OK\r\n", "deletions watcher enabled"); 178 179 # verify watcher for delete 180 print $ps "set vfoo 0 0 4\r\nvbar\r\n"; 181 is(<$ps>, "STORED\r\n", "stored the key"); 182 183 print $ps "delete vfoo\r\n"; 184 is(<$ps>, "DELETED\r\n", "key was deleted"); 185 186 like(<$watcher>, qr/ts=\d+\.\d+\ gid=\d+ type=deleted key=vfoo cmd=delete .+ size=4/, 187 "delete command logged with correct size"); 188 189 # verify watcher for md 190 print $ps "set vfoo 0 0 4\r\nvbar\r\n"; 191 is(<$ps>, "STORED\r\n", "stored the key"); 192 193 print $ps "md vfoo\r\n"; 194 is(<$ps>, "HD\r\n", "key was deleted"); 195 196 like(<$watcher>, qr/ts=\d+\.\d+\ gid=\d+ type=deleted key=vfoo cmd=md .+ size=4/, 197 "meta-delete command logged with correct size"); 198}; 199 200done_testing(); 201 202END { 203 unlink $ext_path if $ext_path; 204} 205