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

Revision 769, 10.1 kB (checked in by dormando, 20 months ago)

All existing tests pass.

  • 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    # CAS is returned with all gets.
144    $rv->{xx}->[2]  = 0;
145    $rv->{wye}->[2] = 0;
146        is_deeply($rv->{xx}, [1, 'ex', 0], "X is correct");
147        is_deeply($rv->{wye}, [2, 'why', 0], "Y is correct");
148        is(keys(%$rv), 2, "Got only two answers like we expect");
149}
150
151diag "Test increment";
152$mc->flush;
153is($mc->incr("x"), 0, "First incr call is zero");
154is($mc->incr("x"), 1, "Second incr call is one");
155is($mc->incr("x", 211), 212, "Adding 211 gives you 212");
156is($mc->incr("x", 2**33), 8589934804, "Blast the 32bit border");
157
158diag "Test decrement";
159$mc->flush;
160is($mc->incr("x", undef, 5), 5, "Initial value");
161is($mc->decr("x"), 4, "Decrease by one");
162is($mc->decr("x", 211), 0, "Floor is zero");
163
164{
165        diag "CAS";
166        $mc->flush;
167       
168        {
169                my $rv =()= eval { $mc->set("x", 5, 19, "bad value", 0x7FFFFFFFFF) };
170                is($rv, 0, "Empty return on expected failure");
171                ok($@->not_found, "Error was 'not found' as expected");
172        }
173
174        $mc->add("x", 5, 19, "original value");
175
176        my ($flags, $val, $i) = $mc->get("x");
177        is($val, "original value", "->gets returned proper value");
178
179    {
180                my $rv =()= eval { $mc->set("x", 5, 19, "broken value", $i+1) };
181                is($rv, 0, "Empty return on expected failure (1)");
182                ok($@->exists, "Expected error state of 'exists' (1)");
183        }
184
185        $mc->set("x", 5, 19, "new value", $i);
186
187        my ($newflags, $newval, $newi) = $mc->get("x");
188        is($newval, "new value", "CAS properly overwrote value");
189
190        {
191                my $rv =()= eval { $mc->set("x", 5, 19, "replay value", $i) };
192                is($rv, 0, "Empty return on expected failure (2)");
193                ok($@->exists, "Expected error state of 'exists' (2)");
194        }
195
196        (undef, my $newval2) = $mc->get("x");
197        is($newval2, "new value", "CAS replay didn't overwrite value");
198}
199
200$mc->flush;
201$mc->close;
202
203
204package MC::Client;
205
206use strict;
207use warnings;
208
209use fields qw(socket);
210
211use IO::Socket::INET;
212
213sub new {
214        my $self = shift;
215
216        my $host = shift || '127.0.0.1';
217        my $port = shift || 11211;
218
219        my $sock = IO::Socket::INET->new(PeerHost => $host, PeerPort => $port);
220
221        unless ($sock) {
222                warn "Unable to contact memcached.";
223                return;
224        }
225
226        $self = fields::new($self);
227
228        $self->{socket} = $sock;
229
230        return $self;
231}
232
233sub close {
234        my $self = shift;
235        return $self->{socket}->close(@_);
236}
237
238sub _sendCmd {
239        my $self = shift;
240        die "Not enough args to _sendCmd" unless @_ >= 4;
241        my ($cmd, $key, $val, $opaque, $extraHeader) = @_;
242
243        $extraHeader = '' unless defined $extraHeader;
244
245        my $keylen = length($key);
246        my $vallen = length($val);
247        my $extralen = length($extraHeader);
248
249        my $msg = pack(::PKT_FMT, ::REQ_MAGIC_BYTE, $cmd, $keylen, $extralen,
250                    $keylen + $vallen + $extralen, $opaque);
251        return $self->{socket}->send($msg . $extraHeader . $key . $val);
252}
253
254sub _handleSingleResponse {
255        my $self = shift;
256        my $myopaque = shift;
257
258        $self->{socket}->recv(my $response, ::MIN_RECV_PACKET);
259
260        Test::More::is(length($response), ::MIN_RECV_PACKET, "Expected read length");
261
262        my ($magic, $cmd, $errcode, $extralen, $remaining,
263        $opaque) = unpack(::PKT_FMT, $response);
264
265        Test::More::is($magic, ::RES_MAGIC_BYTE, "Got proper magic");
266
267        return ($opaque, "")
268                if $remaining == 0;
269
270        $self->{socket}->recv(my $rv, $remaining);
271
272        if (defined $myopaque) {
273                Test::More::is($opaque, $myopaque, "Expected opaque");
274        } else {
275                Test::More::pass("Implicit pass since myopaque is undefined");
276        }
277
278        if ($errcode) {
279                die MC::Error->new($errcode, $rv);
280        }
281
282        return ($opaque, $rv);
283}
284
285sub _doCmd {
286        my $self = shift;
287        die unless @_ >= 3;
288        my ($cmd, $key, $val, $extraHeader) = @_;
289
290        $extraHeader = '' unless defined $extraHeader;
291
292        my $opaque = int(rand(2**32));
293
294        $self->_sendCmd($cmd, $key, $val, $opaque, $extraHeader);
295        (undef, my $rv) = $self->_handleSingleResponse($opaque);
296        return $rv;
297}
298
299sub __parseGet {
300        my $self = shift;
301        my $rv = shift; # currently contains 4 bytes of 'flag' followed by value
302        my $header = substr $rv, 0, 12, '';
303        my ($ident_hi, $ident_lo, $flags) = unpack "NNN", $header;
304        my $ident = ($ident_hi * 2 ** 32) + $ident_lo;
305
306        return $flags, $rv, $ident;
307}
308
309sub get {
310        my $self = shift;
311        my $key = shift;
312        my $parts = $self->_doCmd(::CMD_GET, $key, '');
313        return $self->__parseGet($parts);
314}
315
316sub _mutate {
317        my $self = shift;
318        my ($cmd, $key, $exp, $flags, $val, $ident) = @_;
319
320    my $ident_hi = 0;
321    my $ident_lo = 0;
322    if ($ident) {
323        $ident_hi = int($ident / 2 ** 32);
324        $ident_lo = int($ident % 2 ** 32);
325    }
326
327        return $self->_doCmd($cmd, $key, $val, pack(::SET_PKT_FMT, $ident_hi, $ident_lo, $flags, $exp));
328}
329
330sub set {
331        my $self = shift;
332        my ($key, $exp, $flags, $val, $ident) = @_;
333
334        return $self->_mutate(::CMD_SET, $key, $exp, $flags, $val, $ident);
335}
336
337sub __incrdecr {
338        my $self = shift;
339        my ($cmd, $key, $amt, $init, $exp) = @_;
340
341        my $amt_hi = int($amt / 2 ** 32);
342        my $amt_lo = int($amt % 2 ** 32);
343
344        my $init_hi = int($init / 2 ** 32);
345        my $init_lo = int($init % 2 ** 32);
346
347        my $data = $self->_doCmd($cmd, $key, '', pack(::INCRDECR_PKT_FMT, $amt_hi, $amt_lo, $init_hi, $init_lo, $exp));
348        my $header = substr $data, 0, 12, '';
349        my ($resp_hi, $resp_lo) = unpack "NN", $header;
350        my $resp = ($resp_hi * 2 ** 32) + $resp_lo;
351    return $resp;
352}
353
354sub incr {
355        my $self = shift;
356        my ($key, $amt, $init, $exp) = @_;
357        $amt = 1 unless defined $amt;
358        $init = 0 unless defined $init;
359        $exp = 0 unless defined $exp;
360
361        return $self->__incrdecr(::CMD_INCR, $key, $amt, $init, $exp);
362}
363
364sub decr {
365        my $self = shift;
366        my ($key, $amt, $init, $exp) = @_;
367        $amt = 1 unless defined $amt;
368        $init = 0 unless defined $init;
369        $exp = 0 unless defined $exp;
370
371        return $self->__incrdecr(::CMD_DECR, $key, $amt, $init, $exp);
372}
373
374sub add {
375        my $self = shift;
376        my ($key, $exp, $flags, $val) = @_;
377        return $self->_mutate(::CMD_ADD, $key, $exp, $flags, $val);
378}
379sub replace {
380        my $self = shift;
381        my ($key, $exp, $flags, $val) = @_;
382        return $self->_mutate(::CMD_REPLACE, $key, $exp, $flags, $val);
383}
384
385sub getMulti {
386        my $self = shift;
387        my @keys = @_;
388
389        for (my $i = 0; $i < @keys; $i++) {
390                $self->_sendCmd(::CMD_GETQ, $keys[$i], '', $i);
391        }
392
393        my $terminal = @keys + 10;
394        $self->_sendCmd(::CMD_NOOP, '', '', $terminal);
395
396        my %return;
397
398        while (1) {
399                my ($opaque, $data) = $self->_handleSingleResponse;
400                last if $opaque == $terminal;
401
402                $return{$keys[$opaque]} = [$self->__parseGet($data)];
403        }
404        return %return if wantarray;
405        return \%return;
406}
407
408sub noop {
409        my $self = shift;
410        return $self->_doCmd(::CMD_NOOP, '', '');
411}
412
413sub delete {
414        my $self = shift;
415        my ($key, $when) = @_;
416        $when = 0 unless defined $when;
417
418        return $self->_doCmd(::CMD_DELETE, $key, '', pack(::DEL_PKT_FMT, $when));
419}
420
421sub version {
422        my $self = shift;
423        return $self->_doCmd(::CMD_VERSION, '', '');
424}
425
426sub flush {
427        my $self = shift;
428        return $self->_doCmd(::CMD_FLUSH, '', '');
429}
430
431package MC::Error;
432
433use strict;
434use warnings;
435
436use constant ERR_UNKNOWN_CMD => 0x81;
437use constant ERR_NOT_FOUND   => 0x1;
438use constant ERR_EXISTS      => 0x2;
439
440use overload '""' => sub {
441        my $self = shift;
442
443        return "Memcache Error ($self->[0]): $self->[1]";
444};
445
446sub new {
447        my $class = shift;
448        my $error = [@_];
449       
450        my $self = bless $error, (ref $class || $class);
451
452        return $self;
453}
454
455sub not_found {
456        my $self = shift;
457
458        return $self->[0] == ERR_NOT_FOUND;
459}
460
461sub exists {
462        my $self = shift;
463
464        return $self->[0] == ERR_EXISTS;
465}
466
467# vim: filetype=perl
Note: See TracBrowser for help on using the browser.