root/branches/binary/server/t/binary.t @ 682

Revision 682, 11.3 kB (checked in by hachi, 2 years ago)

Add decrement test, which is currently failing. This is due to a problem in the test, not a problem in the memcached.

  • Property svn:executable set to *
Line 
1#!/usr/bin/perl
2
3use strict;
4use warnings;
5
6use Test::More 'no_plan';
7
8# Based almost 100% off testClient.py which is Copyright (c) 2007  Dustin Sallings <dustin@spy.net>
9
10# Command constants
11use constant CMD_GET     => 0;
12use constant CMD_SET     => 1;
13use constant CMD_ADD     => 2;
14use constant CMD_REPLACE => 3;
15use constant CMD_DELETE  => 4;
16use constant CMD_INCR    => 5;
17use constant CMD_QUIT    => 6;
18use constant CMD_FLUSH   => 7;
19use constant CMD_GETQ    => 8;
20use constant CMD_NOOP    => 9;
21use constant CMD_VERSION => 10;
22
23use constant CMD_GETS    => 50;
24use constant CMD_CAS     => 51;
25
26# Flags, expiration
27use constant SET_PKT_FMT => "NN";
28
29# Flags, expiration, id
30use constant CAS_PKT_FMT => "NNNN";
31
32# How long until the deletion takes effect.
33use constant DEL_PKT_FMT => "N";
34
35# amount, initial value, expiration
36use constant INCRDECR_PKT_FMT => "NNNNN";
37
38use constant REQ_MAGIC_BYTE => 0x0f;
39use constant RES_MAGIC_BYTE => 0xf0;
40
41use constant PKT_FMT => "CCCxNN";
42
43#min recv packet size
44use constant MIN_RECV_PACKET => length(pack(PKT_FMT));
45
46my $mc = MC::Client->new;
47my $check = sub {
48        my ($key, $orig_flags, $orig_value) = @_;
49        my ($flags, $value) = $mc->get($key);
50        is($flags, $orig_flags, "Flags is set properly");
51        is($value, $orig_value, "Value is set properly");
52};
53
54my $set = sub {
55        my ($key, $exp, $orig_flags, $orig_value) = @_;
56        $mc->set($key, $exp, $orig_flags, $orig_value);
57        $check->($key, $orig_flags, $orig_value);
58};
59
60my $empty = sub {
61        my $key = shift;
62        my $rv =()= eval { $mc->get($key) };
63        is($rv, 0, "Didn't get a result from get");
64        ok($@->not_found, "We got a not found error when we expected one");
65};
66
67my $delete = sub {
68        my ($key, $when) = @_;
69        $mc->delete($key, $when);
70        $empty->($key);
71};
72
73$mc->flush;
74
75{
76        diag "Test Version";
77        my $v = $mc->version;
78        ok(defined $v && length($v), "Proper version");
79}
80
81diag "Noop";
82$mc->noop;
83       
84diag "Simple set/get";
85$set->('x', 5, 19, "somevalue");
86
87diag "Delete";
88$delete->('x');
89
90diag "Flush";
91$set->('x', 5, 19, "somevaluex");
92$set->('y', 5, 17, "somevaluey");
93$mc->flush;
94$empty->('x');
95$empty->('y');
96
97{
98        diag "Reservation delete";
99        $set->('y', 5, 19, "someothervalue");
100        $delete->('y', 1);
101        my $rv =()= eval { $mc->add('y', 5, 19, "yetanothervalue") };
102        is($rv, 0, "Add didn't return anything");
103        ok($@->exists, "We got an exists error like we expected");
104        sleep 2;
105        $mc->add('y', 5, 19, "wibblevalue");
106}
107
108{
109        diag "Add";
110        $empty->('i');
111        $mc->add('i', 5, 19, "ex");
112        $check->('i', 19, "ex");
113
114        my $rv =()= eval { $mc->add('i', 5, 19, "ex2") };
115        is($rv, 0, "Add didn't return anything");
116        ok($@->exists, "Expected exists error received");
117       
118        $check->('i', 19, "ex");
119}
120
121{
122        diag "Replace";
123        $empty->('j');
124
125        my $rv =()= eval { $mc->replace('j', 5, 19, "ex") };
126        is($rv, 0, "Replace didn't return anything");
127        ok($@->not_found, "Expected not_found error received");
128
129        $empty->('j');
130       
131        $mc->add('j', 5, 14, "ex2");
132        $check->('j', 14, "ex2");
133       
134        $mc->replace('j', 5, 24, "ex3");
135        $check->('j', 24, "ex3");
136}
137
138{
139        diag "MultiGet";
140        $mc->add('xx', 5, 1, "ex");
141        $mc->add('wye', 5, 2, "why");
142        my $rv = $mc->getMulti(qw(xx wye zed));
143
144        is_deeply([1, 'ex'], $rv->{xx}, "X is correct");
145        is_deeply([2, 'why'], $rv->{wye}, "Y is correct");
146        is(keys(%$rv), 2, "Got only two answers like we expect");
147}
148
149diag "Test increment";
150$mc->flush;
151is($mc->incr("x"), 0, "First incr call is zero");
152is($mc->incr("x"), 1, "Second incr call is one");
153is($mc->incr("x", 211), 212, "Adding 211 gives you 212");
154is($mc->incr("x", 2**33), 8589934804, "Blast the 32bit border");
155
156diag "Test decrement";
157$mc->flush;
158is($mc->incr("x", undef, 5), 5, "Initial value");
159is($mc->decr("x"), 4, "Decrease by one");
160is($mc->decr("x", 211), 0, "Floor is zero");
161
162<<EOT;
163    def testIncrDoesntExistNoCreate(self):
164        """Testing incr when a value doesn't exist (and not creating)."""
165        try:
166            self.mc.incr("x", exp=-1)
167            self.fail("Expected failure to increment non-existent key")
168        except MemcachedError, e:
169            self.assertEquals(memcacheConstants.ERR_NOT_FOUND, e.status)
170        self.assertNotExists("x")
171
172    def testIncrDoesntExistCreate(self):
173        """Testing incr when a value doesn't exist (and we make a new one)"""
174        self.assertNotExists("x")
175        self.assertEquals(19, self.mc.incr("x", init=19))
176
177    def testDecrDoesntExistNoCreate(self):
178        """Testing decr when a value doesn't exist (and not creating)."""
179        try:
180            self.mc.decr("x", exp=-1)
181            self.fail("Expected failiure to decrement non-existent key.")
182        except MemcachedError, e:
183            self.assertEquals(memcacheConstants.ERR_NOT_FOUND, e.status)
184        self.assertNotExists("x")
185
186    def testDecrDoesntExistCreate(self):
187        """Testing decr when a value doesn't exist (and we make a new one)"""
188        self.assertNotExists("x")
189        self.assertEquals(19, self.mc.decr("x", init=19))
190EOT
191
192{
193        diag "CAS";
194        $mc->flush;
195       
196        {
197                my $rv =()= eval { $mc->cas("x", 5, 19, 0x7FFFFFFFFF, "bad value") };
198                is($rv, 0, "Empty return on expected failure");
199                ok($@->not_found, "Error was 'not found' as expected");
200        }
201
202        $mc->add("x", 5, 19, "original value");
203
204        my ($flags, $i, $val) = $mc->gets("x");
205        is($val, "original value", "->gets returned proper value");
206       
207        {
208                my $rv =()= eval { $mc->cas("x", 5, 19, $i+1, "broken value") };
209                is($rv, 0, "Empty return on expected failure (1)");
210                ok($@->exists, "Expected error state of 'exists' (1)");
211        }
212
213        $mc->cas("x", 5, 19, $i, "new value");
214
215        my ($newflags, $newi, $newval) = $mc->gets("x");
216        is($newval, "new value", "CAS properly overwrote value");
217
218        {
219                my $rv =()= eval { $mc->cas("x", 5, 19, $i, "replay value") };
220                is($rv, 0, "Empty return on expected failure (2)");
221                ok($@->exists, "Expected error state of 'exists' (2)");
222        }
223
224        (undef, undef, my $newval2) = $mc->gets("x");
225        is($newval2, "new value", "CAS replay didn't overwrite value");
226}
227
228$mc->flush;
229$mc->close;
230
231
232package MC::Client;
233
234use strict;
235use warnings;
236
237use fields qw(socket);
238
239use IO::Socket::INET;
240
241sub new {
242        my $self = shift;
243
244        my $host = shift || '127.0.0.1';
245        my $port = shift || 11212;
246
247        my $sock = IO::Socket::INET->new(PeerHost => $host, PeerPort => $port);
248
249        unless ($sock) {
250                warn "Unable to contact memcached.";
251                return;
252        }
253
254        $self = fields::new($self);
255
256        $self->{socket} = $sock;
257
258        return $self;
259}
260
261sub close {
262        my $self = shift;
263        return $self->{socket}->close(@_);
264}
265
266sub _sendCmd {
267        my $self = shift;
268        die "Not enough args to _sendCmd" unless @_ >= 4;
269        my ($cmd, $key, $val, $opaque, $extraHeader) = @_;
270
271        $extraHeader = '' unless defined $extraHeader;
272
273        my $keylen = length($key);
274        my $vallen = length($val);
275        my $extralen = length($extraHeader);
276
277        my $msg = pack(::PKT_FMT, ::REQ_MAGIC_BYTE, $cmd, $keylen, $opaque, $keylen + $vallen + $extralen);
278        return $self->{socket}->send($msg . $extraHeader . $key . $val);
279}
280
281sub _handleSingleResponse {
282        my $self = shift;
283        my $myopaque = shift;
284
285        $self->{socket}->recv(my $response, ::MIN_RECV_PACKET);
286
287        Test::More::is(length($response), ::MIN_RECV_PACKET, "Expected read length");
288
289        my ($magic, $cmd, $errcode, $opaque, $remaining) = unpack(::PKT_FMT, $response);
290
291        Test::More::is($magic, ::RES_MAGIC_BYTE, "Got proper magic");
292
293        return ($opaque, "")
294                if $remaining == 0;
295
296        $self->{socket}->recv(my $rv, $remaining);
297
298        if (defined $myopaque) {
299                Test::More::is($opaque, $myopaque, "Expected opaque");
300        } else {
301                Test::More::pass("Implicit pass since myopaque is undefined");
302        }
303
304        if ($errcode) {
305                die MC::Error->new($errcode, $rv);
306        }
307
308        return ($opaque, $rv);
309}
310
311sub _doCmd {
312        my $self = shift;
313        die unless @_ >= 3;
314        my ($cmd, $key, $val, $extraHeader) = @_;
315
316        $extraHeader = '' unless defined $extraHeader;
317
318        my $opaque = int(rand(2**32));
319
320        $self->_sendCmd($cmd, $key, $val, $opaque, $extraHeader);
321        (undef, my $rv) = $self->_handleSingleResponse($opaque);
322        return $rv;
323}
324
325sub __parseGet {
326        my $self = shift;
327        my $rv = shift; # currently contains 4 bytes of 'flag' followed by value
328        my $flag = substr $rv, 0, 4, ''; # Now $flag contains flags, $rv contains value
329        return unpack("N", $flag), $rv;
330}
331
332sub get {
333        my $self = shift;
334        my $key = shift;
335        my $parts = $self->_doCmd(::CMD_GET, $key, '');
336        return $self->__parseGet($parts);
337}
338
339sub _mutate {
340        my $self = shift;
341        my ($cmd, $key, $exp, $flags, $val) = @_;
342
343        return $self->_doCmd($cmd, $key, $val, pack(::SET_PKT_FMT, $flags, $exp));
344}
345
346sub set {
347        my $self = shift;
348        my ($key, $exp, $flags, $val) = @_;
349
350        return $self->_mutate(::CMD_SET, $key, $exp, $flags, $val);
351}
352
353sub __incrdecr {
354        my $self = shift;
355        my ($cmd, $key, $amt, $init, $exp) = @_;
356
357        my $amt_hi = int($amt / 2 ** 32);
358        my $amt_lo = int($amt % 2 ** 32);
359
360        my $init_hi = int($init / 2 ** 32);
361        my $init_lo = int($init % 2 ** 32);
362
363        return $self->_doCmd($cmd, $key, '', pack(::INCRDECR_PKT_FMT, $amt_hi, $amt_lo, $init_hi, $init_lo, $exp));
364}
365
366sub incr {
367        my $self = shift;
368        my ($key, $amt, $init, $exp) = @_;
369        $amt = 1 unless defined $amt;
370        $init = 0 unless defined $init;
371        $exp = 0 unless defined $exp;
372
373        return $self->__incrdecr(::CMD_INCR, $key, $amt, $init, $exp);
374}
375
376sub decr {
377        my $self = shift;
378        my ($key, $amt, $init, $exp) = @_;
379        $amt = 1 unless defined $amt;
380        $init = 0 unless defined $init;
381        $exp = 0 unless defined $exp;
382
383        return $self->__incrdecr(::CMD_INCR, $key, 0 - $amt, $init, $exp);
384}
385
386sub add {
387        my $self = shift;
388        my ($key, $exp, $flags, $val) = @_;
389        return $self->_mutate(::CMD_ADD, $key, $exp, $flags, $val);
390}
391sub replace {
392        my $self = shift;
393        my ($key, $exp, $flags, $val) = @_;
394        return $self->_mutate(::CMD_REPLACE, $key, $exp, $flags, $val);
395}
396
397sub getMulti {
398        my $self = shift;
399        my @keys = @_;
400
401        for (my $i = 0; $i < @keys; $i++) {
402                $self->_sendCmd(::CMD_GETQ, $keys[$i], '', $i);
403        }
404
405        my $terminal = @keys + 10;
406        $self->_sendCmd(::CMD_NOOP, '', '', $terminal);
407
408        my %return;
409
410        while (1) {
411                my ($opaque, $data) = $self->_handleSingleResponse;
412                last if $opaque == $terminal;
413
414                $return{$keys[$opaque]} = [$self->__parseGet($data)];
415        }
416        return %return if wantarray;
417        return \%return;
418}
419
420sub gets {
421        my $self = shift;
422        my $key = shift;
423
424        my $data = $self->_doCmd(::CMD_GETS, $key, '');
425        my $header = substr $data, 0, 12, '';
426        my ($flags, $ident_hi, $ident_lo) = unpack "NNN", $header;
427        my $ident = ($ident_hi * 2 ** 32) + $ident_lo;
428
429        return $flags, $ident, $data;
430}
431
432sub cas {
433        my $self = shift;
434        my ($key, $exp, $flags, $oldVal, $val) = @_;
435
436        my $oldVal_hi = int($oldVal / 2 ** 32);
437        my $oldVal_lo = int($oldVal % 2 ** 32);
438
439        return $self->_doCmd(::CMD_CAS, $key, $val, pack(::CAS_PKT_FMT, $flags, $exp, $oldVal_hi, $oldVal_lo));
440}
441
442sub noop {
443        my $self = shift;
444        return $self->_doCmd(::CMD_NOOP, '', '');
445}
446
447sub delete {
448        my $self = shift;
449        my ($key, $when) = @_;
450        $when = 0 unless defined $when;
451
452        return $self->_doCmd(::CMD_DELETE, $key, '', pack(::DEL_PKT_FMT, $when));
453}
454
455sub version {
456        my $self = shift;
457        return $self->_doCmd(::CMD_VERSION, '', '');
458}
459
460sub flush {
461        my $self = shift;
462        return $self->_doCmd(::CMD_FLUSH, '', '');
463}
464
465package MC::Error;
466
467use strict;
468use warnings;
469
470use constant ERR_UNKNOWN_CMD => 0x81;
471use constant ERR_NOT_FOUND   => 0x1;
472use constant ERR_EXISTS      => 0x2;
473
474use overload '""' => sub {
475        my $self = shift;
476
477        return "Memcache Error ($self->[0]): $self->[1]";
478};
479
480sub new {
481        my $class = shift;
482        my $error = [@_];
483       
484        my $self = bless $error, (ref $class || $class);
485
486        return $self;
487}
488
489sub not_found {
490        my $self = shift;
491
492        return $self->[0] == ERR_NOT_FOUND;
493}
494
495sub exists {
496        my $self = shift;
497
498        return $self->[0] == ERR_EXISTS;
499}
500
501# vim: filetype=perl
Note: See TracBrowser for help on using the browser.