/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */ #undef NDEBUG #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "cache.h" #include "util.h" #include "protocol_binary.h" #define TMP_TEMPLATE "/tmp/test_file.XXXXXXX" enum test_return { TEST_SKIP, TEST_PASS, TEST_FAIL }; static pid_t server_pid; static in_port_t port; static int sock; static bool allow_closed_read = false; static enum test_return cache_create_test(void) { cache_t *cache = cache_create("test", sizeof(uint32_t), sizeof(char*), NULL, NULL); assert(cache != NULL); cache_destroy(cache); return TEST_PASS; } const uint64_t constructor_pattern = 0xdeadcafebabebeef; static int cache_constructor(void *buffer, void *notused1, int notused2) { uint64_t *ptr = buffer; *ptr = constructor_pattern; return 0; } static enum test_return cache_constructor_test(void) { cache_t *cache = cache_create("test", sizeof(uint64_t), sizeof(uint64_t), cache_constructor, NULL); assert(cache != NULL); uint64_t *ptr = cache_alloc(cache); uint64_t pattern = *ptr; cache_free(cache, ptr); cache_destroy(cache); return (pattern == constructor_pattern) ? TEST_PASS : TEST_FAIL; } static int cache_fail_constructor(void *buffer, void *notused1, int notused2) { return 1; } static enum test_return cache_fail_constructor_test(void) { enum test_return ret = TEST_PASS; cache_t *cache = cache_create("test", sizeof(uint64_t), sizeof(uint64_t), cache_fail_constructor, NULL); assert(cache != NULL); uint64_t *ptr = cache_alloc(cache); if (ptr != NULL) { ret = TEST_FAIL; } cache_destroy(cache); return ret; } static void *destruct_data = 0; static void cache_destructor(void *buffer, void *notused) { destruct_data = buffer; } static enum test_return cache_destructor_test(void) { cache_t *cache = cache_create("test", sizeof(uint32_t), sizeof(char*), NULL, cache_destructor); assert(cache != NULL); char *ptr = cache_alloc(cache); cache_free(cache, ptr); cache_destroy(cache); return (ptr == destruct_data) ? TEST_PASS : TEST_FAIL; } static enum test_return cache_reuse_test(void) { int ii; cache_t *cache = cache_create("test", sizeof(uint32_t), sizeof(char*), NULL, NULL); char *ptr = cache_alloc(cache); cache_free(cache, ptr); for (ii = 0; ii < 100; ++ii) { char *p = cache_alloc(cache); assert(p == ptr); cache_free(cache, ptr); } cache_destroy(cache); return TEST_PASS; } static enum test_return cache_bulkalloc(size_t datasize) { cache_t *cache = cache_create("test", datasize, sizeof(char*), NULL, NULL); #define ITERATIONS 1024 void *ptr[ITERATIONS]; for (int ii = 0; ii < ITERATIONS; ++ii) { ptr[ii] = cache_alloc(cache); assert(ptr[ii] != 0); memset(ptr[ii], 0xff, datasize); } for (int ii = 0; ii < ITERATIONS; ++ii) { cache_free(cache, ptr[ii]); } #undef ITERATIONS cache_destroy(cache); return TEST_PASS; } static enum test_return test_issue_161(void) { enum test_return ret = cache_bulkalloc(1); if (ret == TEST_PASS) { ret = cache_bulkalloc(512); } return ret; } static enum test_return cache_redzone_test(void) { #ifndef HAVE_UMEM_H cache_t *cache = cache_create("test", sizeof(uint32_t), sizeof(char*), NULL, NULL); /* Ignore SIGABORT */ struct sigaction old_action; struct sigaction action = { .sa_handler = SIG_IGN, .sa_flags = 0}; sigemptyset(&action.sa_mask); sigaction(SIGABRT, &action, &old_action); /* check memory debug.. */ char *p = cache_alloc(cache); char old = *(p - 1); *(p - 1) = 0; cache_free(cache, p); assert(cache_error == -1); *(p - 1) = old; p[sizeof(uint32_t)] = 0; cache_free(cache, p); assert(cache_error == 1); /* restore signal handler */ sigaction(SIGABRT, &old_action, NULL); cache_destroy(cache); return TEST_PASS; #else return TEST_SKIP; #endif } static enum test_return test_safe_strtoul(void) { uint32_t val; assert(safe_strtoul("123", &val)); assert(val == 123); assert(safe_strtoul("+123", &val)); assert(val == 123); assert(!safe_strtoul("", &val)); // empty assert(!safe_strtoul("123BOGUS", &val)); // non-numeric assert(!safe_strtoul(" issue221", &val)); // non-numeric /* Not sure what it does, but this works with ICC :/ assert(!safe_strtoul("92837498237498237498029383", &val)); // out of range */ // extremes: assert(safe_strtoul("4294967295", &val)); // 2**32 - 1 assert(val == 4294967295L); /* This actually works on 64-bit ubuntu assert(!safe_strtoul("4294967296", &val)); // 2**32 */ assert(!safe_strtoul("-1", &val)); // negative return TEST_PASS; } static enum test_return test_safe_strtoull(void) { uint64_t val; assert(safe_strtoull("123", &val)); assert(val == 123); assert(safe_strtoull("+123", &val)); assert(val == 123); assert(!safe_strtoull("", &val)); // empty assert(!safe_strtoull("123BOGUS", &val)); // non-numeric assert(!safe_strtoull("92837498237498237498029383", &val)); // out of range assert(!safe_strtoull(" issue221", &val)); // non-numeric // extremes: assert(safe_strtoull("18446744073709551615", &val)); // 2**64 - 1 assert(val == 18446744073709551615ULL); assert(!safe_strtoull("18446744073709551616", &val)); // 2**64 assert(!safe_strtoull("-1", &val)); // negative return TEST_PASS; } static enum test_return test_safe_strtoll(void) { int64_t val; assert(safe_strtoll("123", &val)); assert(val == 123); assert(safe_strtoll("+123", &val)); assert(val == 123); assert(safe_strtoll("-123", &val)); assert(val == -123); assert(!safe_strtoll("", &val)); // empty assert(!safe_strtoll("123BOGUS", &val)); // non-numeric assert(!safe_strtoll("92837498237498237498029383", &val)); // out of range assert(!safe_strtoll(" issue221", &val)); // non-numeric // extremes: assert(!safe_strtoll("18446744073709551615", &val)); // 2**64 - 1 assert(safe_strtoll("9223372036854775807", &val)); // 2**63 - 1 assert(val == 9223372036854775807LL); /* assert(safe_strtoll("-9223372036854775808", &val)); // -2**63 assert(val == -9223372036854775808LL); */ assert(!safe_strtoll("-9223372036854775809", &val)); // -2**63 - 1 // We'll allow space to terminate the string. And leading space. assert(safe_strtoll(" 123 foo", &val)); assert(val == 123); return TEST_PASS; } static enum test_return test_safe_strtol(void) { int32_t val; assert(safe_strtol("123", &val)); assert(val == 123); assert(safe_strtol("+123", &val)); assert(val == 123); assert(safe_strtol("-123", &val)); assert(val == -123); assert(!safe_strtol("", &val)); // empty assert(!safe_strtol("123BOGUS", &val)); // non-numeric assert(!safe_strtol("92837498237498237498029383", &val)); // out of range assert(!safe_strtol(" issue221", &val)); // non-numeric // extremes: /* This actually works on 64-bit ubuntu assert(!safe_strtol("2147483648", &val)); // (expt 2.0 31.0) */ assert(safe_strtol("2147483647", &val)); // (- (expt 2.0 31) 1) assert(val == 2147483647L); /* This actually works on 64-bit ubuntu assert(!safe_strtol("-2147483649", &val)); // (- (expt -2.0 31) 1) */ // We'll allow space to terminate the string. And leading space. assert(safe_strtol(" 123 foo", &val)); assert(val == 123); return TEST_PASS; } /** * Function to start the server and let it listen on a random port * * @param port_out where to store the TCP port number the server is * listening on * @param daemon set to true if you want to run the memcached server * as a daemon process * @return the pid of the memcached server */ static pid_t start_server(in_port_t *port_out, bool daemon, int timeout) { char environment[80]; snprintf(environment, sizeof(environment), "MEMCACHED_PORT_FILENAME=/tmp/ports.%lu", (long)getpid()); char *filename= environment + strlen("MEMCACHED_PORT_FILENAME="); char pid_file[80]; snprintf(pid_file, sizeof(pid_file), "/tmp/pid.%lu", (long)getpid()); remove(filename); remove(pid_file); #ifdef __sun /* I want to name the corefiles differently so that they don't overwrite each other */ char coreadm[128]; snprintf(coreadm, sizeof(coreadm), "coreadm -p core.%%f.%%p %lu", (unsigned long)getpid()); system(coreadm); #endif pid_t pid = fork(); assert(pid != -1); if (pid == 0) { /* Child */ char *argv[20]; int arg = 0; char tmo[24]; snprintf(tmo, sizeof(tmo), "%u", timeout); putenv(environment); #ifdef __sun putenv("LD_PRELOAD=watchmalloc.so.1"); putenv("MALLOC_DEBUG=WATCH"); #endif if (!daemon) { argv[arg++] = "./timedrun"; argv[arg++] = tmo; } argv[arg++] = "./memcached-debug"; argv[arg++] = "-p"; argv[arg++] = "-1"; argv[arg++] = "-U"; argv[arg++] = "0"; /* Handle rpmbuild and the like doing this as root */ if (getuid() == 0) { argv[arg++] = "-u"; argv[arg++] = "root"; } if (daemon) { argv[arg++] = "-d"; argv[arg++] = "-P"; argv[arg++] = pid_file; } #ifdef MESSAGE_DEBUG argv[arg++] = "-vvv"; #endif argv[arg++] = NULL; assert(execv(argv[0], argv) != -1); } /* Yeah just let us "busy-wait" for the file to be created ;-) */ while (access(filename, F_OK) == -1) { usleep(10); } FILE *fp = fopen(filename, "r"); if (fp == NULL) { fprintf(stderr, "Failed to open the file containing port numbers: %s\n", strerror(errno)); assert(false); } *port_out = (in_port_t)-1; char buffer[80]; while ((fgets(buffer, sizeof(buffer), fp)) != NULL) { if (strncmp(buffer, "TCP INET: ", 10) == 0) { int32_t val; assert(safe_strtol(buffer + 10, &val)); *port_out = (in_port_t)val; } } fclose(fp); assert(remove(filename) == 0); if (daemon) { /* loop and wait for the pid file.. There is a potential race * condition that the server just created the file but isn't * finished writing the content, but I'll take the chance.... */ while (access(pid_file, F_OK) == -1) { usleep(10); } fp = fopen(pid_file, "r"); if (fp == NULL) { fprintf(stderr, "Failed to open pid file: %s\n", strerror(errno)); assert(false); } assert(fgets(buffer, sizeof(buffer), fp) != NULL); fclose(fp); int32_t val; assert(safe_strtol(buffer, &val)); pid = (pid_t)val; } return pid; } static enum test_return test_issue_44(void) { in_port_t port; pid_t pid = start_server(&port, true, 15); assert(kill(pid, SIGHUP) == 0); sleep(1); assert(kill(pid, SIGTERM) == 0); return TEST_PASS; } static struct addrinfo *lookuphost(const char *hostname, in_port_t port) { struct addrinfo *ai = 0; struct addrinfo hints = { .ai_family = AF_UNSPEC, .ai_protocol = IPPROTO_TCP, .ai_socktype = SOCK_STREAM }; char service[NI_MAXSERV]; int error; (void)snprintf(service, NI_MAXSERV, "%d", port); if ((error = getaddrinfo(hostname, service, &hints, &ai)) != 0) { if (error != EAI_SYSTEM) { fprintf(stderr, "getaddrinfo(): %s\n", gai_strerror(error)); } else { perror("getaddrinfo()"); } } return ai; } static int connect_server(const char *hostname, in_port_t port, bool nonblock) { struct addrinfo *ai = lookuphost(hostname, port); int sock = -1; if (ai != NULL) { if ((sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) != -1) { if (connect(sock, ai->ai_addr, ai->ai_addrlen) == -1) { fprintf(stderr, "Failed to connect socket: %s\n", strerror(errno)); close(sock); sock = -1; } else if (nonblock) { int flags = fcntl(sock, F_GETFL, 0); if (flags < 0 || fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) { fprintf(stderr, "Failed to enable nonblocking mode: %s\n", strerror(errno)); close(sock); sock = -1; } } } else { fprintf(stderr, "Failed to create socket: %s\n", strerror(errno)); } freeaddrinfo(ai); } return sock; } static enum test_return test_vperror(void) { int rv = 0; int oldstderr = dup(STDERR_FILENO); char tmpl[sizeof(TMP_TEMPLATE)+1]; strncpy(tmpl, TMP_TEMPLATE, sizeof(TMP_TEMPLATE)+1); int newfile = mkstemp(tmpl); assert(newfile > 0); rv = dup2(newfile, STDERR_FILENO); assert(rv == STDERR_FILENO); rv = close(newfile); assert(rv == 0); errno = EIO; vperror("Old McDonald had a farm. %s", "EI EIO"); /* Restore stderr */ rv = dup2(oldstderr, STDERR_FILENO); assert(rv == STDERR_FILENO); /* Go read the file */ char buf[80] = { 0 }; FILE *efile = fopen(tmpl, "r"); assert(efile); char *prv = fgets(buf, sizeof(buf), efile); assert(prv); fclose(efile); unlink(tmpl); char expected[80] = { 0 }; snprintf(expected, sizeof(expected), "Old McDonald had a farm. EI EIO: %s\n", strerror(EIO)); /* fprintf(stderr, "\nExpected: ``%s''" "\nGot: ``%s''\n", expected, buf); */ return strcmp(expected, buf) == 0 ? TEST_PASS : TEST_FAIL; } static void send_ascii_command(const char *buf) { off_t offset = 0; const char* ptr = buf; size_t len = strlen(buf); do { ssize_t nw = write(sock, ptr + offset, len - offset); if (nw == -1) { if (errno != EINTR) { fprintf(stderr, "Failed to write: %s\n", strerror(errno)); abort(); } } else { offset += nw; } } while (offset < len); } /* * This is a dead slow single byte read, but it should only read out * _one_ response and I don't have an input buffer... The current * implementation only supports single-line responses, so if you want to use * it for get commands you need to implement that first ;-) */ static void read_ascii_response(char *buffer, size_t size) { off_t offset = 0; bool need_more = true; do { ssize_t nr = read(sock, buffer + offset, 1); if (nr == -1) { if (errno != EINTR) { fprintf(stderr, "Failed to read: %s\n", strerror(errno)); abort(); } } else { assert(nr == 1); if (buffer[offset] == '\n') { need_more = false; buffer[offset + 1] = '\0'; } offset += nr; assert(offset + 1 < size); } } while (need_more); } static enum test_return test_issue_92(void) { char buffer[1024]; close(sock); sock = connect_server("127.0.0.1", port, false); send_ascii_command("stats cachedump 1 0 0\r\n"); read_ascii_response(buffer, sizeof(buffer)); assert(strncmp(buffer, "END", strlen("END")) == 0); send_ascii_command("stats cachedump 200 0 0\r\n"); read_ascii_response(buffer, sizeof(buffer)); assert(strncmp(buffer, "CLIENT_ERROR", strlen("CLIENT_ERROR")) == 0); close(sock); sock = connect_server("127.0.0.1", port, false); return TEST_PASS; } static enum test_return test_issue_102(void) { char buffer[4096]; memset(buffer, ' ', sizeof(buffer)); buffer[sizeof(buffer) - 1] = '\0'; close(sock); sock = connect_server("127.0.0.1", port, false); send_ascii_command(buffer); /* verify that the server closed the connection */ assert(read(sock, buffer, sizeof(buffer)) == 0); close(sock); sock = connect_server("127.0.0.1", port, false); snprintf(buffer, sizeof(buffer), "gets "); size_t offset = 5; while (offset < 4000) { offset += snprintf(buffer + offset, sizeof(buffer) - offset, "%010u ", (unsigned int)offset); } send_ascii_command(buffer); usleep(250); send_ascii_command("\r\n"); char rsp[80]; read_ascii_response(rsp, sizeof(rsp)); assert(strncmp(rsp, "END", strlen("END")) == 0); buffer[3]= ' '; send_ascii_command(buffer); usleep(250); send_ascii_command("\r\n"); read_ascii_response(rsp, sizeof(rsp)); assert(strncmp(rsp, "END", strlen("END")) == 0); memset(buffer, ' ', sizeof(buffer)); int len = snprintf(buffer + 101, sizeof(buffer) - 101, "gets foo"); buffer[101 + len] = ' '; buffer[sizeof(buffer) - 1] = '\0'; send_ascii_command(buffer); /* verify that the server closed the connection */ assert(read(sock, buffer, sizeof(buffer)) == 0); close(sock); sock = connect_server("127.0.0.1", port, false); return TEST_PASS; } static enum test_return start_memcached_server(void) { server_pid = start_server(&port, false, 600); sock = connect_server("127.0.0.1", port, false); return TEST_PASS; } static enum test_return stop_memcached_server(void) { close(sock); assert(kill(server_pid, SIGTERM) == 0); return TEST_PASS; } static void safe_send(const void* buf, size_t len, bool hickup) { off_t offset = 0; const char* ptr = buf; #ifdef MESSAGE_DEBUG uint8_t val = *ptr; assert(val == (uint8_t)0x80); fprintf(stderr, "About to send %lu bytes:", (unsigned long)len); for (int ii = 0; ii < len; ++ii) { if (ii % 4 == 0) { fprintf(stderr, "\n "); } val = *(ptr + ii); fprintf(stderr, " 0x%02x", val); } fprintf(stderr, "\n"); usleep(500); #endif do { size_t num_bytes = len - offset; if (hickup) { if (num_bytes > 1024) { num_bytes = (rand() % 1023) + 1; } } ssize_t nw = write(sock, ptr + offset, num_bytes); if (nw == -1) { if (errno != EINTR) { fprintf(stderr, "Failed to write: %s\n", strerror(errno)); abort(); } } else { if (hickup) { usleep(100); } offset += nw; } } while (offset < len); } static bool safe_recv(void *buf, size_t len) { if (len == 0) { return true; } off_t offset = 0; do { ssize_t nr = read(sock, ((char*)buf) + offset, len - offset); if (nr == -1) { if (errno != EINTR) { fprintf(stderr, "Failed to read: %s\n", strerror(errno)); abort(); } } else { if (nr == 0 && allow_closed_read) { return false; } assert(nr != 0); offset += nr; } } while (offset < len); return true; } static bool safe_recv_packet(void *buf, size_t size) { protocol_binary_response_no_extras *response = buf; assert(size > sizeof(*response)); if (!safe_recv(response, sizeof(*response))) { return false; } response->message.header.response.keylen = ntohs(response->message.header.response.keylen); response->message.header.response.status = ntohs(response->message.header.response.status); response->message.header.response.bodylen = ntohl(response->message.header.response.bodylen); size_t len = sizeof(*response); char *ptr = buf; ptr += len; if (!safe_recv(ptr, response->message.header.response.bodylen)) { return false; } #ifdef MESSAGE_DEBUG usleep(500); ptr = buf; len += response->message.header.response.bodylen; uint8_t val = *ptr; assert(val == (uint8_t)0x81); fprintf(stderr, "Received %lu bytes:", (unsigned long)len); for (int ii = 0; ii < len; ++ii) { if (ii % 4 == 0) { fprintf(stderr, "\n "); } val = *(ptr + ii); fprintf(stderr, " 0x%02x", val); } fprintf(stderr, "\n"); #endif return true; } static off_t storage_command(char*buf, size_t bufsz, uint8_t cmd, const void* key, size_t keylen, const void* dta, size_t dtalen, uint32_t flags, uint32_t exp) { /* all of the storage commands use the same command layout */ protocol_binary_request_set *request = (void*)buf; assert(bufsz > sizeof(*request) + keylen + dtalen); memset(request, 0, sizeof(*request)); request->message.header.request.magic = PROTOCOL_BINARY_REQ; request->message.header.request.opcode = cmd; request->message.header.request.keylen = htons(keylen); request->message.header.request.extlen = 8; request->message.header.request.bodylen = htonl(keylen + 8 + dtalen); request->message.header.request.opaque = 0xdeadbeef; request->message.body.flags = flags; request->message.body.expiration = exp; off_t key_offset = sizeof(protocol_binary_request_no_extras) + 8; memcpy(buf + key_offset, key, keylen); if (dta != NULL) { memcpy(buf + key_offset + keylen, dta, dtalen); } return key_offset + keylen + dtalen; } static off_t raw_command(char* buf, size_t bufsz, uint8_t cmd, const void* key, size_t keylen, const void* dta, size_t dtalen) { /* all of the storage commands use the same command layout */ protocol_binary_request_no_extras *request = (void*)buf; assert(bufsz > sizeof(*request) + keylen + dtalen); memset(request, 0, sizeof(*request)); request->message.header.request.magic = PROTOCOL_BINARY_REQ; request->message.header.request.opcode = cmd; request->message.header.request.keylen = htons(keylen); request->message.header.request.bodylen = htonl(keylen + dtalen); request->message.header.request.opaque = 0xdeadbeef; off_t key_offset = sizeof(protocol_binary_request_no_extras); if (key != NULL) { memcpy(buf + key_offset, key, keylen); } if (dta != NULL) { memcpy(buf + key_offset + keylen, dta, dtalen); } return sizeof(*request) + keylen + dtalen; } static off_t flush_command(char* buf, size_t bufsz, uint8_t cmd, uint32_t exptime, bool use_extra) { protocol_binary_request_flush *request = (void*)buf; assert(bufsz > sizeof(*request)); memset(request, 0, sizeof(*request)); request->message.header.request.magic = PROTOCOL_BINARY_REQ; request->message.header.request.opcode = cmd; off_t size = sizeof(protocol_binary_request_no_extras); if (use_extra) { request->message.header.request.extlen = 4; request->message.body.expiration = htonl(exptime); request->message.header.request.bodylen = htonl(4); size += 4; } request->message.header.request.opaque = 0xdeadbeef; return size; } static off_t touch_command(char* buf, size_t bufsz, uint8_t cmd, const void* key, size_t keylen, uint32_t exptime) { protocol_binary_request_touch *request = (void*)buf; assert(bufsz > sizeof(*request)); memset(request, 0, sizeof(*request)); request->message.header.request.magic = PROTOCOL_BINARY_REQ; request->message.header.request.opcode = cmd; request->message.header.request.keylen = htons(keylen); request->message.header.request.extlen = 4; request->message.body.expiration = htonl(exptime); request->message.header.request.bodylen = htonl(keylen + 4); request->message.header.request.opaque = 0xdeadbeef; off_t key_offset = sizeof(protocol_binary_request_no_extras) + 4; memcpy(buf + key_offset, key, keylen); return sizeof(protocol_binary_request_no_extras) + 4 + keylen; } static off_t arithmetic_command(char* buf, size_t bufsz, uint8_t cmd, const void* key, size_t keylen, uint64_t delta, uint64_t initial, uint32_t exp) { protocol_binary_request_incr *request = (void*)buf; assert(bufsz > sizeof(*request) + keylen); memset(request, 0, sizeof(*request)); request->message.header.request.magic = PROTOCOL_BINARY_REQ; request->message.header.request.opcode = cmd; request->message.header.request.keylen = htons(keylen); request->message.header.request.extlen = 20; request->message.header.request.bodylen = htonl(keylen + 20); request->message.header.request.opaque = 0xdeadbeef; request->message.body.delta = htonll(delta); request->message.body.initial = htonll(initial); request->message.body.expiration = htonl(exp); off_t key_offset = sizeof(protocol_binary_request_no_extras) + 20; memcpy(buf + key_offset, key, keylen); return key_offset + keylen; } static void validate_response_header(protocol_binary_response_no_extras *response, uint8_t cmd, uint16_t status) { assert(response->message.header.response.magic == PROTOCOL_BINARY_RES); assert(response->message.header.response.opcode == cmd); assert(response->message.header.response.datatype == PROTOCOL_BINARY_RAW_BYTES); assert(response->message.header.response.status == status); assert(response->message.header.response.opaque == 0xdeadbeef); if (status == PROTOCOL_BINARY_RESPONSE_SUCCESS) { switch (cmd) { case PROTOCOL_BINARY_CMD_ADDQ: case PROTOCOL_BINARY_CMD_APPENDQ: case PROTOCOL_BINARY_CMD_DECREMENTQ: case PROTOCOL_BINARY_CMD_DELETEQ: case PROTOCOL_BINARY_CMD_FLUSHQ: case PROTOCOL_BINARY_CMD_INCREMENTQ: case PROTOCOL_BINARY_CMD_PREPENDQ: case PROTOCOL_BINARY_CMD_QUITQ: case PROTOCOL_BINARY_CMD_REPLACEQ: case PROTOCOL_BINARY_CMD_SETQ: assert("Quiet command shouldn't return on success" == NULL); default: break; } switch (cmd) { case PROTOCOL_BINARY_CMD_ADD: case PROTOCOL_BINARY_CMD_REPLACE: case PROTOCOL_BINARY_CMD_SET: case PROTOCOL_BINARY_CMD_APPEND: case PROTOCOL_BINARY_CMD_PREPEND: assert(response->message.header.response.keylen == 0); assert(response->message.header.response.extlen == 0); assert(response->message.header.response.bodylen == 0); assert(response->message.header.response.cas != 0); break; case PROTOCOL_BINARY_CMD_FLUSH: case PROTOCOL_BINARY_CMD_NOOP: case PROTOCOL_BINARY_CMD_QUIT: case PROTOCOL_BINARY_CMD_DELETE: assert(response->message.header.response.keylen == 0); assert(response->message.header.response.extlen == 0); assert(response->message.header.response.bodylen == 0); assert(response->message.header.response.cas == 0); break; case PROTOCOL_BINARY_CMD_DECREMENT: case PROTOCOL_BINARY_CMD_INCREMENT: assert(response->message.header.response.keylen == 0); assert(response->message.header.response.extlen == 0); assert(response->message.header.response.bodylen == 8); assert(response->message.header.response.cas != 0); break; case PROTOCOL_BINARY_CMD_STAT: assert(response->message.header.response.extlen == 0); /* key and value exists in all packets except in the terminating */ assert(response->message.header.response.cas == 0); break; case PROTOCOL_BINARY_CMD_VERSION: assert(response->message.header.response.keylen == 0); assert(response->message.header.response.extlen == 0); assert(response->message.header.response.bodylen != 0); assert(response->message.header.response.cas == 0); break; case PROTOCOL_BINARY_CMD_GET: case PROTOCOL_BINARY_CMD_GETQ: assert(response->message.header.response.keylen == 0); assert(response->message.header.response.extlen == 4); assert(response->message.header.response.cas != 0); break; case PROTOCOL_BINARY_CMD_GETK: case PROTOCOL_BINARY_CMD_GETKQ: assert(response->message.header.response.keylen != 0); assert(response->message.header.response.extlen == 4); assert(response->message.header.response.cas != 0); break; default: /* Undefined command code */ break; } } else { assert(response->message.header.response.cas == 0); assert(response->message.header.response.extlen == 0); if (cmd != PROTOCOL_BINARY_CMD_GETK && cmd != PROTOCOL_BINARY_CMD_GATK) { assert(response->message.header.response.keylen == 0); } } } static enum test_return test_binary_noop(void) { union { protocol_binary_request_no_extras request; protocol_binary_response_no_extras response; char bytes[1024]; } buffer; size_t len = raw_command(buffer.bytes, sizeof(buffer.bytes), PROTOCOL_BINARY_CMD_NOOP, NULL, 0, NULL, 0); safe_send(buffer.bytes, len, false); safe_recv_packet(buffer.bytes, sizeof(buffer.bytes)); validate_response_header(&buffer.response, PROTOCOL_BINARY_CMD_NOOP, PROTOCOL_BINARY_RESPONSE_SUCCESS); return TEST_PASS; } static enum test_return test_binary_quit_impl(uint8_t cmd) { union { protocol_binary_request_no_extras request; protocol_binary_response_no_extras response; char bytes[1024]; } buffer; size_t len = raw_command(buffer.bytes, sizeof(buffer.bytes), cmd, NULL, 0, NULL, 0); safe_send(buffer.bytes, len, false); if (cmd == PROTOCOL_BINARY_CMD_QUIT) { safe_recv_packet(buffer.bytes, sizeof(buffer.bytes)); validate_response_header(&buffer.response, PROTOCOL_BINARY_CMD_QUIT, PROTOCOL_BINARY_RESPONSE_SUCCESS); } /* Socket should be closed now, read should return 0 */ assert(read(sock, buffer.bytes, sizeof(buffer.bytes)) == 0); close(sock); sock = connect_server("127.0.0.1", port, false); return TEST_PASS; } static enum test_return test_binary_quit(void) { return test_binary_quit_impl(PROTOCOL_BINARY_CMD_QUIT); } static enum test_return test_binary_quitq(void) { return test_binary_quit_impl(PROTOCOL_BINARY_CMD_QUITQ); } static enum test_return test_binary_set_impl(const char *key, uint8_t cmd) { union { protocol_binary_request_no_extras request; protocol_binary_response_no_extras response; char bytes[1024]; } send, receive; uint64_t value = 0xdeadbeefdeadcafe; size_t len = storage_command(send.bytes, sizeof(send.bytes), cmd, key, strlen(key), &value, sizeof(value), 0, 0); /* Set should work over and over again */ int ii; for (ii = 0; ii < 10; ++ii) { safe_send(send.bytes, len, false); if (cmd == PROTOCOL_BINARY_CMD_SET) { safe_recv_packet(receive.bytes, sizeof(receive.bytes)); validate_response_header(&receive.response, cmd, PROTOCOL_BINARY_RESPONSE_SUCCESS); } } if (cmd == PROTOCOL_BINARY_CMD_SETQ) { return test_binary_noop(); } send.request.message.header.request.cas = receive.response.message.header.response.cas; safe_send(send.bytes, len, false); if (cmd == PROTOCOL_BINARY_CMD_SET) { safe_recv_packet(receive.bytes, sizeof(receive.bytes)); validate_response_header(&receive.response, cmd, PROTOCOL_BINARY_RESPONSE_SUCCESS); assert(receive.response.message.header.response.cas != send.request.message.header.request.cas); } else { return test_binary_noop(); } return TEST_PASS; } static enum test_return test_binary_set(void) { return test_binary_set_impl("test_binary_set", PROTOCOL_BINARY_CMD_SET); } static enum test_return test_binary_setq(void) { return test_binary_set_impl("test_binary_setq", PROTOCOL_BINARY_CMD_SETQ); } static enum test_return test_binary_add_impl(const char *key, uint8_t cmd) { uint64_t value = 0xdeadbeefdeadcafe; union { protocol_binary_request_no_extras request; protocol_binary_response_no_extras response; char bytes[1024]; } send, receive; size_t len = storage_command(send.bytes, sizeof(send.bytes), cmd, key, strlen(key), &value, sizeof(value), 0, 0); /* Add should only work the first time */ int ii; for (ii = 0; ii < 10; ++ii) { safe_send(send.bytes, len, false); if (ii == 0) { if (cmd == PROTOCOL_BINARY_CMD_ADD) { safe_recv_packet(receive.bytes, sizeof(receive.bytes)); validate_response_header(&receive.response, cmd, PROTOCOL_BINARY_RESPONSE_SUCCESS); } } else { safe_recv_packet(receive.bytes, sizeof(receive.bytes)); validate_response_header(&receive.response, cmd, PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS); } } return TEST_PASS; } static enum test_return test_binary_add(void) { return test_binary_add_impl("test_binary_add", PROTOCOL_BINARY_CMD_ADD); } static enum test_return test_binary_addq(void) { return test_binary_add_impl("test_binary_addq", PROTOCOL_BINARY_CMD_ADDQ); } static enum test_return test_binary_replace_impl(const char* key, uint8_t cmd) { uint64_t value = 0xdeadbeefdeadcafe; union { protocol_binary_request_no_extras request; protocol_binary_response_no_extras response; char bytes[1024]; } send, receive; size_t len = storage_command(send.bytes, sizeof(send.bytes), cmd, key, strlen(key), &value, sizeof(value), 0, 0); safe_send(send.bytes, len, false); safe_recv_packet(receive.bytes, sizeof(receive.bytes)); validate_response_header(&receive.response, cmd, PROTOCOL_BINARY_RESPONSE_KEY_ENOENT); len = storage_command(send.bytes, sizeof(send.bytes), PROTOCOL_BINARY_CMD_ADD, key, strlen(key), &value, sizeof(value), 0, 0); safe_send(send.bytes, len, false); safe_recv_packet(receive.bytes, sizeof(receive.bytes)); validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_ADD, PROTOCOL_BINARY_RESPONSE_SUCCESS); len = storage_command(send.bytes, sizeof(send.bytes), cmd, key, strlen(key), &value, sizeof(value), 0, 0); int ii; for (ii = 0; ii < 10; ++ii) { safe_send(send.bytes, len, false); if (cmd == PROTOCOL_BINARY_CMD_REPLACE) { safe_recv_packet(receive.bytes, sizeof(receive.bytes)); validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_REPLACE, PROTOCOL_BINARY_RESPONSE_SUCCESS); } } if (cmd == PROTOCOL_BINARY_CMD_REPLACEQ) { test_binary_noop(); } return TEST_PASS; } static enum test_return test_binary_replace(void) { return test_binary_replace_impl("test_binary_replace", PROTOCOL_BINARY_CMD_REPLACE); } static enum test_return test_binary_replaceq(void) { return test_binary_replace_impl("test_binary_replaceq", PROTOCOL_BINARY_CMD_REPLACEQ); } static enum test_return test_binary_delete_impl(const char *key, uint8_t cmd) { union { protocol_binary_request_no_extras request; protocol_binary_response_no_extras response; char bytes[1024]; } send, receive; size_t len = raw_command(send.bytes, sizeof(send.bytes), cmd, key, strlen(key), NULL, 0); safe_send(send.bytes, len, false); safe_recv_packet(receive.bytes, sizeof(receive.bytes)); validate_response_header(&receive.response, cmd, PROTOCOL_BINARY_RESPONSE_KEY_ENOENT); len = storage_command(send.bytes, sizeof(send.bytes), PROTOCOL_BINARY_CMD_ADD, key, strlen(key), NULL, 0, 0, 0); safe_send(send.bytes, len, false); safe_recv_packet(receive.bytes, sizeof(receive.bytes)); validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_ADD, PROTOCOL_BINARY_RESPONSE_SUCCESS); len = raw_command(send.bytes, sizeof(send.bytes), cmd, key, strlen(key), NULL, 0); safe_send(send.bytes, len, false); if (cmd == PROTOCOL_BINARY_CMD_DELETE) { safe_recv_packet(receive.bytes, sizeof(receive.bytes)); validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_DELETE, PROTOCOL_BINARY_RESPONSE_SUCCESS); } safe_send(send.bytes, len, false); safe_recv_packet(receive.bytes, sizeof(receive.bytes)); validate_response_header(&receive.response, cmd, PROTOCOL_BINARY_RESPONSE_KEY_ENOENT); return TEST_PASS; } static enum test_return test_binary_delete(void) { return test_binary_delete_impl("test_binary_delete", PROTOCOL_BINARY_CMD_DELETE); } static enum test_return test_binary_deleteq(void) { return test_binary_delete_impl("test_binary_deleteq", PROTOCOL_BINARY_CMD_DELETEQ); } static enum test_return test_binary_get_impl(const char *key, uint8_t cmd) { union { protocol_binary_request_no_extras request; protocol_binary_response_no_extras response; char bytes[1024]; } send, receive; size_t len = raw_command(send.bytes, sizeof(send.bytes), cmd, key, strlen(key), NULL, 0); safe_send(send.bytes, len, false); safe_recv_packet(receive.bytes, sizeof(receive.bytes)); validate_response_header(&receive.response, cmd, PROTOCOL_BINARY_RESPONSE_KEY_ENOENT); len = storage_command(send.bytes, sizeof(send.bytes), PROTOCOL_BINARY_CMD_ADD, key, strlen(key), NULL, 0, 0, 0); safe_send(send.bytes, len, false); safe_recv_packet(receive.bytes, sizeof(receive.bytes)); validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_ADD, PROTOCOL_BINARY_RESPONSE_SUCCESS); /* run a little pipeline test ;-) */ len = 0; int ii; for (ii = 0; ii < 10; ++ii) { union { protocol_binary_request_no_extras request; char bytes[1024]; } temp; size_t l = raw_command(temp.bytes, sizeof(temp.bytes), cmd, key, strlen(key), NULL, 0); memcpy(send.bytes + len, temp.bytes, l); len += l; } safe_send(send.bytes, len, false); for (ii = 0; ii < 10; ++ii) { safe_recv_packet(receive.bytes, sizeof(receive.bytes)); validate_response_header(&receive.response, cmd, PROTOCOL_BINARY_RESPONSE_SUCCESS); } return TEST_PASS; } static enum test_return test_binary_get(void) { return test_binary_get_impl("test_binary_get", PROTOCOL_BINARY_CMD_GET); } static enum test_return test_binary_getk(void) { return test_binary_get_impl("test_binary_getk", PROTOCOL_BINARY_CMD_GETK); } static enum test_return test_binary_getq_impl(const char *key, uint8_t cmd) { const char *missing = "test_binary_getq_missing"; union { protocol_binary_request_no_extras request; protocol_binary_response_no_extras response; char bytes[1024]; } send, temp, receive; size_t len = storage_command(send.bytes, sizeof(send.bytes), PROTOCOL_BINARY_CMD_ADD, key, strlen(key), NULL, 0, 0, 0); size_t len2 = raw_command(temp.bytes, sizeof(temp.bytes), cmd, missing, strlen(missing), NULL, 0); /* I need to change the first opaque so that I can separate the two * return packets */ temp.request.message.header.request.opaque = 0xfeedface; memcpy(send.bytes + len, temp.bytes, len2); len += len2; len2 = raw_command(temp.bytes, sizeof(temp.bytes), cmd, key, strlen(key), NULL, 0); memcpy(send.bytes + len, temp.bytes, len2); len += len2; safe_send(send.bytes, len, false); safe_recv_packet(receive.bytes, sizeof(receive.bytes)); validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_ADD, PROTOCOL_BINARY_RESPONSE_SUCCESS); /* The first GETQ shouldn't return anything */ safe_recv_packet(receive.bytes, sizeof(receive.bytes)); validate_response_header(&receive.response, cmd, PROTOCOL_BINARY_RESPONSE_SUCCESS); return TEST_PASS; } static enum test_return test_binary_getq(void) { return test_binary_getq_impl("test_binary_getq", PROTOCOL_BINARY_CMD_GETQ); } static enum test_return test_binary_getkq(void) { return test_binary_getq_impl("test_binary_getkq", PROTOCOL_BINARY_CMD_GETKQ); } static enum test_return test_binary_incr_impl(const char* key, uint8_t cmd) { union { protocol_binary_request_no_extras request; protocol_binary_response_no_extras response_header; protocol_binary_response_incr response; char bytes[1024]; } send, receive; size_t len = arithmetic_command(send.bytes, sizeof(send.bytes), cmd, key, strlen(key), 1, 0, 0); int ii; for (ii = 0; ii < 10; ++ii) { safe_send(send.bytes, len, false); if (cmd == PROTOCOL_BINARY_CMD_INCREMENT) { safe_recv_packet(receive.bytes, sizeof(receive.bytes)); validate_response_header(&receive.response_header, cmd, PROTOCOL_BINARY_RESPONSE_SUCCESS); assert(ntohll(receive.response.message.body.value) == ii); } } if (cmd == PROTOCOL_BINARY_CMD_INCREMENTQ) { test_binary_noop(); } return TEST_PASS; } static enum test_return test_binary_incr(void) { return test_binary_incr_impl("test_binary_incr", PROTOCOL_BINARY_CMD_INCREMENT); } static enum test_return test_binary_incrq(void) { return test_binary_incr_impl("test_binary_incrq", PROTOCOL_BINARY_CMD_INCREMENTQ); } static enum test_return test_binary_decr_impl(const char* key, uint8_t cmd) { union { protocol_binary_request_no_extras request; protocol_binary_response_no_extras response_header; protocol_binary_response_decr response; char bytes[1024]; } send, receive; size_t len = arithmetic_command(send.bytes, sizeof(send.bytes), cmd, key, strlen(key), 1, 9, 0); int ii; for (ii = 9; ii >= 0; --ii) { safe_send(send.bytes, len, false); if (cmd == PROTOCOL_BINARY_CMD_DECREMENT) { safe_recv_packet(receive.bytes, sizeof(receive.bytes)); validate_response_header(&receive.response_header, cmd, PROTOCOL_BINARY_RESPONSE_SUCCESS); assert(ntohll(receive.response.message.body.value) == ii); } } /* decr on 0 should not wrap */ safe_send(send.bytes, len, false); if (cmd == PROTOCOL_BINARY_CMD_DECREMENT) { safe_recv_packet(receive.bytes, sizeof(receive.bytes)); validate_response_header(&receive.response_header, cmd, PROTOCOL_BINARY_RESPONSE_SUCCESS); assert(ntohll(receive.response.message.body.value) == 0); } else { test_binary_noop(); } return TEST_PASS; } static enum test_return test_binary_decr(void) { return test_binary_decr_impl("test_binary_decr", PROTOCOL_BINARY_CMD_DECREMENT); } static enum test_return test_binary_decrq(void) { return test_binary_decr_impl("test_binary_decrq", PROTOCOL_BINARY_CMD_DECREMENTQ); } static enum test_return test_binary_version(void) { union { protocol_binary_request_no_extras request; protocol_binary_response_no_extras response; char bytes[1024]; } buffer; size_t len = raw_command(buffer.bytes, sizeof(buffer.bytes), PROTOCOL_BINARY_CMD_VERSION, NULL, 0, NULL, 0); safe_send(buffer.bytes, len, false); safe_recv_packet(buffer.bytes, sizeof(buffer.bytes)); validate_response_header(&buffer.response, PROTOCOL_BINARY_CMD_VERSION, PROTOCOL_BINARY_RESPONSE_SUCCESS); return TEST_PASS; } static enum test_return test_binary_flush_impl(const char *key, uint8_t cmd) { union { protocol_binary_request_no_extras request; protocol_binary_response_no_extras response; char bytes[1024]; } send, receive; size_t len = storage_command(send.bytes, sizeof(send.bytes), PROTOCOL_BINARY_CMD_ADD, key, strlen(key), NULL, 0, 0, 0); safe_send(send.bytes, len, false); safe_recv_packet(receive.bytes, sizeof(receive.bytes)); validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_ADD, PROTOCOL_BINARY_RESPONSE_SUCCESS); len = flush_command(send.bytes, sizeof(send.bytes), cmd, 2, true); safe_send(send.bytes, len, false); if (cmd == PROTOCOL_BINARY_CMD_FLUSH) { safe_recv_packet(receive.bytes, sizeof(receive.bytes)); validate_response_header(&receive.response, cmd, PROTOCOL_BINARY_RESPONSE_SUCCESS); } len = raw_command(send.bytes, sizeof(send.bytes), PROTOCOL_BINARY_CMD_GET, key, strlen(key), NULL, 0); safe_send(send.bytes, len, false); safe_recv_packet(receive.bytes, sizeof(receive.bytes)); validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_GET, PROTOCOL_BINARY_RESPONSE_SUCCESS); sleep(2); safe_send(send.bytes, len, false); safe_recv_packet(receive.bytes, sizeof(receive.bytes)); validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_GET, PROTOCOL_BINARY_RESPONSE_KEY_ENOENT); int ii; for (ii = 0; ii < 2; ++ii) { len = storage_command(send.bytes, sizeof(send.bytes), PROTOCOL_BINARY_CMD_ADD, key, strlen(key), NULL, 0, 0, 0); safe_send(send.bytes, len, false); safe_recv_packet(receive.bytes, sizeof(receive.bytes)); validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_ADD, PROTOCOL_BINARY_RESPONSE_SUCCESS); len = flush_command(send.bytes, sizeof(send.bytes), cmd, 0, ii == 0); safe_send(send.bytes, len, false); if (cmd == PROTOCOL_BINARY_CMD_FLUSH) { safe_recv_packet(receive.bytes, sizeof(receive.bytes)); validate_response_header(&receive.response, cmd, PROTOCOL_BINARY_RESPONSE_SUCCESS); } len = raw_command(send.bytes, sizeof(send.bytes), PROTOCOL_BINARY_CMD_GET, key, strlen(key), NULL, 0); safe_send(send.bytes, len, false); safe_recv_packet(receive.bytes, sizeof(receive.bytes)); validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_GET, PROTOCOL_BINARY_RESPONSE_KEY_ENOENT); } return TEST_PASS; } static enum test_return test_binary_flush(void) { return test_binary_flush_impl("test_binary_flush", PROTOCOL_BINARY_CMD_FLUSH); } static enum test_return test_binary_flushq(void) { return test_binary_flush_impl("test_binary_flushq", PROTOCOL_BINARY_CMD_FLUSHQ); } static enum test_return test_binary_concat_impl(const char *key, uint8_t cmd) { union { protocol_binary_request_no_extras request; protocol_binary_response_no_extras response; char bytes[1024]; } send, receive; const char *value = "world"; size_t len = raw_command(send.bytes, sizeof(send.bytes), cmd, key, strlen(key), value, strlen(value)); safe_send(send.bytes, len, false); safe_recv_packet(receive.bytes, sizeof(receive.bytes)); validate_response_header(&receive.response, cmd, PROTOCOL_BINARY_RESPONSE_NOT_STORED); len = storage_command(send.bytes, sizeof(send.bytes), PROTOCOL_BINARY_CMD_ADD, key, strlen(key), value, strlen(value), 0, 0); safe_send(send.bytes, len, false); safe_recv_packet(receive.bytes, sizeof(receive.bytes)); validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_ADD, PROTOCOL_BINARY_RESPONSE_SUCCESS); len = raw_command(send.bytes, sizeof(send.bytes), cmd, key, strlen(key), value, strlen(value)); safe_send(send.bytes, len, false); if (cmd == PROTOCOL_BINARY_CMD_APPEND || cmd == PROTOCOL_BINARY_CMD_PREPEND) { safe_recv_packet(receive.bytes, sizeof(receive.bytes)); validate_response_header(&receive.response, cmd, PROTOCOL_BINARY_RESPONSE_SUCCESS); } else { len = raw_command(send.bytes, sizeof(send.bytes), PROTOCOL_BINARY_CMD_NOOP, NULL, 0, NULL, 0); safe_send(send.bytes, len, false); safe_recv_packet(receive.bytes, sizeof(receive.bytes)); validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_NOOP, PROTOCOL_BINARY_RESPONSE_SUCCESS); } len = raw_command(send.bytes, sizeof(send.bytes), PROTOCOL_BINARY_CMD_GETK, key, strlen(key), NULL, 0); safe_send(send.bytes, len, false); safe_recv_packet(receive.bytes, sizeof(receive.bytes)); validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_GETK, PROTOCOL_BINARY_RESPONSE_SUCCESS); assert(receive.response.message.header.response.keylen == strlen(key)); assert(receive.response.message.header.response.bodylen == (strlen(key) + 2*strlen(value) + 4)); char *ptr = receive.bytes; ptr += sizeof(receive.response); ptr += 4; assert(memcmp(ptr, key, strlen(key)) == 0); ptr += strlen(key); assert(memcmp(ptr, value, strlen(value)) == 0); ptr += strlen(value); assert(memcmp(ptr, value, strlen(value)) == 0); return TEST_PASS; } static enum test_return test_binary_append(void) { return test_binary_concat_impl("test_binary_append", PROTOCOL_BINARY_CMD_APPEND); } static enum test_return test_binary_prepend(void) { return test_binary_concat_impl("test_binary_prepend", PROTOCOL_BINARY_CMD_PREPEND); } static enum test_return test_binary_appendq(void) { return test_binary_concat_impl("test_binary_appendq", PROTOCOL_BINARY_CMD_APPENDQ); } static enum test_return test_binary_prependq(void) { return test_binary_concat_impl("test_binary_prependq", PROTOCOL_BINARY_CMD_PREPENDQ); } static enum test_return test_binary_stat(void) { union { protocol_binary_request_no_extras request; protocol_binary_response_no_extras response; char bytes[1024]; } buffer; size_t len = raw_command(buffer.bytes, sizeof(buffer.bytes), PROTOCOL_BINARY_CMD_STAT, NULL, 0, NULL, 0); safe_send(buffer.bytes, len, false); do { safe_recv_packet(buffer.bytes, sizeof(buffer.bytes)); validate_response_header(&buffer.response, PROTOCOL_BINARY_CMD_STAT, PROTOCOL_BINARY_RESPONSE_SUCCESS); } while (buffer.response.message.header.response.keylen != 0); return TEST_PASS; } static enum test_return test_binary_illegal(void) { uint8_t cmd = 0x25; while (cmd != 0x00) { union { protocol_binary_request_no_extras request; protocol_binary_response_no_extras response; char bytes[1024]; } buffer; size_t len = raw_command(buffer.bytes, sizeof(buffer.bytes), cmd, NULL, 0, NULL, 0); safe_send(buffer.bytes, len, false); safe_recv_packet(buffer.bytes, sizeof(buffer.bytes)); validate_response_header(&buffer.response, cmd, PROTOCOL_BINARY_RESPONSE_UNKNOWN_COMMAND); ++cmd; } return TEST_PASS; } volatile bool hickup_thread_running; static void *binary_hickup_recv_verification_thread(void *arg) { protocol_binary_response_no_extras *response = malloc(65*1024); if (response != NULL) { while (safe_recv_packet(response, 65*1024)) { /* Just validate the packet format */ validate_response_header(response, response->message.header.response.opcode, response->message.header.response.status); } free(response); } hickup_thread_running = false; allow_closed_read = false; return NULL; } static enum test_return test_binary_pipeline_hickup_chunk(void *buffer, size_t buffersize) { off_t offset = 0; char *key[256]; uint64_t value = 0xfeedfacedeadbeef; while (hickup_thread_running && offset + sizeof(protocol_binary_request_no_extras) < buffersize) { union { protocol_binary_request_no_extras request; char bytes[65 * 1024]; } command; uint8_t cmd = (uint8_t)(rand() & 0xff); size_t len; size_t keylen = (rand() % 250) + 1; switch (cmd) { case PROTOCOL_BINARY_CMD_ADD: case PROTOCOL_BINARY_CMD_ADDQ: case PROTOCOL_BINARY_CMD_REPLACE: case PROTOCOL_BINARY_CMD_REPLACEQ: case PROTOCOL_BINARY_CMD_SET: case PROTOCOL_BINARY_CMD_SETQ: len = storage_command(command.bytes, sizeof(command.bytes), cmd, key, keylen , &value, sizeof(value), 0, 0); break; case PROTOCOL_BINARY_CMD_APPEND: case PROTOCOL_BINARY_CMD_APPENDQ: case PROTOCOL_BINARY_CMD_PREPEND: case PROTOCOL_BINARY_CMD_PREPENDQ: len = raw_command(command.bytes, sizeof(command.bytes), cmd, key, keylen, &value, sizeof(value)); break; case PROTOCOL_BINARY_CMD_FLUSH: case PROTOCOL_BINARY_CMD_FLUSHQ: len = raw_command(command.bytes, sizeof(command.bytes), cmd, NULL, 0, NULL, 0); break; case PROTOCOL_BINARY_CMD_NOOP: len = raw_command(command.bytes, sizeof(command.bytes), cmd, NULL, 0, NULL, 0); break; case PROTOCOL_BINARY_CMD_DELETE: case PROTOCOL_BINARY_CMD_DELETEQ: len = raw_command(command.bytes, sizeof(command.bytes), cmd, key, keylen, NULL, 0); break; case PROTOCOL_BINARY_CMD_DECREMENT: case PROTOCOL_BINARY_CMD_DECREMENTQ: case PROTOCOL_BINARY_CMD_INCREMENT: case PROTOCOL_BINARY_CMD_INCREMENTQ: len = arithmetic_command(command.bytes, sizeof(command.bytes), cmd, key, keylen, 1, 0, 0); break; case PROTOCOL_BINARY_CMD_VERSION: len = raw_command(command.bytes, sizeof(command.bytes), PROTOCOL_BINARY_CMD_VERSION, NULL, 0, NULL, 0); break; case PROTOCOL_BINARY_CMD_GET: case PROTOCOL_BINARY_CMD_GETK: case PROTOCOL_BINARY_CMD_GETKQ: case PROTOCOL_BINARY_CMD_GETQ: len = raw_command(command.bytes, sizeof(command.bytes), cmd, key, keylen, NULL, 0); break; case PROTOCOL_BINARY_CMD_TOUCH: case PROTOCOL_BINARY_CMD_GAT: case PROTOCOL_BINARY_CMD_GATQ: case PROTOCOL_BINARY_CMD_GATK: case PROTOCOL_BINARY_CMD_GATKQ: len = touch_command(command.bytes, sizeof(command.bytes), cmd, key, keylen, 10); break; case PROTOCOL_BINARY_CMD_STAT: len = raw_command(command.bytes, sizeof(command.bytes), PROTOCOL_BINARY_CMD_STAT, NULL, 0, NULL, 0); break; case PROTOCOL_BINARY_CMD_SASL_LIST_MECHS: case PROTOCOL_BINARY_CMD_SASL_AUTH: case PROTOCOL_BINARY_CMD_SASL_STEP: /* Ignoring SASL */ case PROTOCOL_BINARY_CMD_QUITQ: case PROTOCOL_BINARY_CMD_QUIT: /* I don't want to pass on the quit commands ;-) */ cmd |= 0xf0; /* FALLTHROUGH */ default: len = raw_command(command.bytes, sizeof(command.bytes), cmd, NULL, 0, NULL, 0); } if ((len + offset) < buffersize) { memcpy(((char*)buffer) + offset, command.bytes, len); offset += len; } else { break; } } safe_send(buffer, offset, true); return TEST_PASS; } static enum test_return test_binary_pipeline_hickup(void) { size_t buffersize = 65 * 1024; void *buffer = malloc(buffersize); int ii; pthread_t tid; int ret; allow_closed_read = true; hickup_thread_running = true; if ((ret = pthread_create(&tid, NULL, binary_hickup_recv_verification_thread, NULL)) != 0) { fprintf(stderr, "Can't create thread: %s\n", strerror(ret)); return TEST_FAIL; } /* Allow the thread to start */ usleep(250); srand((int)time(NULL)); for (ii = 0; ii < 2; ++ii) { test_binary_pipeline_hickup_chunk(buffer, buffersize); } /* send quitq to shut down the read thread ;-) */ size_t len = raw_command(buffer, buffersize, PROTOCOL_BINARY_CMD_QUITQ, NULL, 0, NULL, 0); safe_send(buffer, len, false); pthread_join(tid, NULL); free(buffer); return TEST_PASS; } static enum test_return test_issue_101(void) { const int max = 2; enum test_return ret = TEST_PASS; int fds[max]; int ii = 0; pid_t child = 0; if (getenv("SKIP_TEST_101") != NULL) { return TEST_SKIP; } const char *command = "stats\r\nstats\r\nstats\r\nstats\r\nstats\r\n"; size_t cmdlen = strlen(command); server_pid = start_server(&port, false, 1000); for (ii = 0; ii < max; ++ii) { fds[ii] = connect_server("127.0.0.1", port, true); assert(fds[ii] > 0); } /* Send command on the connection until it blocks */ for (ii = 0; ii < max; ++ii) { bool more = true; do { ssize_t err = write(fds[ii], command, cmdlen); if (err == -1) { switch (errno) { case EINTR: break; case ENOMEM: case EWOULDBLOCK: more = false; break; default: ret = TEST_FAIL; goto cleanup; } } } while (more); } child = fork(); if (child == (pid_t)-1) { abort(); } else if (child > 0) { int stat; pid_t c; while ((c = waitpid(child, &stat, 0)) == (pid_t)-1 && errno == EINTR); assert(c == child); assert(stat == 0); } else { sock = connect_server("127.0.0.1", port, false); ret = test_binary_noop(); close(sock); exit(0); } cleanup: /* close all connections */ for (ii = 0; ii < max; ++ii) { close(fds[ii]); } assert(kill(server_pid, SIGTERM) == 0); return ret; } typedef enum test_return (*TEST_FUNC)(void); struct testcase { const char *description; TEST_FUNC function; }; struct testcase testcases[] = { { "cache_create", cache_create_test }, { "cache_constructor", cache_constructor_test }, { "cache_constructor_fail", cache_fail_constructor_test }, { "cache_destructor", cache_destructor_test }, { "cache_reuse", cache_reuse_test }, { "cache_redzone", cache_redzone_test }, { "issue_161", test_issue_161 }, { "strtol", test_safe_strtol }, { "strtoll", test_safe_strtoll }, { "strtoul", test_safe_strtoul }, { "strtoull", test_safe_strtoull }, { "issue_44", test_issue_44 }, { "vperror", test_vperror }, { "issue_101", test_issue_101 }, /* The following tests all run towards the same server */ { "start_server", start_memcached_server }, { "issue_92", test_issue_92 }, { "issue_102", test_issue_102 }, { "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_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_flush", test_binary_flush }, { "binary_flushq", test_binary_flushq }, { "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 }, { "binary_illegal", test_binary_illegal }, { "binary_pipeline_hickup", test_binary_pipeline_hickup }, { "stop_server", stop_memcached_server }, { NULL, NULL } }; int main(int argc, char **argv) { int exitcode = 0; int ii = 0, num_cases = 0; for (num_cases = 0; testcases[num_cases].description; num_cases++) { /* Just counting */ } printf("1..%d\n", num_cases); for (ii = 0; testcases[ii].description != NULL; ++ii) { fflush(stdout); #ifndef DEBUG /* the test program shouldn't run longer than 10 minutes... */ alarm(600); #endif enum test_return ret = testcases[ii].function(); if (ret == TEST_SKIP) { fprintf(stdout, "ok # SKIP %d - %s\n", ii + 1, testcases[ii].description); } else if (ret == TEST_PASS) { fprintf(stdout, "ok %d - %s\n", ii + 1, testcases[ii].description); } else { fprintf(stdout, "not ok %d - %s\n", ii + 1, testcases[ii].description); exitcode = 1; } fflush(stdout); } return exitcode; }