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

Revision 768, 11.5 kB (checked in by dormando, 20 months ago)

Most tests pass now. MultiGet is still busted.

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