+/* 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
/* 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;
* 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)
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;
}
* @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);
}
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) {
- struct pollfd fds = {
+ 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)
{
- errno = ETIMEDOUT;
+ errno= ETIMEDOUT;
}
else
{
if (!val)
{
if (verbose)
- fprintf(stderr, "%s:%u: %s\n", file, line, expression);
+ fprintf(stderr, "\n%s:%d: %s", file, line, expression);
if (do_core)
abort();
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);
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;
*/
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=
* @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,
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;
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);
* @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,
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;
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);
* @param cc the expected command
* @param status the expected status
*/
-static enum test_return validate_response_header(response *rsp,
- uint8_t cc, uint16_t status)
+static enum test_return do_validate_response_header(response *rsp,
+ uint8_t cc, uint16_t status)
{
verify(rsp->plain.message.header.response.magic == PROTOCOL_BINARY_RES);
verify(rsp->plain.message.header.response.opcode == cc);
return TEST_PASS;
}
-static enum test_return test_binary_noop(void)
+/* We call verify(validate_response_header), but that macro
+ * expects a boolean expression, and the function returns
+ * an enum.... Let's just create a macro to avoid cluttering
+ * the code with all of the == TEST_PASS ;-)
+ */
+#define validate_response_header(a,b,c) \
+ do_validate_response_header(a,b,c) == TEST_PASS
+
+
+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;
execute(test_binary_noop());
}
+ /*
+ * We need to get the current CAS id, and at this time we haven't
+ * verified that we have a working get
+ */
+ if (cc == PROTOCOL_BINARY_CMD_SETQ)
+ {
+ cmd.set.message.header.request.opcode= PROTOCOL_BINARY_CMD_SET;
+ execute(resend_packet(&cmd));
+ execute(recv_packet(&rsp));
+ verify(validate_response_header(&rsp, PROTOCOL_BINARY_CMD_SET,
+ PROTOCOL_BINARY_RESPONSE_SUCCESS));
+ cmd.set.message.header.request.opcode= PROTOCOL_BINARY_CMD_SETQ;
+ }
+
/* try to set with the correct CAS value */
cmd.plain.message.header.request.cas=
htonll(rsp.plain.message.header.response.cas);
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)
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
return test_binary_add_impl("test_binary_addq", PROTOCOL_BINARY_CMD_ADDQ);
}
-static enum test_return set_item(const char *key, const char *value)
+static enum test_return binary_set_item(const char *key, const char *value)
{
command cmd;
response rsp;
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)
- execute(set_item(key, key));
+ execute(binary_set_item(key, key));
}
else
execute(test_binary_noop());
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;
/* 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(set_item(key, key));
+ execute(receive_binary_noop());
+ execute(binary_set_item(key, key));
/* The item should be present now, resend*/
execute(resend_packet(&cmd));
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(set_item(key, key));
+ 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;
}
for (int ii= 0; ii < 2; ++ii)
{
- execute(set_item(key, key));
+ execute(binary_set_item(key, key));
flush_command(&cmd, cc, 0, ii == 0);
execute(send_packet(&cmd));
else
value=" world";
- execute(set_item(key, value));
+ execute(binary_set_item(key, value));
if (cc == PROTOCOL_BINARY_CMD_APPEND || cc == PROTOCOL_BINARY_CMD_APPENDQ)
value=" world";
return TEST_PASS;
}
-static enum test_return test_binary_illegal(void)
+static enum test_return send_string(const char *cmd)
{
- command cmd;
- response rsp;
- uint8_t cc= 0x1b;
+ execute(retry_write(cmd, strlen(cmd)));
+ return TEST_PASS;
+}
- while (cc != 0x00)
+static enum test_return receive_line(char *buffer, size_t size)
+{
+ size_t offset= 0;
+ while (offset < size)
{
- 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;
+ execute(retry_read(buffer + offset, 1));
+ if (buffer[offset] == '\n')
+ {
+ if (offset + 1 < size)
+ {
+ buffer[offset + 1]= '\0';
+ return TEST_PASS;
+ }
+ else
+ return TEST_FAIL;
+ }
+ ++offset;
+ }
+
+ return TEST_FAIL;
+}
+
+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_error_response());
+
+ /* quit doesn't support noreply */
+ execute(send_string("quit noreply\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 */
+ char buffer[80];
+ verify(timeout_io_op(sock, POLLIN, buffer, sizeof(buffer)) == 0);
+ return TEST_PASS_RECONNECT;
+
+}
+
+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_error_response());
+
+ /* version doesn't support noreply */
+ execute(send_string("version noreply\r\n"));
+ execute(receive_error_response());
+
+ /* Verify that verify works */
+ execute(send_string("version\r\n"));
+ char buffer[256];
+ execute(receive_line(buffer, sizeof(buffer)));
+ verify(strncmp(buffer, "VERSION ", 8) == 0);
+
+ return TEST_PASS;
+}
+
+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_error_response());
+
+ execute(send_string("verbosity noreply\r\n"));
+ execute(receive_error_response());
+
+ execute(send_string("verbosity 0 noreply\r\n"));
+ execute(test_ascii_version());
+
+ execute(send_string("verbosity\r\n"));
+ execute(receive_error_response());
+
+ execute(send_string("verbosity 1\r\n"));
+ execute(receive_response("OK\r\n"));
+
+ execute(send_string("verbosity 0\r\n"));
+ execute(receive_response("OK\r\n"));
+
+ return TEST_PASS;
+}
+
+
+
+static enum test_return test_ascii_set_impl(const char* key, bool noreply)
+{
+ /* @todo add tests for bogus format! */
+ char buffer[1024];
+ snprintf(buffer, sizeof(buffer), "set %s 0 0 5%s\r\nvalue\r\n", key, noreply ? " noreply" : "");
+ execute(send_string(buffer));
+
+ if (!noreply)
+ execute(receive_response("STORED\r\n"));
+
+ return test_ascii_version();
+}
+
+static enum test_return test_ascii_set(void)
+{
+ return test_ascii_set_impl("test_ascii_set", false);
+}
+
+static enum test_return test_ascii_set_noreply(void)
+{
+ return test_ascii_set_impl("test_ascii_set_noreply", true);
+}
+
+static enum test_return test_ascii_add_impl(const char* key, bool noreply)
+{
+ /* @todo add tests for bogus format! */
+ char buffer[1024];
+ snprintf(buffer, sizeof(buffer), "add %s 0 0 5%s\r\nvalue\r\n", key, noreply ? " noreply" : "");
+ execute(send_string(buffer));
+
+ if (!noreply)
+ execute(receive_response("STORED\r\n"));
+
+ execute(send_string(buffer));
+
+ if (!noreply)
+ execute(receive_response("NOT_STORED\r\n"));
+
+ return test_ascii_version();
+}
+
+static enum test_return test_ascii_add(void)
+{
+ return test_ascii_add_impl("test_ascii_add", false);
+}
+
+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)
+{
+
+ char buffer[1024];
+ size_t datasize= strlen(value);
+
+ verify(datasize < sizeof(buffer));
+ execute(receive_line(buffer, sizeof(buffer)));
+ verify(strncmp(buffer, "VALUE ", 6) == 0);
+ verify(strncmp(buffer + 6, key, strlen(key)) == 0);
+ char *ptr= buffer + 6 + strlen(key) + 1;
+ char *end;
+
+ unsigned long val= strtoul(ptr, &end, 10); /* flags */
+ verify(ptr != end);
+ verify(val == 0);
+ verify(end != NULL);
+ val= strtoul(end, &end, 10); /* size */
+ verify(ptr != end);
+ verify(val == datasize);
+ verify(end != NULL);
+ while (*end != '\n' && isspace(*end))
+ ++end;
+ verify(*end == '\n');
+
+ execute(retry_read(buffer, datasize));
+ verify(memcmp(buffer, value, datasize) == 0);
+
+ execute(retry_read(buffer, 2));
+ verify(memcmp(buffer, "\r\n", 2) == 0);
return TEST_PASS;
}
+static enum test_return ascii_get_item(const char *key, const char *value,
+ bool exist)
+{
+ char buffer[1024];
+ size_t datasize= 0;
+ if (value != NULL)
+ datasize= strlen(value);
+
+ verify(datasize < sizeof(buffer));
+ snprintf(buffer, sizeof(buffer), "get %s\r\n", key);
+ execute(send_string(buffer));
+
+ if (exist)
+ execute(ascii_get_value(key, value));
+
+ execute(retry_read(buffer, 5));
+ verify(memcmp(buffer, "END\r\n", 5) == 0);
+
+ return TEST_PASS;
+}
+
+static enum test_return ascii_gets_value(const char *key, const char *value,
+ unsigned long *cas)
+{
+
+ char buffer[1024];
+ size_t datasize= strlen(value);
+
+ verify(datasize < sizeof(buffer));
+ execute(receive_line(buffer, sizeof(buffer)));
+ verify(strncmp(buffer, "VALUE ", 6) == 0);
+ verify(strncmp(buffer + 6, key, strlen(key)) == 0);
+ char *ptr= buffer + 6 + strlen(key) + 1;
+ char *end;
+
+ unsigned long val= strtoul(ptr, &end, 10); /* flags */
+ verify(ptr != end);
+ verify(val == 0);
+ verify(end != NULL);
+ val= strtoul(end, &end, 10); /* size */
+ verify(ptr != end);
+ verify(val == datasize);
+ verify(end != NULL);
+ *cas= strtoul(end, &end, 10); /* cas */
+ verify(ptr != end);
+ verify(val == datasize);
+ verify(end != NULL);
+
+ while (*end != '\n' && isspace(*end))
+ ++end;
+ verify(*end == '\n');
+
+ execute(retry_read(buffer, datasize));
+ verify(memcmp(buffer, value, datasize) == 0);
+
+ execute(retry_read(buffer, 2));
+ verify(memcmp(buffer, "\r\n", 2) == 0);
+
+ return TEST_PASS;
+}
+
+static enum test_return ascii_gets_item(const char *key, const char *value,
+ bool exist, unsigned long *cas)
+{
+ char buffer[1024];
+ size_t datasize= 0;
+ if (value != NULL)
+ datasize= strlen(value);
+
+ verify(datasize < sizeof(buffer));
+ snprintf(buffer, sizeof(buffer), "gets %s\r\n", key);
+ execute(send_string(buffer));
+
+ if (exist)
+ execute(ascii_gets_value(key, value, cas));
+
+ execute(retry_read(buffer, 5));
+ verify(memcmp(buffer, "END\r\n", 5) == 0);
+
+ return TEST_PASS;
+}
+
+static enum test_return ascii_set_item(const char *key, const char *value)
+{
+ char buffer[300];
+ size_t len= strlen(value);
+ 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"));
+ execute(receive_response("STORED\r\n"));
+ return TEST_PASS;
+}
+
+static enum test_return test_ascii_replace_impl(const char* key, bool noreply)
+{
+ char buffer[1024];
+ snprintf(buffer, sizeof(buffer), "replace %s 0 0 5%s\r\nvalue\r\n", key, noreply ? " noreply" : "");
+ execute(send_string(buffer));
+
+ if (noreply)
+ execute(test_ascii_version());
+ else
+ execute(receive_response("NOT_STORED\r\n"));
+
+ execute(ascii_set_item(key, "value"));
+ execute(ascii_get_item(key, "value", true));
+
+
+ execute(send_string(buffer));
+
+ if (noreply)
+ execute(test_ascii_version());
+ else
+ execute(receive_response("STORED\r\n"));
+
+ return test_ascii_version();
+}
+
+static enum test_return test_ascii_replace(void)
+{
+ return test_ascii_replace_impl("test_ascii_replace", false);
+}
+
+static enum test_return test_ascii_replace_noreply(void)
+{
+ return test_ascii_replace_impl("test_ascii_replace_noreply", true);
+}
+
+static enum test_return test_ascii_cas_impl(const char* key, bool noreply)
+{
+ char buffer[1024];
+ unsigned long cas;
+
+ execute(ascii_set_item(key, "value"));
+ execute(ascii_gets_item(key, "value", true, &cas));
+
+ 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)
+ execute(test_ascii_version());
+ else
+ execute(receive_response("STORED\r\n"));
+
+ /* reexecute the same command should fail due to illegal cas */
+ execute(send_string(buffer));
+
+ if (noreply)
+ execute(test_ascii_version());
+ else
+ execute(receive_response("EXISTS\r\n"));
+
+ return test_ascii_version();
+}
+
+static enum test_return test_ascii_cas(void)
+{
+ return test_ascii_cas_impl("test_ascii_cas", false);
+}
+
+static enum test_return test_ascii_cas_noreply(void)
+{
+ return test_ascii_cas_impl("test_ascii_cas_noreply", true);
+}
+
+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_error_response());
+ /* BUG: the server accepts delete a b */
+ execute(send_string("delete a b c d e\r\n"));
+ execute(receive_error_response());
+
+ char buffer[1024];
+ snprintf(buffer, sizeof(buffer), "delete %s%s\r\n", key, noreply ? " noreply" : "");
+ execute(send_string(buffer));
+
+ if (noreply)
+ execute(test_ascii_version());
+ else
+ execute(receive_response("DELETED\r\n"));
+
+ execute(ascii_get_item(key, "value", false));
+ execute(send_string(buffer));
+ if (noreply)
+ execute(test_ascii_version());
+ else
+ execute(receive_response("NOT_FOUND\r\n"));
+
+ return TEST_PASS;
+}
+
+static enum test_return test_ascii_delete(void)
+{
+ return test_ascii_delete_impl("test_ascii_delete", false);
+}
+
+static enum test_return test_ascii_delete_noreply(void)
+{
+ return test_ascii_delete_impl("test_ascii_delete_noreply", true);
+}
+
+static enum test_return test_ascii_get(void)
+{
+ execute(ascii_set_item("test_ascii_get", "value"));
+
+ execute(send_string("get\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));
+
+ return TEST_PASS;
+}
+
+static enum test_return test_ascii_gets(void)
+{
+ execute(ascii_set_item("test_ascii_gets", "value"));
+
+ execute(send_string("gets\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));
+
+ return TEST_PASS;
+}
+
+static enum test_return test_ascii_mget(void)
+{
+ 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"));
+
+ 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);
+
+ /* 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];
+ 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)
+ {
+ execute(send_string(cmd));
+
+ if (noreply)
+ execute(test_ascii_version());
+ else
+ {
+ char buffer[80];
+ execute(receive_line(buffer, sizeof(buffer)));
+ int val= atoi(buffer);
+ verify(val == x);
+ }
+ }
+
+ execute(ascii_get_item(key, "10", true));
+
+ return TEST_PASS;
+}
+
+static enum test_return test_ascii_incr(void)
+{
+ return test_ascii_incr_impl("test_ascii_incr", false);
+}
+
+static enum test_return test_ascii_incr_noreply(void)
+{
+ return test_ascii_incr_impl("test_ascii_incr_noreply", true);
+}
+
+static enum test_return test_ascii_decr_impl(const char* key, bool noreply)
+{
+ char cmd[300];
+ 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)
+ {
+ execute(send_string(cmd));
+
+ if (noreply)
+ execute(test_ascii_version());
+ else
+ {
+ char buffer[80];
+ execute(receive_line(buffer, sizeof(buffer)));
+ int val= atoi(buffer);
+ verify(val == x);
+ }
+ }
+
+ execute(ascii_get_item(key, "0", true));
+
+ /* verify that it doesn't wrap */
+ execute(send_string(cmd));
+ if (noreply)
+ execute(test_ascii_version());
+ else
+ {
+ char buffer[80];
+ execute(receive_line(buffer, sizeof(buffer)));
+ }
+ execute(ascii_get_item(key, "0", true));
+
+ return TEST_PASS;
+}
+
+static enum test_return test_ascii_decr(void)
+{
+ return test_ascii_decr_impl("test_ascii_decr", false);
+}
+
+static enum test_return test_ascii_decr_noreply(void)
+{
+ return test_ascii_decr_impl("test_ascii_decr_noreply", true);
+}
+
+
+static enum test_return test_ascii_flush_impl(const char *key, bool noreply)
+{
+#if 0
+ /* 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_error_response());
+#endif
+
+ execute(ascii_set_item(key, key));
+ execute(ascii_get_item(key, key, true));
+
+ if (noreply)
+ {
+ execute(send_string("flush_all noreply\r\n"));
+ execute(test_ascii_version());
+ }
+ else
+ {
+ execute(send_string("flush_all\r\n"));
+ execute(receive_response("OK\r\n"));
+ }
+
+ execute(ascii_get_item(key, key, false));
+
+ return TEST_PASS;
+}
+
+static enum test_return test_ascii_flush(void)
+{
+ return test_ascii_flush_impl("test_ascii_flush", false);
+}
+
+static enum test_return test_ascii_flush_noreply(void)
+{
+ return test_ascii_flush_impl("test_ascii_flush_noreply", true);
+}
+
+static enum test_return test_ascii_concat_impl(const char *key,
+ bool append,
+ bool noreply)
+{
+ const char *value;
+
+ if (append)
+ value="hello";
+ else
+ value=" world";
+
+ execute(ascii_set_item(key, value));
+
+ if (append)
+ value=" world";
+ else
+ value="hello";
+
+ char cmd[400];
+ 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)
+ execute(test_ascii_version());
+ else
+ execute(receive_response("STORED\r\n"));
+
+ execute(ascii_get_item(key, "hello world", true));
+
+ 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)
+ execute(test_ascii_version());
+ else
+ execute(receive_response("NOT_STORED\r\n"));
+
+ return TEST_PASS;
+}
+
+static enum test_return test_ascii_append(void)
+{
+ return test_ascii_concat_impl("test_ascii_append", true, false);
+}
+
+static enum test_return test_ascii_prepend(void)
+{
+ return test_ascii_concat_impl("test_ascii_prepend", false, false);
+}
+
+static enum test_return test_ascii_append_noreply(void)
+{
+ return test_ascii_concat_impl("test_ascii_append_noreply", true, true);
+}
+
+static enum test_return test_ascii_prepend_noreply(void)
+{
+ return test_ascii_concat_impl("test_ascii_prepend_noreply", false, true);
+}
+
+static enum test_return test_ascii_stat(void)
+{
+ execute(send_string("stats noreply\r\n"));
+ execute(receive_error_response());
+ execute(send_string("stats\r\n"));
+ char buffer[1024];
+ do {
+ execute(receive_line(buffer, sizeof(buffer)));
+ } while (strcmp(buffer, "END\r\n") != 0);
+
+ return TEST_PASS_RECONNECT;
+}
+
typedef enum test_return(*TEST_FUNC)(void);
struct testcase
};
struct testcase testcases[]= {
- { "noop", test_binary_noop},
- { "quit", test_binary_quit},
- { "quitq", test_binary_quitq},
- { "set", test_binary_set},
- { "setq", test_binary_setq},
- { "flush", test_binary_flush},
- { "flushq", test_binary_flushq},
- { "add", test_binary_add},
- { "addq", test_binary_addq},
- { "replace", test_binary_replace},
- { "replaceq", test_binary_replaceq},
- { "delete", test_binary_delete},
- { "deleteq", test_binary_deleteq},
- { "get", test_binary_get},
- { "getq", test_binary_getq},
- { "getk", test_binary_getk},
- { "getkq", test_binary_getkq},
- { "incr", test_binary_incr},
- { "incrq", test_binary_incrq},
- { "decr", test_binary_decr},
- { "decrq", test_binary_decrq},
- { "version", test_binary_version},
- { "append", test_binary_append},
- { "appendq", test_binary_appendq},
- { "prepend", test_binary_prepend},
- { "prependq", test_binary_prependq},
- { "stat", test_binary_stat},
- { "illegal", test_binary_illegal},
+ { "ascii quit", test_ascii_quit },
+ { "ascii version", test_ascii_version },
+ { "ascii verbosity", test_ascii_verbosity },
+ { "ascii set", test_ascii_set },
+ { "ascii set noreply", test_ascii_set_noreply },
+ { "ascii get", test_ascii_get },
+ { "ascii gets", test_ascii_gets },
+ { "ascii mget", test_ascii_mget },
+ { "ascii flush", test_ascii_flush },
+ { "ascii flush noreply", test_ascii_flush_noreply },
+ { "ascii add", test_ascii_add },
+ { "ascii add noreply", test_ascii_add_noreply },
+ { "ascii replace", test_ascii_replace },
+ { "ascii replace noreply", test_ascii_replace_noreply },
+ { "ascii cas", test_ascii_cas },
+ { "ascii cas noreply", test_ascii_cas_noreply },
+ { "ascii delete", test_ascii_delete },
+ { "ascii delete noreply", test_ascii_delete_noreply },
+ { "ascii incr", test_ascii_incr },
+ { "ascii incr noreply", test_ascii_incr_noreply },
+ { "ascii decr", test_ascii_decr },
+ { "ascii decr noreply", test_ascii_decr_noreply },
+ { "ascii append", test_ascii_append },
+ { "ascii append noreply", test_ascii_append_noreply },
+ { "ascii prepend", test_ascii_prepend },
+ { "ascii prepend noreply", test_ascii_prepend_noreply },
+ { "ascii stat", test_ascii_stat },
+ { "binary noop", test_binary_noop },
+ { "binary quit", test_binary_quit },
+ { "binary quitq", test_binary_quitq },
+ { "binary set", test_binary_set },
+ { "binary setq", test_binary_setq },
+ { "binary flush", test_binary_flush },
+ { "binary flushq", test_binary_flushq },
+ { "binary add", test_binary_add },
+ { "binary addq", test_binary_addq },
+ { "binary replace", test_binary_replace },
+ { "binary replaceq", test_binary_replaceq },
+ { "binary delete", test_binary_delete },
+ { "binary deleteq", test_binary_deleteq },
+ { "binary get", test_binary_get },
+ { "binary getq", test_binary_getq },
+ { "binary getk", test_binary_getk },
+ { "binary getkq", test_binary_getkq },
+ { "binary incr", test_binary_incr },
+ { "binary incrq", test_binary_incrq },
+ { "binary decr", test_binary_decr },
+ { "binary decrq", test_binary_decrq },
+ { "binary version", test_binary_version },
+ { "binary append", test_binary_append },
+ { "binary appendq", test_binary_appendq },
+ { "binary prepend", test_binary_prepend },
+ { "binary prependq", test_binary_prependq },
+ { "binary stat", test_binary_stat },
{ 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)
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-c 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;
}
}
+ 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));
+ hostname, port, strerror(get_socket_errno()));
return 1;
}
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, "%s\t\t", testcases[ii].description);
+ 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();
- fprintf(stderr, "%s\n", status_msg[ret]);
if (ret == TEST_FAIL)
{
reconnect= true;
++failed;
+ if (verbose)
+ fprintf(stderr, "\n");
}
else if (ret == TEST_PASS_RECONNECT)
reconnect= true;
+ 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;
}
}
}
- (void) close(sock);
+ closesocket(sock);
if (failed == 0)
fprintf(stdout, "All tests passed\n");
else