X-Git-Url: https://git.m6w6.name/?a=blobdiff_plain;f=clients%2Fmemcapable.c;h=2527861739c00c3e7f618558944968c99433aa1f;hb=1c0e12077080d4cdbf5313daab562deab94c2b72;hp=7a2b514c27d401ddae78c82cfd09a50b9b1e06d4;hpb=c472631907d070a97d80df05ac3c42a99736a9b6;p=awesomized%2Flibmemcached diff --git a/clients/memcapable.c b/clients/memcapable.c index 7a2b514c..25278617 100644 --- a/clients/memcapable.c +++ b/clients/memcapable.c @@ -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 #include -#include -#include -#include -#include -#include #include #include #include @@ -18,10 +24,12 @@ #include #include #include -#include +#include +#include #include #include +#include "utilities.h" #ifdef linux /* /usr/include/netinet/in.h defines macros from ntohs() to _bswap_nn to @@ -36,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; @@ -100,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) @@ -115,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; } @@ -129,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); } @@ -158,32 +177,33 @@ 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) { - 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 { @@ -206,7 +226,7 @@ static enum test_return ensure(bool val, const char *expression, const char *fil 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(); @@ -233,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); @@ -283,7 +303,7 @@ 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); + verify(get_socket_errno() == EINTR || get_socket_errno() == EAGAIN); break; case 0: return TEST_FAIL; @@ -330,7 +350,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, @@ -339,7 +359,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; @@ -352,7 +372,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); @@ -423,7 +443,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, @@ -431,7 +451,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; @@ -442,7 +462,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); @@ -454,8 +474,8 @@ static void arithmetic_command(command *cmd, * @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); @@ -555,18 +575,39 @@ static enum test_return validate_response_header(response *rsp, 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; @@ -622,6 +663,20 @@ static enum test_return test_binary_set_impl(const char* key, uint8_t cc) 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); @@ -698,7 +753,7 @@ static enum test_return test_binary_addq(void) 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; @@ -739,7 +794,7 @@ static enum test_return test_binary_replace_impl(const char* key, uint8_t cc) 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()); @@ -788,7 +843,7 @@ static enum test_return test_binary_delete_impl(const char *key, uint8_t cc) execute(send_packet(&cmd)); execute(recv_packet(&rsp)); verify(validate_response_header(&rsp, cc, PROTOCOL_BINARY_RESPONSE_KEY_ENOENT)); - execute(set_item(key, key)); + execute(binary_set_item(key, key)); /* The item should be present now, resend*/ execute(resend_packet(&cmd)); @@ -820,19 +875,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(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; } @@ -971,7 +1030,7 @@ static enum test_return test_binary_flush_impl(const char *key, uint8_t cc) 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)); @@ -1014,7 +1073,7 @@ static enum test_return test_binary_concat_impl(const char *key, uint8_t cc) 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"; @@ -1080,24 +1139,662 @@ static enum test_return test_binary_stat(void) 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]; + sprintf(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]; + sprintf(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_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)); + sprintf(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)); + sprintf(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); + sprintf(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]; + sprintf(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)); + + sprintf(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]; + sprintf(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) +{ + 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")); + + 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 buffer[5]; + execute(retry_read(buffer, 5)); + verify(memcmp(buffer, "END\r\n", 5) == 0); + 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" : ""); + + 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]; + sprintf(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]; + sprintf(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)); + + 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); + 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 @@ -1107,34 +1804,60 @@ 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} }; @@ -1170,51 +1893,54 @@ int main(int argc, char **argv) fprintf(stderr, "Usage: %s [-h hostname] [-p port] [-c] [-v] [-t n]\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", 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) { ++total; - fprintf(stdout, "%s\t\t", testcases[ii].description); + 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