Merge in fixes for deprecated bits in behavior.
[m6w6/libmemcached] / clients / memcapable.c
index f1d7d6a4d7330f8b91e969fe951870b35a98dc38..69d2557ea959608a0702a2494971595cd17cb313 100644 (file)
@@ -1,13 +1,19 @@
+/* LibMemcached
+ * Copyright (C) 2006-2009 Brian Aker
+ * All rights reserved.
+ *
+ * Use and distribution licensed under the BSD license.  See
+ * the COPYING file in the parent directory for full text.
+ *
+ * Summary:
+ *
+ */
+
 /* -*- Mode: C; tab-width: 2; c-basic-offset: 2; indent-tabs-mode: nil -*- */
 #undef NDEBUG
 #include "config.h"
 #include <pthread.h>
 #include <sys/types.h>
-#include <sys/socket.h>
-#include <netdb.h>
-#include <arpa/inet.h>
-#include <netinet/in.h>
-#include <netinet/tcp.h>
 #include <fcntl.h>
 #include <signal.h>
 #include <stdio.h>
 #include <inttypes.h>
 #include <stdbool.h>
 #include <unistd.h>
-#include <poll.h>
 #include <ctype.h>
 
+#include <libmemcached/memcached.h>
 #include <libmemcached/memcached/protocol_binary.h>
 #include <libmemcached/byteorder.h>
+#include "utilities.h"
 
 #ifdef linux
 /* /usr/include/netinet/in.h defines macros from ntohs() to _bswap_nn to
@@ -37,7 +44,7 @@
 /* Should we generate coredumps when we enounter an error (-c) */
 static bool do_core= false;
 /* connection to the server */
-static int sock;
+static memcached_socket_t sock;
 /* Should the output from test failures be verbose or quiet? */
 static bool verbose= false;
 
@@ -101,14 +108,23 @@ static struct addrinfo *lookuphost(const char *hostname, const char *port)
  * Set the socket in nonblocking mode
  * @return -1 if failure, the socket otherwise
  */
-static int set_noblock(void)
+static memcached_socket_t set_noblock(void)
 {
+#ifdef WIN32
+  u_long arg = 1;
+  if (ioctlsocket(sock, FIONBIO, &arg) == SOCKET_ERROR)
+  {
+    perror("Failed to set nonblocking io");
+    closesocket(sock);
+    return INVALID_SOCKET;
+  }
+#else
   int flags= fcntl(sock, F_GETFL, 0);
   if (flags == -1)
   {
     perror("Failed to get socket flags");
-    close(sock);
-    return -1;
+    closesocket(sock);
+    return INVALID_SOCKET;
   }
 
   if ((flags & O_NONBLOCK) != O_NONBLOCK)
@@ -116,11 +132,11 @@ static int set_noblock(void)
     if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) == -1)
     {
       perror("Failed to set socket to nonblocking mode");
-      close(sock);
-      return -1;
+      closesocket(sock);
+      return INVALID_SOCKET;
     }
   }
-
+#endif
   return sock;
 }
 
@@ -130,28 +146,30 @@ static int set_noblock(void)
  * @param port the port number (or service) to connect to
  * @return positive integer if success, -1 otherwise
  */
-static int connect_server(const char *hostname, const char *port)
+static memcached_socket_t connect_server(const char *hostname, const char *port)
 {
   struct addrinfo *ai= lookuphost(hostname, port);
-  sock= -1;
+  sock= INVALID_SOCKET;
   if (ai != NULL)
   {
-    if ((sock=socket(ai->ai_family, ai->ai_socktype,
-                     ai->ai_protocol)) != -1)
+    if ((sock= socket(ai->ai_family, ai->ai_socktype,
+                      ai->ai_protocol)) != INVALID_SOCKET)
     {
-      if (connect(sock, ai->ai_addr, ai->ai_addrlen) == -1)
+      if (connect(sock, ai->ai_addr, ai->ai_addrlen) == SOCKET_ERROR)
       {
         fprintf(stderr, "Failed to connect socket: %s\n",
-                strerror(errno));
-        close(sock);
-        sock= -1;
+                strerror(get_socket_errno()));
+        closesocket(sock);
+        sock= INVALID_SOCKET;
       }
       else
       {
         sock= set_noblock();
       }
-    } else
-      fprintf(stderr, "Failed to create socket: %s\n", strerror(errno));
+    }
+    else
+      fprintf(stderr, "Failed to create socket: %s\n",
+              strerror(get_socket_errno()));
 
     freeaddrinfo(ai);
   }
@@ -159,28 +177,29 @@ static int connect_server(const char *hostname, const char *port)
   return sock;
 }
 
