Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

1968 lines (1690 sloc) 66.573 kb
/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
#undef NDEBUG
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <fcntl.h>
#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++] = "-A";
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, so we loop a few times
* reading as well */
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);
}
/* Avoid race by retrying 20 times */
for (int x = 0; x < 20 && fgets(buffer, sizeof(buffer), fp) == NULL; x++) {
usleep(10);
}
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);
if (server_pid != -1) {
assert(kill(server_pid, SIGTERM) == 0);
}
return TEST_PASS;
}
static enum test_return shutdown_memcached_server(void) {
char buffer[1024];
close(sock);
sock = connect_server("127.0.0.1", port, false);
send_ascii_command("shutdown\r\n");
/* verify that the server closed the connection */
assert(read(sock, buffer, sizeof(buffer)) == 0);
close(sock);
/* We set server_pid to -1 so that we don't later call kill() */
if (kill(server_pid, 0) == 0) {
server_pid = -1;
}
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) {
enum { 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 },
{ "shutdown", shutdown_memcached_server },
{ "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;
}
Jump to Line
Something went wrong with that request. Please try again.