1#!/usr/bin/perl 2 3use strict; 4use warnings; 5use Test::More tests => 2880; 6use FindBin qw($Bin); 7use lib "$Bin/lib"; 8use MemcachedTest; 9 10my $server = new_memcached(); 11ok($server, "started the server"); 12 13# Based almost 100% off testClient.py which is: 14# Copyright (c) 2007 Dustin Sallings <[email protected]> 15 16# Command constants 17use constant CMD_GET => 0x00; 18use constant CMD_SET => 0x01; 19use constant CMD_ADD => 0x02; 20use constant CMD_REPLACE => 0x03; 21use constant CMD_DELETE => 0x04; 22use constant CMD_INCR => 0x05; 23use constant CMD_DECR => 0x06; 24use constant CMD_QUIT => 0x07; 25use constant CMD_FLUSH => 0x08; 26use constant CMD_GETQ => 0x09; 27use constant CMD_NOOP => 0x0A; 28use constant CMD_VERSION => 0x0B; 29use constant CMD_GETK => 0x0C; 30use constant CMD_GETKQ => 0x0D; 31use constant CMD_APPEND => 0x0E; 32use constant CMD_PREPEND => 0x0F; 33use constant CMD_STAT => 0x10; 34use constant CMD_SETQ => 0x11; 35use constant CMD_ADDQ => 0x12; 36use constant CMD_REPLACEQ => 0x13; 37use constant CMD_DELETEQ => 0x14; 38use constant CMD_INCREMENTQ => 0x15; 39use constant CMD_DECREMENTQ => 0x16; 40use constant CMD_QUITQ => 0x17; 41use constant CMD_FLUSHQ => 0x18; 42use constant CMD_APPENDQ => 0x19; 43use constant CMD_PREPENDQ => 0x1A; 44 45# REQ and RES formats are divided even though they currently share 46# the same format, since they _could_ differ in the future. 47use constant REQ_PKT_FMT => "CCnCCnNNNN"; 48use constant RES_PKT_FMT => "CCnCCnNNNN"; 49use constant INCRDECR_PKT_FMT => "NNNNN"; 50use constant MIN_RECV_BYTES => length(pack(RES_PKT_FMT)); 51use constant REQ_MAGIC => 0x80; 52use constant RES_MAGIC => 0x81; 53 54my $mc = MC::Client->new; 55 56# Let's turn on detail stats for all this stuff 57 58$mc->stats('detail on'); 59 60my $check = sub { 61 my ($key, $orig_flags, $orig_val) = @_; 62 my ($flags, $val, $cas) = $mc->get($key); 63 is($flags, $orig_flags, "Flags is set properly"); 64 ok($val eq $orig_val || $val == $orig_val, $val . " = " . $orig_val); 65}; 66 67my $set = sub { 68 my ($key, $exp, $orig_flags, $orig_value) = @_; 69 $mc->set($key, $orig_value, $orig_flags, $exp); 70 $check->($key, $orig_flags, $orig_value); 71}; 72 73my $empty = sub { 74 my $key = shift; 75 my $rv =()= eval { $mc->get($key) }; 76 is($rv, 0, "Didn't get a result from get"); 77 ok($@->not_found, "We got a not found error when we expected one"); 78}; 79 80my $delete = sub { 81 my ($key, $when) = @_; 82 $mc->delete($key, $when); 83 $empty->($key); 84}; 85 86# diag "Test Version"; 87my $v = $mc->version; 88ok(defined $v && length($v), "Proper version: $v"); 89 90# diag "Flushing..."; 91$mc->flush; 92 93# diag "Noop"; 94$mc->noop; 95 96# diag "Simple set/get"; 97$set->('x', 5, 19, "somevalue"); 98 99# diag "Delete"; 100$delete->('x'); 101 102# diag "Flush"; 103$set->('x', 5, 19, "somevaluex"); 104$set->('y', 5, 17, "somevaluey"); 105$mc->flush; 106$empty->('x'); 107$empty->('y'); 108 109{ 110 # diag "Add"; 111 $empty->('i'); 112 $mc->add('i', 'ex', 5, 10); 113 $check->('i', 5, "ex"); 114 115 my $rv =()= eval { $mc->add('i', "ex2", 10, 5) }; 116 is($rv, 0, "Add didn't return anything"); 117 ok($@->exists, "Expected exists error received"); 118 $check->('i', 5, "ex"); 119} 120 121{ 122 # diag "Too big."; 123 $empty->('toobig'); 124 $mc->set('toobig', 'not too big', 10, 10); 125 eval { 126 my $bigval = ("x" x (1024*1024)) . "x"; 127 $mc->set('toobig', $bigval, 10, 10); 128 }; 129 ok($@->too_big, "Was too big"); 130 $empty->('toobig'); 131} 132 133{ 134 # diag "Replace"; 135 $empty->('j'); 136 137 my $rv =()= eval { $mc->replace('j', "ex", 19, 5) }; 138 is($rv, 0, "Replace didn't return anything"); 139 ok($@->not_found, "Expected not_found error received"); 140 $empty->('j'); 141 $mc->add('j', "ex2", 14, 5); 142 $check->('j', 14, "ex2"); 143 $mc->replace('j', "ex3", 24, 5); 144 $check->('j', 24, "ex3"); 145} 146 147{ 148 # diag "MultiGet"; 149 $mc->add('xx', "ex", 1, 5); 150 $mc->add('wye', "why", 2, 5); 151 my $rv = $mc->get_multi(qw(xx wye zed)); 152 153 # CAS is returned with all gets. 154 $rv->{xx}->[2] = 0; 155 $rv->{wye}->[2] = 0; 156 is_deeply($rv->{xx}, [1, 'ex', 0], "X is correct"); 157 is_deeply($rv->{wye}, [2, 'why', 0], "Y is correct"); 158 is(keys(%$rv), 2, "Got only two answers like we expect"); 159} 160 161# diag "Test increment"; 162$mc->flush; 163is($mc->incr("x"), 0, "First incr call is zero"); 164is($mc->incr("x"), 1, "Second incr call is one"); 165is($mc->incr("x", 211), 212, "Adding 211 gives you 212"); 166is($mc->incr("x", 2**33), 8589934804, "Blast the 32bit border"); 167 168# diag "Issue 48 - incrementing plain text."; 169{ 170 $mc->set("issue48", "text", 0, 0); 171 my $rv =()= eval { $mc->incr('issue48'); }; 172 ok($@ && $@->delta_badval, "Expected invalid value when incrementing text."); 173 $check->('issue48', 0, "text"); 174 175 $rv =()= eval { $mc->decr('issue48'); }; 176 ok($@ && $@->delta_badval, "Expected invalid value when decrementing text."); 177 $check->('issue48', 0, "text"); 178} 179 180 181# diag "Test decrement"; 182$mc->flush; 183is($mc->incr("x", undef, 5), 5, "Initial value"); 184is($mc->decr("x"), 4, "Decrease by one"); 185is($mc->decr("x", 211), 0, "Floor is zero"); 186 187{ 188 # diag "bug21"; 189 $mc->add("bug21", "9223372036854775807", 0, 0); 190 is($mc->incr("bug21"), 9223372036854775808, "First incr for bug21."); 191 is($mc->incr("bug21"), 9223372036854775809, "Second incr for bug21."); 192 is($mc->decr("bug21"), 9223372036854775808, "Decr for bug21."); 193} 194 195{ 196 # diag "CAS"; 197 $mc->flush; 198 199 { 200 my $rv =()= eval { $mc->set("x", "bad value", 19, 5, 0x7FFFFFF) }; 201 is($rv, 0, "Empty return on expected failure"); 202 ok($@->not_found, "Error was 'not found' as expected"); 203 } 204 205 $mc->add("x", "original value", 5, 19); 206 207 my ($flags, $val, $i) = $mc->get("x"); 208 is($val, "original value", "->gets returned proper value"); 209 210 { 211 my $rv =()= eval { $mc->set("x", "broken value", 19, 5, $i+1) }; 212 is($rv, 0, "Empty return on expected failure (1)"); 213 ok($@->exists, "Expected error state of 'exists' (1)"); 214 } 215 216 $mc->set("x", "new value", 19, 5, $i); 217 218 my ($newflags, $newval, $newi) = $mc->get("x"); 219 is($newval, "new value", "CAS properly overwrote value"); 220 221 { 222 my $rv =()= eval { $mc->set("x", "replay value", 19, 5, $i) }; 223 is($rv, 0, "Empty return on expected failure (2)"); 224 ok($@->exists, "Expected error state of 'exists' (2)"); 225 } 226} 227 228# diag "Silent set."; 229$mc->silent_mutation(::CMD_SETQ, 'silentset', 'silentsetval'); 230 231# diag "Silent add."; 232$mc->silent_mutation(::CMD_ADDQ, 'silentadd', 'silentaddval'); 233 234# diag "Silent replace."; 235{ 236 my $key = "silentreplace"; 237 my $extra = pack "NN", 829, 0; 238 $empty->($key); 239 # $mc->send_silent(::CMD_REPLACEQ, $key, 'somevalue', 7278552, $extra, 0); 240 # $empty->($key); 241 242 $mc->add($key, "xval", 831, 0); 243 $check->($key, 831, 'xval'); 244 245 $mc->send_silent(::CMD_REPLACEQ, $key, 'somevalue', 7278552, $extra, 0); 246 $check->($key, 829, 'somevalue'); 247} 248 249# diag "Silent delete"; 250{ 251 my $key = "silentdelete"; 252 $empty->($key); 253 $mc->set($key, "some val", 19, 0); 254 $mc->send_silent(::CMD_DELETEQ, $key, '', 772); 255 $empty->($key); 256} 257 258# diag "Silent increment"; 259{ 260 my $key = "silentincr"; 261 my $opaque = 98428747; 262 $empty->($key); 263 $mc->silent_incrdecr(::CMD_INCREMENTQ, $key, 0, 0, 0); 264 is($mc->incr($key, 0), 0, "First call is 0"); 265 266 $mc->silent_incrdecr(::CMD_INCREMENTQ, $key, 8, 0, 0); 267 is($mc->incr($key, 0), 8); 268} 269 270# diag "Silent decrement"; 271{ 272 my $key = "silentdecr"; 273 my $opaque = 98428147; 274 $empty->($key); 275 $mc->silent_incrdecr(::CMD_DECREMENTQ, $key, 0, 185, 0); 276 is($mc->incr($key, 0), 185); 277 278 $mc->silent_incrdecr(::CMD_DECREMENTQ, $key, 8, 0, 0); 279 is($mc->incr($key, 0), 177); 280} 281 282# diag "Silent flush"; 283{ 284 $set->('x', 5, 19, "somevaluex"); 285 $set->('y', 5, 17, "somevaluey"); 286 $mc->send_silent(::CMD_FLUSHQ, '', '', 2775256); 287 $empty->('x'); 288 $empty->('y'); 289} 290 291# diag "Append"; 292{ 293 my $key = "appendkey"; 294 my $value = "some value"; 295 $set->($key, 8, 19, $value); 296 $mc->_append_prepend(::CMD_APPEND, $key, " more"); 297 $check->($key, 19, $value . " more"); 298} 299 300# diag "Prepend"; 301{ 302 my $key = "prependkey"; 303 my $value = "some value"; 304 $set->($key, 8, 19, $value); 305 $mc->_append_prepend(::CMD_PREPEND, $key, "prefixed "); 306 $check->($key, 19, "prefixed " . $value); 307} 308 309# diag "Silent append"; 310{ 311 my $key = "appendqkey"; 312 my $value = "some value"; 313 $set->($key, 8, 19, $value); 314 $mc->send_silent(::CMD_APPENDQ, $key, " more", 7284492); 315 $check->($key, 19, $value . " more"); 316} 317 318# diag "Silent prepend"; 319{ 320 my $key = "prependqkey"; 321 my $value = "some value"; 322 $set->($key, 8, 19, $value); 323 $mc->send_silent(::CMD_PREPENDQ, $key, "prefixed ", 7284492); 324 $check->($key, 19, "prefixed " . $value); 325} 326 327# diag "Leaky binary get test."; 328# # http://code.google.com/p/memcached/issues/detail?id=16 329{ 330 # Get a new socket so we can speak text to it. 331 my $sock = $server->new_sock; 332 my $max = 1024 * 1024; 333 my $big = "a big value that's > .5M and < 1M. "; 334 while (length($big) * 2 < $max) { 335 $big = $big . $big; 336 } 337 my $biglen = length($big); 338 339 for(1..100) { 340 my $key = "some_key_$_"; 341 # print STDERR "Key is $key\n"; 342 # print $sock "set $key 0 0 $vallen\r\n$value\r\n"; 343 print $sock "set $key 0 0 $biglen\r\n$big\r\n"; 344 is(scalar <$sock>, "STORED\r\n", "stored big"); 345 my ($f, $v, $c) = $mc->get($key); 346 } 347} 348 349# diag "Test stats settings." 350{ 351 my %stats = $mc->stats('settings'); 352 353 is(1024, $stats{'maxconns'}); 354 is('NULL', $stats{'domain_socket'}); 355 is('on', $stats{'evictions'}); 356 is('yes', $stats{'cas_enabled'}); 357} 358 359# diag "Test protocol boundary overruns"; 360{ 361 use List::Util qw[min]; 362 # Attempting some protocol overruns by toying around with the edge 363 # of the data buffer at a few different sizes. This assumes the 364 # boundary is at or around 2048 bytes. 365 for (my $i = 1900; $i < 2100; $i++) { 366 my $k = "test_key_$i"; 367 my $v = 'x' x $i; 368 # diag "Trying $i $k"; 369 my $extra = pack "NN", 82, 0; 370 my $data = $mc->build_command(::CMD_SETQ, $k, $v, 0, $extra, 0); 371 $data .= $mc->build_command(::CMD_SETQ, "alt_$k", "blah", 0, $extra, 0); 372 if (length($data) > 2024) { 373 for (my $j = 2024; $j < min(2096, length($data)); $j++) { 374 $mc->{socket}->send(substr($data, 0, $j)); 375 $mc->flush_socket; 376 sleep(0.001); 377 $mc->{socket}->send(substr($data, $j)); 378 $mc->flush_socket; 379 } 380 } else { 381 $mc->{socket}->send($data); 382 } 383 $mc->flush_socket; 384 $check->($k, 82, $v); 385 $check->("alt_$k", 82, "blah"); 386 } 387} 388 389# Along with the assertion added to the code to verify we're staying 390# within bounds when we do a stats detail dump (detail turned on at 391# the top). 392my %stats = $mc->stats('detail dump'); 393 394# ###################################################################### 395# Test ends around here. 396# ###################################################################### 397 398package MC::Client; 399 400use strict; 401use warnings; 402use fields qw(socket); 403use IO::Socket::INET; 404 405sub new { 406 my $self = shift; 407 my $sock = $server->sock; 408 $self = fields::new($self); 409 $self->{socket} = $sock; 410 return $self; 411} 412 413sub build_command { 414 my $self = shift; 415 die "Not enough args to send_command" unless @_ >= 4; 416 my ($cmd, $key, $val, $opaque, $extra_header, $cas) = @_; 417 418 $extra_header = '' unless defined $extra_header; 419 my $keylen = length($key); 420 my $vallen = length($val); 421 my $extralen = length($extra_header); 422 my $datatype = 0; # field for future use 423 my $reserved = 0; # field for future use 424 my $totallen = $keylen + $vallen + $extralen; 425 my $ident_hi = 0; 426 my $ident_lo = 0; 427 428 if ($cas) { 429 $ident_hi = int($cas / 2 ** 32); 430 $ident_lo = int($cas % 2 ** 32); 431 } 432 433 my $msg = pack(::REQ_PKT_FMT, ::REQ_MAGIC, $cmd, $keylen, $extralen, 434 $datatype, $reserved, $totallen, $opaque, $ident_hi, 435 $ident_lo); 436 my $full_msg = $msg . $extra_header . $key . $val; 437 return $full_msg; 438} 439 440sub send_command { 441 my $self = shift; 442 die "Not enough args to send_command" unless @_ >= 4; 443 my ($cmd, $key, $val, $opaque, $extra_header, $cas) = @_; 444 445 my $full_msg = $self->build_command($cmd, $key, $val, $opaque, $extra_header, $cas); 446 447 my $sent = $self->{socket}->send($full_msg); 448 if($sent != length($full_msg)) { 449 die("only sent $sent of " . length($full_msg) . " bytes"); 450 } 451} 452 453sub flush_socket { 454 my $self = shift; 455 $self->{socket}->flush; 456} 457 458# Send a silent command and ensure it doesn't respond. 459sub send_silent { 460 my $self = shift; 461 die "Not enough args to send_silent" unless @_ >= 4; 462 my ($cmd, $key, $val, $opaque, $extra_header, $cas) = @_; 463 464 $self->send_command($cmd, $key, $val, $opaque, $extra_header, $cas); 465 $self->send_command(::CMD_NOOP, '', '', $opaque + 1); 466 467 my ($ropaque, $data) = $self->_handle_single_response; 468 Test::More::is($ropaque, $opaque + 1); 469} 470 471sub silent_mutation { 472 my $self = shift; 473 my ($cmd, $key, $value) = @_; 474 475 $empty->($key); 476 my $extra = pack "NN", 82, 0; 477 $mc->send_silent($cmd, $key, $value, 7278552, $extra, 0); 478 $check->($key, 82, $value); 479} 480 481sub _handle_single_response { 482 my $self = shift; 483 my $myopaque = shift; 484 485 $self->{socket}->recv(my $response, ::MIN_RECV_BYTES); 486 Test::More::is(length($response), ::MIN_RECV_BYTES, "Expected read length"); 487 488 my ($magic, $cmd, $keylen, $extralen, $datatype, $status, $remaining, 489 $opaque, $ident_hi, $ident_lo) = unpack(::RES_PKT_FMT, $response); 490 Test::More::is($magic, ::RES_MAGIC, "Got proper response magic"); 491 492 return ($opaque, '', '', 0) if($remaining == 0); 493 494 # fetch the value 495 my $rv=""; 496 while($remaining - length($rv) > 0) { 497 $self->{socket}->recv(my $buf, $remaining - length($rv)); 498 $rv .= $buf; 499 } 500 if(length($rv) != $remaining) { 501 my $found = length($rv); 502 die("Expected $remaining bytes, got $found"); 503 } 504 505 if (defined $myopaque) { 506 Test::More::is($opaque, $myopaque, "Expected opaque"); 507 } else { 508 Test::More::pass("Implicit pass since myopaque is undefined"); 509 } 510 511 my $cas = ($ident_hi * 2 ** 32) + $ident_lo; 512 513 if ($status) { 514 die MC::Error->new($status, $rv); 515 } 516 517 return ($opaque, $rv, $cas, $keylen); 518} 519 520sub _do_command { 521 my $self = shift; 522 die unless @_ >= 3; 523 my ($cmd, $key, $val, $extra_header, $cas) = @_; 524 525 $extra_header = '' unless defined $extra_header; 526 my $opaque = int(rand(2**32)); 527 $self->send_command($cmd, $key, $val, $opaque, $extra_header, $cas); 528 my (undef, $rv, $rcas) = $self->_handle_single_response($opaque); 529 return ($rv, $rcas); 530} 531 532sub _incrdecr_header { 533 my $self = shift; 534 my ($amt, $init, $exp) = @_; 535 536 my $amt_hi = int($amt / 2 ** 32); 537 my $amt_lo = int($amt % 2 ** 32); 538 539 my $init_hi = int($init / 2 ** 32); 540 my $init_lo = int($init % 2 ** 32); 541 542 my $extra_header = pack(::INCRDECR_PKT_FMT, $amt_hi, $amt_lo, $init_hi, 543 $init_lo, $exp); 544 545 return $extra_header; 546} 547 548sub _incrdecr { 549 my $self = shift; 550 my ($cmd, $key, $amt, $init, $exp) = @_; 551 552 my ($data, undef) = $self->_do_command($cmd, $key, '', 553 $self->_incrdecr_header($amt, $init, $exp)); 554 555 my $header = substr $data, 0, 8, ''; 556 my ($resp_hi, $resp_lo) = unpack "NN", $header; 557 my $resp = ($resp_hi * 2 ** 32) + $resp_lo; 558 559 return $resp; 560} 561 562sub silent_incrdecr { 563 my $self = shift; 564 my ($cmd, $key, $amt, $init, $exp) = @_; 565 my $opaque = 8275753; 566 567 $mc->send_silent($cmd, $key, '', $opaque, 568 $mc->_incrdecr_header($amt, $init, $exp)); 569} 570 571sub stats { 572 my $self = shift; 573 my $key = shift; 574 my $cas = 0; 575 my $opaque = int(rand(2**32)); 576 $self->send_command(::CMD_STAT, $key, '', $opaque, '', $cas); 577 578 my %rv = (); 579 my $found_key = ''; 580 my $found_val = ''; 581 do { 582 my ($op, $data, $cas, $keylen) = $self->_handle_single_response($opaque); 583 if($keylen > 0) { 584 $found_key = substr($data, 0, $keylen); 585 $found_val = substr($data, $keylen); 586 $rv{$found_key} = $found_val; 587 } else { 588 $found_key = ''; 589 } 590 } while($found_key ne ''); 591 return %rv; 592} 593 594sub get { 595 my $self = shift; 596 my $key = shift; 597 my ($rv, $cas) = $self->_do_command(::CMD_GET, $key, '', ''); 598 599 my $header = substr $rv, 0, 4, ''; 600 my $flags = unpack("N", $header); 601 602 return ($flags, $rv, $cas); 603} 604 605sub get_multi { 606 my $self = shift; 607 my @keys = @_; 608 609 for (my $i = 0; $i < @keys; $i++) { 610 $self->send_command(::CMD_GETQ, $keys[$i], '', $i, '', 0); 611 } 612 613 my $terminal = @keys + 10; 614 $self->send_command(::CMD_NOOP, '', '', $terminal); 615 616 my %return; 617 while (1) { 618 my ($opaque, $data) = $self->_handle_single_response; 619 last if $opaque == $terminal; 620 621 my $header = substr $data, 0, 4, ''; 622 my $flags = unpack("N", $header); 623 624 $return{$keys[$opaque]} = [$flags, $data]; 625 } 626 627 return %return if wantarray; 628 return \%return; 629} 630 631sub version { 632 my $self = shift; 633 return $self->_do_command(::CMD_VERSION, '', ''); 634} 635 636sub flush { 637 my $self = shift; 638 return $self->_do_command(::CMD_FLUSH, '', ''); 639} 640 641sub add { 642 my $self = shift; 643 my ($key, $val, $flags, $expire) = @_; 644 my $extra_header = pack "NN", $flags, $expire; 645 my $cas = 0; 646 return $self->_do_command(::CMD_ADD, $key, $val, $extra_header, $cas); 647} 648 649sub set { 650 my $self = shift; 651 my ($key, $val, $flags, $expire, $cas) = @_; 652 my $extra_header = pack "NN", $flags, $expire; 653 return $self->_do_command(::CMD_SET, $key, $val, $extra_header, $cas); 654} 655 656sub _append_prepend { 657 my $self = shift; 658 my ($cmd, $key, $val, $cas) = @_; 659 return $self->_do_command($cmd, $key, $val, '', $cas); 660} 661 662sub replace { 663 my $self = shift; 664 my ($key, $val, $flags, $expire) = @_; 665 my $extra_header = pack "NN", $flags, $expire; 666 my $cas = 0; 667 return $self->_do_command(::CMD_REPLACE, $key, $val, $extra_header, $cas); 668} 669 670sub delete { 671 my $self = shift; 672 my ($key) = @_; 673 return $self->_do_command(::CMD_DELETE, $key, ''); 674} 675 676sub incr { 677 my $self = shift; 678 my ($key, $amt, $init, $exp) = @_; 679 $amt = 1 unless defined $amt; 680 $init = 0 unless defined $init; 681 $exp = 0 unless defined $exp; 682 683 return $self->_incrdecr(::CMD_INCR, $key, $amt, $init, $exp); 684} 685 686sub decr { 687 my $self = shift; 688 my ($key, $amt, $init, $exp) = @_; 689 $amt = 1 unless defined $amt; 690 $init = 0 unless defined $init; 691 $exp = 0 unless defined $exp; 692 693 return $self->_incrdecr(::CMD_DECR, $key, $amt, $init, $exp); 694} 695 696sub noop { 697 my $self = shift; 698 return $self->_do_command(::CMD_NOOP, '', ''); 699} 700 701package MC::Error; 702 703use strict; 704use warnings; 705 706use constant ERR_UNKNOWN_CMD => 0x81; 707use constant ERR_NOT_FOUND => 0x1; 708use constant ERR_EXISTS => 0x2; 709use constant ERR_TOO_BIG => 0x3; 710use constant ERR_EINVAL => 0x4; 711use constant ERR_NOT_STORED => 0x5; 712use constant ERR_DELTA_BADVAL => 0x6; 713 714use overload '""' => sub { 715 my $self = shift; 716 return "Memcache Error ($self->[0]): $self->[1]"; 717}; 718 719sub new { 720 my $class = shift; 721 my $error = [@_]; 722 my $self = bless $error, (ref $class || $class); 723 724 return $self; 725} 726 727sub not_found { 728 my $self = shift; 729 return $self->[0] == ERR_NOT_FOUND; 730} 731 732sub exists { 733 my $self = shift; 734 return $self->[0] == ERR_EXISTS; 735} 736 737sub too_big { 738 my $self = shift; 739 return $self->[0] == ERR_TOO_BIG; 740} 741 742sub delta_badval { 743 my $self = shift; 744 return $self->[0] == ERR_DELTA_BADVAL; 745} 746 747# vim: filetype=perl 748 749