-static ssize_t timeout_io_op(int fd, short direction, void *buf, size_t len)
+static ssize_t timeout_io_op(memcached_socket_t fd, short direction, void *buf, size_t len)
 {
   ssize_t ret;
 
   if (direction == POLLOUT)
-    ret= write(fd, buf, len);
+     ret= send(fd, buf, len, 0);
   else
-    ret= read(fd, buf, len);
+     ret= recv(fd, buf, len, 0);
 
-  if (ret == -1 && errno == EWOULDBLOCK) {
+  if (ret == SOCKET_ERROR && get_socket_errno() == EWOULDBLOCK) {
     struct pollfd fds= {
       .events= direction,
       .fd= fd
     };
+
     int err= poll(&fds, 1, timeout * 1000);
 
     if (err == 1)
     {
       if (direction == POLLOUT)
-        ret= write(fd, buf, len);
+         ret= send(fd, buf, len, 0);
       else
-        ret= read(fd, buf, len);
+         ret= recv(fd, buf, len, 0);
     }
     else if (err == 0)
     {
@@ -207,7 +226,7 @@ static enum test_return ensure(bool val, const char *expression, const char *fil
   if (!val)
   {
     if (verbose)
-      fprintf(stderr, "\n%s:%u: %s", file, line, expression);
+      fprintf(stderr, "\n%s:%d: %s", file, line, expression);
 
     if (do_core)
       abort();
@@ -234,7 +253,7 @@ static enum test_return retry_write(const void* buf, size_t len)
     size_t num_bytes= len - offset;
     ssize_t nw= timeout_io_op(sock, POLLOUT, (void*)(ptr + offset), num_bytes);
     if (nw == -1)
-      verify(errno == EINTR || errno == EAGAIN);
+      verify(get_socket_errno() == EINTR || get_socket_errno() == EAGAIN);
     else
       offset+= (size_t)nw;
   } while (offset < len);
@@ -284,7 +303,8 @@ static enum test_return retry_read(void *buf, size_t len)
     ssize_t nr= timeout_io_op(sock, POLLIN, ((char*) buf) + offset, len - offset);
     switch (nr) {
     case -1 :
-      verify(errno == EINTR || errno == EAGAIN);
+       fprintf(stderr, "Errno: %d %s\n", get_socket_errno(), strerror(errno));
+      verify(get_socket_errno() == EINTR || get_socket_errno() == EAGAIN);
       break;
     case 0:
       return TEST_FAIL;
@@ -302,7 +322,7 @@ static enum test_return retry_read(void *buf, size_t len)
  */
 static enum test_return recv_packet(response *rsp)
 {
-  execute(retry_read(rsp, sizeof (protocol_binary_response_no_extras)));
+  execute(retry_read(rsp, sizeof(protocol_binary_response_no_extras)));
 
   /* Fix the byte order in the packet header */
   rsp->plain.message.header.response.keylen=
@@ -331,7 +351,7 @@ static enum test_return recv_packet(response *rsp)
  * @param dta the data to store with the key
  * @param dtalen the length of the data to store with the key
  * @param flags the flags to store along with the key
- * @param exp the expiry time for the key
+ * @param exptime the expiry time for the key
  */
 static void storage_command(command *cmd,
                             uint8_t cc,
@@ -340,7 +360,7 @@ static void storage_command(command *cmd,
                             const void* dta,
                             size_t dtalen,
                             uint32_t flags,
-                            uint32_t exp)
+                            uint32_t exptime)
 {
   /* all of the storage commands use the same command layout */
   protocol_binary_request_set *request= &cmd->set;
@@ -353,7 +373,7 @@ static void storage_command(command *cmd,
   request->message.header.request.bodylen= (uint32_t)(keylen + 8 + dtalen);
   request->message.header.request.opaque= 0xdeadbeef;
   request->message.body.flags= flags;
-  request->message.body.expiration= exp;
+  request->message.body.expiration= exptime;
 
   off_t key_offset= sizeof (protocol_binary_request_no_extras) + 8;
   memcpy(cmd->bytes + key_offset, key, keylen);
@@ -424,7 +444,7 @@ static void flush_command(command *cmd,
  * @param keylen the number of bytes in the key
  * @param delta the number to add/subtract
  * @param initial the initial value if the key doesn't exist
- * @param exp when the key should expire if it isn't set
+ * @param exptime when the key should expire if it isn't set
  */
 static void arithmetic_command(command *cmd,
                                uint8_t cc,
@@ -432,7 +452,7 @@ static void arithmetic_command(command *cmd,
                                size_t keylen,
                                uint64_t delta,
                                uint64_t initial,
-                               uint32_t exp)
+                               uint32_t exptime)
 {
   memset(cmd, 0, sizeof (cmd->incr));
   cmd->incr.message.header.request.magic= PROTOCOL_BINARY_REQ;
@@ -443,7 +463,7 @@ static void arithmetic_command(command *cmd,
   cmd->incr.message.header.request.opaque= 0xdeadbeef;
   cmd->incr.message.body.delta= htonll(delta);
   cmd->incr.message.body.initial= htonll(initial);
-  cmd->incr.message.body.expiration= htonl(exp);
+  cmd->incr.message.body.expiration= htonl(exptime);
 
   off_t key_offset= sizeof (protocol_binary_request_no_extras) + 20;
   memcpy(cmd->bytes + key_offset, key, keylen);
@@ -564,18 +584,31 @@ static enum test_return do_validate_response_header(response *rsp,
 #define validate_response_header(a,b,c) \
         do_validate_response_header(a,b,c) == TEST_PASS
 
-static enum test_return test_binary_noop(void)
+
+static enum test_return send_binary_noop(void)
 {
   command cmd;
-  response rsp;
   raw_command(&cmd, PROTOCOL_BINARY_CMD_NOOP, NULL, 0, NULL, 0);
   execute(send_packet(&cmd));
+  return TEST_PASS;
+}
+
+static enum test_return receive_binary_noop(void)
+{
+  response rsp;
   execute(recv_packet(&rsp));
   verify(validate_response_header(&rsp, PROTOCOL_BINARY_CMD_NOOP,
                                   PROTOCOL_BINARY_RESPONSE_SUCCESS));
   return TEST_PASS;
 }
 
+static enum test_return test_binary_noop(void)
+{
+  execute(send_binary_noop());
+  execute(receive_binary_noop());
+  return TEST_PASS;
+}
+
 static enum test_return test_binary_quit_impl(uint8_t cc)
 {
   command cmd;
@@ -590,7 +623,7 @@ static enum test_return test_binary_quit_impl(uint8_t cc)
                                     PROTOCOL_BINARY_RESPONSE_SUCCESS));
   }
 
-  /* Socket should be closed now, read should return 0 */
+  /* Socket should be closed now, read should return EXIT_SUCCESS */
   verify(timeout_io_op(sock, POLLIN, rsp.bytes, sizeof(rsp.bytes)) == 0);
 
   return TEST_PASS_RECONNECT;
@@ -661,10 +694,12 @@ static enum test_return test_binary_set_impl(const char* key, uint8_t cc)
   cmd.plain.message.header.request.cas=
           htonll(rsp.plain.message.header.response.cas - 1);
   execute(resend_packet(&cmd));
+  execute(send_binary_noop());
   execute(recv_packet(&rsp));
   verify(validate_response_header(&rsp, cc, PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS));
+  execute(receive_binary_noop());
 
-  return test_binary_noop();
+  return TEST_PASS;
 }
 
 static enum test_return test_binary_set(void)
@@ -701,7 +736,9 @@ static enum test_return test_binary_add_impl(const char* key, uint8_t cc)
       else
         expected_result= PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS;
 
+      execute(send_binary_noop());
       execute(recv_packet(&rsp));
+      execute(receive_binary_noop());
       verify(validate_response_header(&rsp, cc, expected_result));
     }
     else
@@ -758,7 +795,9 @@ static enum test_return test_binary_replace_impl(const char* key, uint8_t cc)
       else
         expected_result=PROTOCOL_BINARY_RESPONSE_SUCCESS;
 
+      execute(send_binary_noop());
       execute(recv_packet(&rsp));
+      execute(receive_binary_noop());
       verify(validate_response_header(&rsp, cc, expected_result));
 
       if (ii == 0)
@@ -785,7 +824,9 @@ static enum test_return test_binary_replace_impl(const char* key, uint8_t cc)
   cmd.plain.message.header.request.cas=
           htonll(rsp.plain.message.header.response.cas - 1);
   execute(resend_packet(&cmd));
+  execute(send_binary_noop());
   execute(recv_packet(&rsp));
+  execute(receive_binary_noop());
   verify(validate_response_header(&rsp, cc, PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS));
 
   return TEST_PASS;
@@ -809,8 +850,10 @@ static enum test_return test_binary_delete_impl(const char *key, uint8_t cc)
 
   /* The delete shouldn't work the first time, because the item isn't there */
   execute(send_packet(&cmd));
+  execute(send_binary_noop());
   execute(recv_packet(&rsp));
   verify(validate_response_header(&rsp, cc, PROTOCOL_BINARY_RESPONSE_KEY_ENOENT));
+  execute(receive_binary_noop());
   execute(binary_set_item(key, key));
 
   /* The item should be present now, resend*/
@@ -843,19 +886,23 @@ static enum test_return test_binary_get_impl(const char *key, uint8_t cc)
 
   raw_command(&cmd, cc, key, strlen(key), NULL, 0);
   execute(send_packet(&cmd));
+  execute(send_binary_noop());
 
   if (cc == PROTOCOL_BINARY_CMD_GET || cc == PROTOCOL_BINARY_CMD_GETK)
   {
     execute(recv_packet(&rsp));
     verify(validate_response_header(&rsp, cc, PROTOCOL_BINARY_RESPONSE_KEY_ENOENT));
   }
-  else
-    execute(test_binary_noop());
+
+  execute(receive_binary_noop());
 
   execute(binary_set_item(key, key));
   execute(resend_packet(&cmd));
+  execute(send_binary_noop());
+
   execute(recv_packet(&rsp));
   verify(validate_response_header(&rsp, cc, PROTOCOL_BINARY_RESPONSE_SUCCESS));
+  execute(receive_binary_noop());
 
   return TEST_PASS;
 }
@@ -1103,24 +1150,6 @@ static enum test_return test_binary_stat(void)
   return TEST_PASS;
 }
 
-static enum test_return test_binary_illegal(void)
-{
-  command cmd;
-  response rsp;
-  uint8_t cc= 0x1b;
-
-  while (cc != 0x00)
-  {
-    raw_command(&cmd, cc, NULL, 0, NULL, 0);
-    execute(send_packet(&cmd));
-    execute(recv_packet(&rsp));
-    verify(validate_response_header(&rsp, cc, PROTOCOL_BINARY_RESPONSE_UNKNOWN_COMMAND));
-    ++cc;
-  }
-
-  return TEST_PASS_RECONNECT;
-}
-
 static enum test_return send_string(const char *cmd)
 {
   execute(retry_write(cmd, strlen(cmd)));
@@ -1152,24 +1181,37 @@ static enum test_return receive_line(char *buffer, size_t size)
 static enum test_return receive_response(const char *msg) {
   char buffer[80];
   execute(receive_line(buffer, sizeof(buffer)));
+  if (strcmp(msg, buffer) != 0) {
+      fprintf(stderr, "[%s]\n", buffer);
+  }
   verify(strcmp(msg, buffer) == 0);
   return TEST_PASS;
 }
 
+static enum test_return receive_error_response(void)
+{
+  char buffer[80];
+  execute(receive_line(buffer, sizeof(buffer)));
+  verify(strncmp(buffer, "ERROR", 5) == 0 ||
+         strncmp(buffer, "CLIENT_ERROR", 12) == 0 ||
+         strncmp(buffer, "SERVER_ERROR", 12) == 0);
+  return TEST_PASS;
+}
+
 static enum test_return test_ascii_quit(void)
 {
   /* Verify that quit handles unknown options */
   execute(send_string("quit foo bar\r\n"));
-  execute(receive_response("ERROR\r\n"));
+  execute(receive_error_response());
 
   /* quit doesn't support noreply */
   execute(send_string("quit noreply\r\n"));
-  execute(receive_response("ERROR\r\n"));
+  execute(receive_error_response());
 
   /* Verify that quit works */
   execute(send_string("quit\r\n"));
 
-  /* Socket should be closed now, read should return 0 */
+  /* Socket should be closed now, read should return EXIT_SUCCESS */
   char buffer[80];
   verify(timeout_io_op(sock, POLLIN, buffer, sizeof(buffer)) == 0);
   return TEST_PASS_RECONNECT;
@@ -1180,11 +1222,11 @@ static enum test_return test_ascii_version(void)
 {
   /* Verify that version command handles unknown options */
   execute(send_string("version foo bar\r\n"));
-  execute(receive_response("ERROR\r\n"));
+  execute(receive_error_response());
 
   /* version doesn't support noreply */
   execute(send_string("version noreply\r\n"));
-  execute(receive_response("ERROR\r\n"));
+  execute(receive_error_response());
 
   /* Verify that verify works */
   execute(send_string("version\r\n"));
@@ -1199,16 +1241,16 @@ static enum test_return test_ascii_verbosity(void)
 {
   /* This command does not adhere to the spec! */
   execute(send_string("verbosity foo bar my\r\n"));
-  execute(receive_response("ERROR\r\n"));
+  execute(receive_error_response());
 
   execute(send_string("verbosity noreply\r\n"));
-  execute(test_ascii_version());
+  execute(receive_error_response());
 
   execute(send_string("verbosity 0 noreply\r\n"));
   execute(test_ascii_version());
 
   execute(send_string("verbosity\r\n"));
-  execute(receive_response("ERROR\r\n"));
+  execute(receive_error_response());
 
   execute(send_string("verbosity 1\r\n"));
   execute(receive_response("OK\r\n"));
@@ -1225,8 +1267,7 @@ static enum test_return test_ascii_set_impl(const char* key, bool noreply)
 {
   /* @todo add tests for bogus format! */
   char buffer[1024];
-  sprintf(buffer, "set %s 0 0 5%s\r\nvalue\r\n", key,
-          noreply ? " noreply" : "");
+  snprintf(buffer, sizeof(buffer), "set %s 0 0 5%s\r\nvalue\r\n", key, noreply ? " noreply" : "");
   execute(send_string(buffer));
 
   if (!noreply)
@@ -1249,8 +1290,7 @@ static enum test_return test_ascii_add_impl(const char* key, bool noreply)
 {
   /* @todo add tests for bogus format! */
   char buffer[1024];
-  sprintf(buffer, "add %s 0 0 5%s\r\nvalue\r\n", key,
-          noreply ? " noreply" : "");
+  snprintf(buffer, sizeof(buffer), "add %s 0 0 5%s\r\nvalue\r\n", key, noreply ? " noreply" : "");
   execute(send_string(buffer));
 
   if (!noreply)
@@ -1274,6 +1314,41 @@ static enum test_return test_ascii_add_noreply(void)
   return test_ascii_add_impl("test_ascii_add_noreply", true);
 }
 
+static enum test_return ascii_get_unknown_value(char **key, char **value, ssize_t *ndata)
+{
+  char buffer[1024];
+
+  execute(receive_line(buffer, sizeof(buffer)));
+  verify(strncmp(buffer, "VALUE ", 6) == 0);
+  char *end= strchr(buffer + 6, ' ');
+  verify(end != NULL);
+  *end= '\0';
+  *key= strdup(buffer + 6);
+  verify(*key != NULL);
+  char *ptr= end + 1;
+
+  unsigned long val= strtoul(ptr, &end, 10); /* flags */
+  verify(ptr != end);
+  verify(val == 0);
+  verify(end != NULL);
+  *ndata = (ssize_t)strtoul(end, &end, 10); /* size */
+  verify(ptr != end);
+  verify(end != NULL);
+  while (*end != '\n' && isspace(*end))
+    ++end;
+  verify(*end == '\n');
+
+  *value= malloc((size_t)*ndata);
+  verify(*value != NULL);
+
+  execute(retry_read(*value, (size_t)*ndata));
+
+  execute(retry_read(buffer, 2));
+  verify(memcmp(buffer, "\r\n", 2) == 0);
+
+  return TEST_PASS;
+}
+
 static enum test_return ascii_get_value(const char *key, const char *value)
 {
 
@@ -1317,7 +1392,7 @@ static enum test_return ascii_get_item(const char *key, const char *value,
     datasize= strlen(value);
 
   verify(datasize < sizeof(buffer));
-  sprintf(buffer, "get %s\r\n", key);
+  snprintf(buffer, sizeof(buffer), "get %s\r\n", key);
   execute(send_string(buffer));
 
   if (exist)
@@ -1378,7 +1453,7 @@ static enum test_return ascii_gets_item(const char *key, const char *value,
     datasize= strlen(value);
 
   verify(datasize < sizeof(buffer));
-  sprintf(buffer, "gets %s\r\n", key);
+  snprintf(buffer, sizeof(buffer), "gets %s\r\n", key);
   execute(send_string(buffer));
 
   if (exist)
@@ -1394,7 +1469,7 @@ static enum test_return ascii_set_item(const char *key, const char *value)
 {
   char buffer[300];
   size_t len= strlen(value);
-  sprintf(buffer, "set %s 0 0 %u\r\n", key, (unsigned int)len);
+  snprintf(buffer, sizeof(buffer), "set %s 0 0 %u\r\n", key, (unsigned int)len);
   execute(send_string(buffer));
   execute(retry_write(value, len));
   execute(send_string("\r\n"));
@@ -1405,8 +1480,7 @@ static enum test_return ascii_set_item(const char *key, const char *value)
 static enum test_return test_ascii_replace_impl(const char* key, bool noreply)
 {
   char buffer[1024];
-  sprintf(buffer, "replace %s 0 0 5%s\r\nvalue\r\n", key,
-          noreply ? " noreply" : "");
+  snprintf(buffer, sizeof(buffer), "replace %s 0 0 5%s\r\nvalue\r\n", key, noreply ? " noreply" : "");
   execute(send_string(buffer));
 
   if (noreply)
@@ -1446,8 +1520,7 @@ static enum test_return test_ascii_cas_impl(const char* key, bool noreply)
   execute(ascii_set_item(key, "value"));
   execute(ascii_gets_item(key, "value", true, &cas));
 
-  sprintf(buffer, "cas %s 0 0 6 %lu%s\r\nvalue2\r\n", key, cas,
-          noreply ? " noreply" : "");
+  snprintf(buffer, sizeof(buffer), "cas %s 0 0 6 %lu%s\r\nvalue2\r\n", key, cas, noreply ? " noreply" : "");
   execute(send_string(buffer));
 
   if (noreply)
@@ -1481,13 +1554,13 @@ static enum test_return test_ascii_delete_impl(const char *key, bool noreply)
   execute(ascii_set_item(key, "value"));
 
   execute(send_string("delete\r\n"));
-  execute(receive_response("ERROR\r\n"));
+  execute(receive_error_response());
   /* BUG: the server accepts delete a b */
   execute(send_string("delete a b c d e\r\n"));
-  execute(receive_response("ERROR\r\n"));
+  execute(receive_error_response());
 
   char buffer[1024];
-  sprintf(buffer, "delete %s%s\r\n", key, noreply ? " noreply" : "");
+  snprintf(buffer, sizeof(buffer), "delete %s%s\r\n", key, noreply ? " noreply" : "");
   execute(send_string(buffer));
 
   if (noreply)
@@ -1520,7 +1593,7 @@ static enum test_return test_ascii_get(void)
   execute(ascii_set_item("test_ascii_get", "value"));
 
   execute(send_string("get\r\n"));
-  execute(receive_response("ERROR\r\n"));
+  execute(receive_error_response());
   execute(ascii_get_item("test_ascii_get", "value", true));
   execute(ascii_get_item("test_ascii_get_notfound", "value", false));
 
@@ -1532,7 +1605,7 @@ static enum test_return test_ascii_gets(void)
   execute(ascii_set_item("test_ascii_gets", "value"));
 
   execute(send_string("gets\r\n"));
-  execute(receive_response("ERROR\r\n"));
+  execute(receive_error_response());
   unsigned long cas;
   execute(ascii_gets_item("test_ascii_gets", "value", true, &cas));
   execute(ascii_gets_item("test_ascii_gets_notfound", "value", false, &cas));
@@ -1542,31 +1615,65 @@ static enum test_return test_ascii_gets(void)
 
 static enum test_return test_ascii_mget(void)
 {
-  execute(ascii_set_item("test_ascii_mget1", "value"));
-  execute(ascii_set_item("test_ascii_mget2", "value"));
-  execute(ascii_set_item("test_ascii_mget3", "value"));
-  execute(ascii_set_item("test_ascii_mget4", "value"));
-  execute(ascii_set_item("test_ascii_mget5", "value"));
+  const uint32_t nkeys= 5;
+  const char * const keys[]= {
+    "test_ascii_mget1",
+    "test_ascii_mget2",
+    /* test_ascii_mget_3 does not exist :) */
+    "test_ascii_mget4",
+    "test_ascii_mget5",
+    "test_ascii_mget6"
+  };
+
+  for (uint32_t x= 0; x < nkeys; ++x)
+    execute(ascii_set_item(keys[x], "value"));
 
+  /* Ask for a key that doesn't exist as well */
   execute(send_string("get test_ascii_mget1 test_ascii_mget2 test_ascii_mget3 "
                       "test_ascii_mget4 test_ascii_mget5 "
                       "test_ascii_mget6\r\n"));
-  execute(ascii_get_value("test_ascii_mget1", "value"));
-  execute(ascii_get_value("test_ascii_mget2", "value"));
-  execute(ascii_get_value("test_ascii_mget3", "value"));
-  execute(ascii_get_value("test_ascii_mget4", "value"));
-  execute(ascii_get_value("test_ascii_mget5", "value"));
+
+  char *returned[nkeys];
+
+  for (uint32_t x= 0; x < nkeys; ++x)
+  {
+    ssize_t nbytes = 0;
+    char *v= NULL;
+    execute(ascii_get_unknown_value(&returned[x], &v, &nbytes));
+    verify(nbytes == 5);
+    verify(memcmp(v, "value", 5) == 0);
+    free(v);
+  }
 
   char buffer[5];
   execute(retry_read(buffer, 5));
   verify(memcmp(buffer, "END\r\n", 5) == 0);
- return TEST_PASS;
+
+  /* verify that we got all the keys we expected */
+  for (uint32_t x= 0; x < nkeys; ++x)
+  {
+    bool found= false;
+    for (uint32_t y= 0; y < nkeys; ++y)
+    {
+      if (strcmp(keys[x], returned[y]) == 0)
+      {
+        found = true;
+        break;
+      }
+    }
+    verify(found);
+  }
+
+  for (uint32_t x= 0; x < nkeys; ++x)
+    free(returned[x]);
+
+  return TEST_PASS;
 }
 
 static enum test_return test_ascii_incr_impl(const char* key, bool noreply)
 {
   char cmd[300];
-  sprintf(cmd, "incr %s 1%s\r\n", key, noreply ? " noreply" : "");
+  snprintf(cmd, sizeof(cmd), "incr %s 1%s\r\n", key, noreply ? " noreply" : "");
 
   execute(ascii_set_item(key, "0"));
   for (int x= 1; x < 11; ++x)
@@ -1602,7 +1709,7 @@ static enum test_return test_ascii_incr_noreply(void)
 static enum test_return test_ascii_decr_impl(const char* key, bool noreply)
 {
   char cmd[300];
-  sprintf(cmd, "decr %s 1%s\r\n", key, noreply ? " noreply" : "");
+  snprintf(cmd, sizeof(cmd), "decr %s 1%s\r\n", key, noreply ? " noreply" : "");
 
   execute(ascii_set_item(key, "9"));
   for (int x= 8; x > -1; --x)
@@ -1653,7 +1760,7 @@ static enum test_return test_ascii_flush_impl(const char *key, bool noreply)
   /* Verify that the flush_all command handles unknown options */
   /* Bug in the current memcached server! */
   execute(send_string("flush_all foo bar\r\n"));
-  execute(receive_response("ERROR\r\n"));
+  execute(receive_error_response());
 #endif
 
   execute(ascii_set_item(key, key));
@@ -1704,10 +1811,10 @@ static enum test_return test_ascii_concat_impl(const char *key,
     value="hello";
 
   char cmd[400];
-  sprintf(cmd, "%s %s 0 0 %u%s\r\n%s\r\n",
-          append ? "append" : "prepend",
-          key, (unsigned int)strlen(value), noreply ? " noreply" : "",
-          value);
+  snprintf(cmd, sizeof(cmd), "%s %s 0 0 %u%s\r\n%s\r\n",
+           append ? "append" : "prepend",
+           key, (unsigned int)strlen(value), noreply ? " noreply" : "",
+           value);
   execute(send_string(cmd));
 
   if (noreply)
@@ -1717,10 +1824,10 @@ static enum test_return test_ascii_concat_impl(const char *key,
 
   execute(ascii_get_item(key, "hello world", true));
 
-  sprintf(cmd, "%s %s_notfound 0 0 %u%s\r\n%s\r\n",
-          append ? "append" : "prepend",
-          key, (unsigned int)strlen(value), noreply ? " noreply" : "",
-          value);
+  snprintf(cmd, sizeof(cmd), "%s %s_notfound 0 0 %u%s\r\n%s\r\n",
+           append ? "append" : "prepend",
+           key, (unsigned int)strlen(value), noreply ? " noreply" : "",
+           value);
   execute(send_string(cmd));
 
   if (noreply)
@@ -1754,7 +1861,7 @@ static enum test_return test_ascii_prepend_noreply(void)
 static enum test_return test_ascii_stat(void)
 {
   execute(send_string("stats noreply\r\n"));
-  execute(receive_response("ERROR\r\n"));
+  execute(receive_error_response());
   execute(send_string("stats\r\n"));
   char buffer[1024];
   do {
@@ -1827,28 +1934,49 @@ struct testcase testcases[]= {
   { "binary prepend", test_binary_prepend },
   { "binary prependq", test_binary_prependq },
   { "binary stat", test_binary_stat },
-  { "binary illegal", test_binary_illegal },
   { NULL, NULL}
 };
 
+const int ascii_tests = 1;
+const int binary_tests = 2;
+
+struct test_type_st
+{
+  bool ascii;
+  bool binary;
+};
+
 int main(int argc, char **argv)
 {
   static const char * const status_msg[]= {"[skip]", "[pass]", "[pass]", "[FAIL]"};
+  struct test_type_st tests= { true, true };
   int total= 0;
   int failed= 0;
   const char *hostname= "localhost";
   const char *port= "11211";
   int cmd;
+  bool prompt= false;
+  const char *testname= NULL;
 
-  while ((cmd= getopt(argc, argv, "t:vch:p:?")) != EOF)
+
+
+  while ((cmd= getopt(argc, argv, "t:vch:p:PT:?ab")) != EOF)
   {
     switch (cmd) {
+    case 'a':
+      tests.ascii= true;
+      tests.binary= false;
+      break;
+    case 'b':
+      tests.ascii= false;
+      tests.binary= true;
+      break;
     case 't':
       timeout= atoi(optarg);
       if (timeout == 0)
       {
         fprintf(stderr, "Invalid timeout. Please specify a number for -t\n");
-        return 1;
+        return EXIT_FAILURE;
       }
       break;
     case 'v': verbose= true;
@@ -1859,30 +1987,70 @@ int main(int argc, char **argv)
       break;
     case 'p': port= optarg;
       break;
+    case 'P': prompt= true;
+      break;
+    case 'T': testname= optarg;
+       break;
     default:
-      fprintf(stderr, "Usage: %s [-h hostname] [-p port] [-c] [-v] [-t n]\n"
+      fprintf(stderr, "Usage: %s [-h hostname] [-p port] [-c] [-v] [-t n] [-P] [-T testname]'\n"
               "\t-c\tGenerate coredump if a test fails\n"
               "\t-v\tVerbose test output (print out the assertion)\n"
-              "\t-t n\tSet the timeout for io-operations to n seconds\n",
+              "\t-t n\tSet the timeout for io-operations to n seconds\n"
+              "\t-P\tPrompt the user before starting a test.\n"
+              "\t\t\t\"skip\" will skip the test\n"
+              "\t\t\t\"quit\" will terminate memcapable\n"
+              "\t\t\tEverything else will start the test\n"
+              "\t-T n\tJust run the test named n\n"
+              "\t-a\tOnly test the ascii protocol\n"
+              "\t-b\tOnly test the binary protocol\n",
               argv[0]);
-      return 1;
+      return EXIT_FAILURE;
     }
   }
 
+  initialize_sockets();
   sock= connect_server(hostname, port);
-  if (sock == -1)
+  if (sock == INVALID_SOCKET)
   {
     fprintf(stderr, "Failed to connect to <%s:%s>: %s\n",
-            hostname, port, strerror(errno));
-    return 1;
+            hostname, port, strerror(get_socket_errno()));
+    return EXIT_FAILURE;
   }
 
   for (int ii= 0; testcases[ii].description != NULL; ++ii)
   {
+    if (testname != NULL && strcmp(testcases[ii].description, testname) != 0)
+       continue;
+
+    if ((testcases[ii].description[0] == 'a' && (tests.ascii) == 0) ||
+        (testcases[ii].description[0] == 'b' && (tests.binary) == 0))
+    {
+      continue;
+    }
     ++total;
     fprintf(stdout, "%-40s", testcases[ii].description);
     fflush(stdout);
 
+    if (prompt)
+    {
+      fprintf(stdout, "\nPress <return> when you are ready? ");
+      char buffer[80] = {0};
+      if (fgets(buffer, sizeof(buffer), stdin) != NULL) {
+        if (strncmp(buffer, "skip", 4) == 0)
+        {
+          fprintf(stdout, "%-40s%s\n", testcases[ii].description,
+                  status_msg[TEST_SKIP]);
+          fflush(stdout);
+          continue;
+        }
+        if (strncmp(buffer, "quit", 4) == 0)
+          exit(0);
+      }
+
+      fprintf(stdout, "%-40s", testcases[ii].description);
+      fflush(stdout);
+    }
+
     bool reconnect= false;
     enum test_return ret= testcases[ii].function();
     if (ret == TEST_FAIL)
@@ -1898,18 +2066,18 @@ int main(int argc, char **argv)
     fprintf(stderr, "%s\n", status_msg[ret]);
     if (reconnect)
     {
-      (void) close(sock);
-      if ((sock=connect_server(hostname, port)) == -1)
+      closesocket(sock);
+      if ((sock= connect_server(hostname, port)) == INVALID_SOCKET)
       {
         fprintf(stderr, "Failed to connect to <%s:%s>: %s\n",
-                hostname, port, strerror(errno));
+                hostname, port, strerror(get_socket_errno()));
         fprintf(stderr, "%d of %d tests failed\n", failed, total);
-        return 1;
+        return EXIT_FAILURE;
       }
     }
   }
 
-  (void) close(sock);
+  closesocket(sock);
   if (failed == 0)
     fprintf(stdout, "All tests passed\n");
   else