From: Brian Aker Date: Fri, 15 Jan 2010 00:35:02 +0000 (-0800) Subject: Merge Thomason's cork patch. X-Git-Tag: 0.40~102 X-Git-Url: https://git.m6w6.name/?a=commitdiff_plain;h=55b5455291498ef63c1c34976552d58708a74732;p=m6w6%2Flibmemcached Merge Thomason's cork patch. --- 55b5455291498ef63c1c34976552d58708a74732 diff --cc ChangeLog index 308aac83,cea8e72b..c324e485 --- a/ChangeLog +++ b/ChangeLog @@@ -1,29 -1,3 +1,31 @@@ +0.38 + ++ * Added MEMCACHED_BEHAVIOR_CORK. ++ + * memslap now creates a configuration file at ~/.memslap.cnf + + * memcached_purge() now calls any callbacks registered during get + execution. + +0.37 Mon Jan 11 16:29:57 PST 2010 + * Fixed build for libhashkit. + * Fixed install path regression. + * Modified RPM to strict check install. + * Added documentation for memcached_server_cursor(); + * Added memcached_servers_reset(). + * Modified memcached_st to remove dead cursor_server member. + +0.36 Wed Jan 6 18:23:50 PST 2010 + * Merged in new memslap utility. + * All of constants.h has been updated to match style (all old identifiers + continue to work). + * Added first pass for libhashkit. + * Updated test Framework/extended tests. + * Random read support during replication added. + * Modified use_sort so that the option can be applied to any distribution type. + * We removed the MEMCACHED_BEHAVIOR_KETAMA_COMPAT_MODE added in 0.35. Instead use + memcached_behavior_set_distribution(). + 0.35 Mon Nov 9 11:18:33 PST 2009 * Added support for by_key operations for inc/dec methods. * Added mget test to memslap. diff --cc docs/memcached_behavior.pod index f59150f1,dd718454..2e0240af --- a/docs/memcached_behavior.pod +++ b/docs/memcached_behavior.pod @@@ -209,15 -209,6 +209,37 @@@ This replication does not dedicate cert replicas in, but instead it will store the replicas together with all of the other objects (on the 'n' next servers specified in your server list). +=item MEMCACHED_BEHAVIOR_RANDOMIZE_REPLICA_READ + +Allows randomizing the replica reads starting point. Normally the read is +done from primary server and in case of miss the read is done from primary ++ 1, then primary + 2 all the way to 'n' replicas. If this option is set +on the starting point of the replica reads is randomized between the servers. +This allows distributing read load to multiple servers with the expense of +more write traffic. + ++=item MEMCACHED_BEHAVIOR_CORK ++ ++Enable TCP_CORK behavior. This is only available as an option Linux. ++MEMCACHED_NO_SERVERS is returned if no servers are available to test with. ++MEMCACHED_NOT_SUPPORTED is returned if we were not able to determine ++if support was available. All other responses then MEMCACHED_SUCCESS ++report an error of some sort. This behavior also enables ++MEMCACHED_BEHAVIOR_TCP_NODELAY when set. ++ ++ ++=item MEMCACHED_BEHAVIOR_SOCKET_SEND_SIZE ++ ++Find the current size of SO_SNDBUF. A value of 0 means either an error ++occured or no hosts were available. It is safe to assume system default ++if this occurs. ++ ++=item MEMCACHED_BEHAVIOR_SOCKET_RECV_SIZE ++ ++Find the current size of SO_RCVBUF. A value of 0 means either an error ++occured or no hosts were available. It is safe to assume system default ++if this occurs. ++ =back =head1 RETURN diff --cc libmemcached/behavior.c index 6d168860,00000000..01f5bd30 mode 100644,000000..100644 --- a/libmemcached/behavior.c +++ b/libmemcached/behavior.c @@@ -1,350 -1,0 +1,416 @@@ +/* LibMemcached + * Copyright (C) 2006-2009 Brian Aker + * All rights reserved. + * + * Use and distribution licensed under the BSD license. See + * the COPYING file in the parent directory for full text. + * + * Summary: Change the behavior of the memcached connection. + * + */ + +#include "common.h" +#include +#include +#include +#include + +static bool set_flag(uint64_t data) +{ + // Wordy :) + return data ? true : false; +} + +static memcached_return_t set_hash(memcached_hash_t *store, memcached_hash_t type) +{ +#ifndef HAVE_HSIEH_HASH + if (type == MEMCACHED_HASH_HSIEH) + return MEMCACHED_FAILURE; +#endif + if (type < MEMCACHED_HASH_MAX) + { + *store= type; + } + else + { + return MEMCACHED_FAILURE; + } + + return MEMCACHED_SUCCESS; +} +/* + This function is used to modify the behavior of running client. + + We quit all connections so we can reset the sockets. +*/ + +memcached_return_t memcached_behavior_set(memcached_st *ptr, + const memcached_behavior_t flag, + uint64_t data) +{ + switch (flag) + { + case MEMCACHED_BEHAVIOR_NUMBER_OF_REPLICAS: + ptr->number_of_replicas= (uint32_t)data; + break; + case MEMCACHED_BEHAVIOR_IO_MSG_WATERMARK: + ptr->io_msg_watermark= (uint32_t) data; + break; + case MEMCACHED_BEHAVIOR_IO_BYTES_WATERMARK: + ptr->io_bytes_watermark= (uint32_t)data; + break; + case MEMCACHED_BEHAVIOR_IO_KEY_PREFETCH: + ptr->io_key_prefetch = (uint32_t)data; + break; + case MEMCACHED_BEHAVIOR_SND_TIMEOUT: + ptr->snd_timeout= (int32_t)data; + break; + case MEMCACHED_BEHAVIOR_RCV_TIMEOUT: + ptr->rcv_timeout= (int32_t)data; + break; + case MEMCACHED_BEHAVIOR_SERVER_FAILURE_LIMIT: + ptr->server_failure_limit= (uint32_t)data; + break; + case MEMCACHED_BEHAVIOR_BINARY_PROTOCOL: + if (data) + { + ptr->flags.verify_key= false; + } + ptr->flags.binary_protocol= set_flag(data); + break; + case MEMCACHED_BEHAVIOR_SUPPORT_CAS: + ptr->flags.support_cas= set_flag(data); + break; + case MEMCACHED_BEHAVIOR_NO_BLOCK: + ptr->flags.no_block= set_flag(data); + memcached_quit(ptr); + break; + case MEMCACHED_BEHAVIOR_BUFFER_REQUESTS: + ptr->flags.buffer_requests= set_flag(data); + memcached_quit(ptr); + break; + case MEMCACHED_BEHAVIOR_USE_UDP: + if (memcached_server_count(ptr)) + { + return MEMCACHED_FAILURE; + } + ptr->flags.use_udp= set_flag(data); + if (data) + { + ptr->flags.no_reply= set_flag(data); + } + break; + case MEMCACHED_BEHAVIOR_TCP_NODELAY: + ptr->flags.tcp_nodelay= set_flag(data); + memcached_quit(ptr); + break; + case MEMCACHED_BEHAVIOR_DISTRIBUTION: + return memcached_behavior_set_distribution(ptr, (memcached_server_distribution_t)data); + case MEMCACHED_BEHAVIOR_KETAMA: + { + if (data) + { + (void)memcached_behavior_set_key_hash(ptr, MEMCACHED_HASH_MD5); + (void)memcached_behavior_set_distribution_hash(ptr, MEMCACHED_HASH_MD5); + (void)memcached_behavior_set_distribution(ptr, MEMCACHED_DISTRIBUTION_CONSISTENT_KETAMA); + } + else + { + (void)memcached_behavior_set_key_hash(ptr, MEMCACHED_HASH_DEFAULT); + (void)memcached_behavior_set_distribution_hash(ptr, MEMCACHED_HASH_DEFAULT); + (void)memcached_behavior_set_distribution(ptr, MEMCACHED_DISTRIBUTION_MODULA); + } + + break; + } + case MEMCACHED_BEHAVIOR_KETAMA_WEIGHTED: + { + (void)memcached_behavior_set_key_hash(ptr, MEMCACHED_HASH_MD5); + (void)memcached_behavior_set_distribution_hash(ptr, MEMCACHED_HASH_MD5); + ptr->flags.ketama_weighted= set_flag(data); + /** + @note We try to keep the same distribution going. This should be deprecated and rewritten. + */ + return memcached_behavior_set_distribution(ptr, MEMCACHED_DISTRIBUTION_CONSISTENT_KETAMA); + } + case MEMCACHED_BEHAVIOR_HASH: + return memcached_behavior_set_key_hash(ptr, (memcached_hash_t)(data)); + case MEMCACHED_BEHAVIOR_KETAMA_HASH: + return memcached_behavior_set_distribution_hash(ptr, (memcached_hash_t)(data)); + case MEMCACHED_BEHAVIOR_CACHE_LOOKUPS: + ptr->flags.use_cache_lookups= set_flag(data); + memcached_quit(ptr); + break; + case MEMCACHED_BEHAVIOR_VERIFY_KEY: + if (ptr->flags.binary_protocol) + return MEMCACHED_FAILURE; + ptr->flags.verify_key= set_flag(data); + break; + case MEMCACHED_BEHAVIOR_SORT_HOSTS: + { + ptr->flags.use_sort_hosts= set_flag(data); + run_distribution(ptr); + + break; + } + case MEMCACHED_BEHAVIOR_POLL_TIMEOUT: + ptr->poll_timeout= (int32_t)data; + break; + case MEMCACHED_BEHAVIOR_CONNECT_TIMEOUT: + ptr->connect_timeout= (int32_t)data; + break; + case MEMCACHED_BEHAVIOR_RETRY_TIMEOUT: + ptr->retry_timeout= (int32_t)data; + break; + case MEMCACHED_BEHAVIOR_SOCKET_SEND_SIZE: + ptr->send_size= (int32_t)data; + memcached_quit(ptr); + break; + case MEMCACHED_BEHAVIOR_SOCKET_RECV_SIZE: + ptr->recv_size= (int32_t)data; + memcached_quit(ptr); + break; + case MEMCACHED_BEHAVIOR_USER_DATA: + return MEMCACHED_FAILURE; + case MEMCACHED_BEHAVIOR_HASH_WITH_PREFIX_KEY: + ptr->flags.hash_with_prefix_key= set_flag(data); + break; + case MEMCACHED_BEHAVIOR_NOREPLY: + ptr->flags.no_reply= set_flag(data); + break; + case MEMCACHED_BEHAVIOR_AUTO_EJECT_HOSTS: + ptr->flags.auto_eject_hosts= set_flag(data); + break; + case MEMCACHED_BEHAVIOR_RANDOMIZE_REPLICA_READ: + srandom((uint32_t) time(NULL)); + ptr->flags.randomize_replica_read= set_flag(data); + break; ++ case MEMCACHED_BEHAVIOR_CORK: ++ { ++ memcached_server_instance_st *instance; ++ bool action= set_flag(data); ++ ++ if (action == false) ++ { ++ ptr->flags.cork= set_flag(false); ++ return MEMCACHED_SUCCESS; ++ } ++ ++ instance= memcached_server_instance_fetch(ptr, 0); ++ if (! instance) ++ return MEMCACHED_NO_SERVERS; ++ ++ ++ /* We just try the first host, and if it is down we return zero */ ++ memcached_return_t rc; ++ rc= memcached_connect(instance); ++ if (rc != MEMCACHED_SUCCESS) ++ { ++ return rc; ++ } ++ ++ /* Now we test! */ ++ memcached_ternary_t enabled; ++ enabled= cork_switch(instance, true); ++ ++ switch (enabled) ++ { ++ case MEM_FALSE: ++ return ptr->cached_errno ? MEMCACHED_ERRNO : MEMCACHED_FAILURE ; ++ case MEM_TRUE: ++ { ++ enabled= cork_switch(instance, false); ++ ++ if (enabled == false) // Possible bug in OS? ++ { ++ memcached_quit_server(instance, false); // We should reset everything on this error. ++ return MEMCACHED_ERRNO; // Errno will be true because we will have already set it. ++ } ++ ptr->flags.cork= true; ++ ptr->flags.tcp_nodelay= true; ++ } ++ break; ++ case MEM_NOT: ++ default: ++ return MEMCACHED_NOT_SUPPORTED; ++ } ++ } ++ break; + case MEMCACHED_BEHAVIOR_MAX: + default: + /* Shouldn't get here */ + WATCHPOINT_ASSERT(0); + return MEMCACHED_FAILURE; + } + + return MEMCACHED_SUCCESS; +} + +uint64_t memcached_behavior_get(memcached_st *ptr, + const memcached_behavior_t flag) +{ + switch (flag) + { + case MEMCACHED_BEHAVIOR_NUMBER_OF_REPLICAS: + return ptr->number_of_replicas; + case MEMCACHED_BEHAVIOR_IO_MSG_WATERMARK: + return ptr->io_msg_watermark; + case MEMCACHED_BEHAVIOR_IO_BYTES_WATERMARK: + return ptr->io_bytes_watermark; + case MEMCACHED_BEHAVIOR_IO_KEY_PREFETCH: + return ptr->io_key_prefetch; + case MEMCACHED_BEHAVIOR_BINARY_PROTOCOL: + return ptr->flags.binary_protocol; + case MEMCACHED_BEHAVIOR_SUPPORT_CAS: + return ptr->flags.support_cas; + case MEMCACHED_BEHAVIOR_CACHE_LOOKUPS: + return ptr->flags.use_cache_lookups; + case MEMCACHED_BEHAVIOR_NO_BLOCK: + return ptr->flags.no_block; + case MEMCACHED_BEHAVIOR_BUFFER_REQUESTS: + return ptr->flags.buffer_requests; + case MEMCACHED_BEHAVIOR_USE_UDP: + return ptr->flags.use_udp; + case MEMCACHED_BEHAVIOR_TCP_NODELAY: + return ptr->flags.tcp_nodelay; + case MEMCACHED_BEHAVIOR_VERIFY_KEY: + return ptr->flags.verify_key; + case MEMCACHED_BEHAVIOR_KETAMA_WEIGHTED: + return ptr->flags.ketama_weighted; + case MEMCACHED_BEHAVIOR_DISTRIBUTION: + return ptr->distribution; + case MEMCACHED_BEHAVIOR_KETAMA: + return (ptr->distribution == MEMCACHED_DISTRIBUTION_CONSISTENT_KETAMA) ? (uint64_t) 1 : 0; + case MEMCACHED_BEHAVIOR_HASH: + return ptr->hash; + case MEMCACHED_BEHAVIOR_KETAMA_HASH: + return ptr->distribution_hash; + case MEMCACHED_BEHAVIOR_SERVER_FAILURE_LIMIT: + return ptr->server_failure_limit; + case MEMCACHED_BEHAVIOR_SORT_HOSTS: + return ptr->flags.use_sort_hosts; + case MEMCACHED_BEHAVIOR_POLL_TIMEOUT: + return (uint64_t)ptr->poll_timeout; + case MEMCACHED_BEHAVIOR_CONNECT_TIMEOUT: + return (uint64_t)ptr->connect_timeout; + case MEMCACHED_BEHAVIOR_RETRY_TIMEOUT: + return (uint64_t)ptr->retry_timeout; + case MEMCACHED_BEHAVIOR_SND_TIMEOUT: + return (uint64_t)ptr->snd_timeout; + case MEMCACHED_BEHAVIOR_RCV_TIMEOUT: + return (uint64_t)ptr->rcv_timeout; + case MEMCACHED_BEHAVIOR_SOCKET_SEND_SIZE: + { - int sock_size; ++ int sock_size= 0; + socklen_t sock_length= sizeof(int); + memcached_server_instance_st *instance; + ++ if (ptr->send_size != -1) // If value is -1 then we are using the default ++ return (uint64_t) ptr->send_size; ++ + instance= memcached_server_instance_fetch(ptr, 0); + - /* REFACTOR */ - /* We just try the first host, and if it is down we return zero */ - if ((memcached_connect(instance)) != MEMCACHED_SUCCESS) - return 0; ++ if (instance) // If we have an instance we test, otherwise we just set and pray ++ { ++ /* REFACTOR */ ++ /* We just try the first host, and if it is down we return zero */ ++ if ((memcached_connect(instance)) != MEMCACHED_SUCCESS) ++ return 0; + - if (getsockopt(instance->fd, SOL_SOCKET, - SO_SNDBUF, &sock_size, &sock_length)) - return 0; /* Zero means error */ ++ if (getsockopt(instance->fd, SOL_SOCKET, ++ SO_SNDBUF, &sock_size, &sock_length)) ++ return 0; /* Zero means error */ ++ } + + return (uint64_t) sock_size; + } + case MEMCACHED_BEHAVIOR_SOCKET_RECV_SIZE: + { - int sock_size; ++ int sock_size= 0; + socklen_t sock_length= sizeof(int); + memcached_server_instance_st *instance; + ++ if (ptr->recv_size != -1) // If value is -1 then we are using the default ++ return (uint64_t) ptr->recv_size; ++ + instance= memcached_server_instance_fetch(ptr, 0); + + /** + @note REFACTOR + */ - /* We just try the first host, and if it is down we return zero */ - if ((memcached_connect(instance)) != MEMCACHED_SUCCESS) - return 0; ++ if (instance) ++ { ++ /* We just try the first host, and if it is down we return zero */ ++ if ((memcached_connect(instance)) != MEMCACHED_SUCCESS) ++ return 0; + - if (getsockopt(instance->fd, SOL_SOCKET, - SO_RCVBUF, &sock_size, &sock_length)) - return 0; /* Zero means error */ ++ if (getsockopt(instance->fd, SOL_SOCKET, ++ SO_RCVBUF, &sock_size, &sock_length)) ++ return 0; /* Zero means error */ ++ ++ } + + return (uint64_t) sock_size; + } + case MEMCACHED_BEHAVIOR_USER_DATA: + return MEMCACHED_FAILURE; + case MEMCACHED_BEHAVIOR_HASH_WITH_PREFIX_KEY: + return ptr->flags.hash_with_prefix_key; + case MEMCACHED_BEHAVIOR_NOREPLY: + return ptr->flags.no_reply; + case MEMCACHED_BEHAVIOR_AUTO_EJECT_HOSTS: + return ptr->flags.auto_eject_hosts; + case MEMCACHED_BEHAVIOR_RANDOMIZE_REPLICA_READ: + return ptr->flags.randomize_replica_read; ++ case MEMCACHED_BEHAVIOR_CORK: ++ return ptr->flags.cork; + case MEMCACHED_BEHAVIOR_MAX: + default: + WATCHPOINT_ASSERT(0); /* Programming mistake if it gets this far */ + return 0; + } + + /* NOTREACHED */ +} + + +memcached_return_t memcached_behavior_set_distribution(memcached_st *ptr, memcached_server_distribution_t type) +{ + if (type < MEMCACHED_DISTRIBUTION_CONSISTENT_MAX) + { + ptr->distribution= type; + run_distribution(ptr); + } + else + { + return MEMCACHED_FAILURE; + } + + return MEMCACHED_SUCCESS; +} + + +memcached_server_distribution_t memcached_behavior_get_distribution(memcached_st *ptr) +{ + return ptr->distribution; +} + +memcached_return_t memcached_behavior_set_key_hash(memcached_st *ptr, memcached_hash_t type) +{ + return set_hash(&ptr->hash, type); +} + +memcached_hash_t memcached_behavior_get_key_hash(memcached_st *ptr) +{ + return ptr->hash; +} + +memcached_return_t memcached_behavior_set_distribution_hash(memcached_st *ptr, memcached_hash_t type) +{ + return set_hash(&ptr->distribution_hash, type); +} + +memcached_hash_t memcached_behavior_get_distribution_hash(memcached_st *ptr) +{ + return ptr->distribution_hash; +} diff --cc libmemcached/common.h index e78d5143,b46d5ba0..1d77ba16 --- a/libmemcached/common.h +++ b/libmemcached/common.h @@@ -71,6 -56,6 +71,13 @@@ struct memcached_continuum_item_s uint32_t value; }; ++/* Yum, Fortran.... can you make the reference? */ ++typedef enum { ++ MEM_NOT= -1, ++ MEM_FALSE= false, ++ MEM_TRUE= true, ++} memcached_ternary_t; ++ #if !defined(__GNUC__) || (__GNUC__ == 2 && __GNUC_MINOR__ < 96) @@@ -83,7 -68,7 +90,6 @@@ #define unlikely(x) if(__builtin_expect((x) != 0, 0)) #endif -- #define MEMCACHED_BLOCK_SIZE 1024 #define MEMCACHED_DEFAULT_COMMAND_SIZE 350 #define SMALL_STRING_LEN 1024 @@@ -155,4 -166,4 +161,42 @@@ static inline memcached_return_t memcac return MEMCACHED_SUCCESS; } ++#ifdef TCP_CORK ++ #define CORK TCP_CORK ++#elif defined TCP_NOPUSH ++ #define CORK TCP_NOPUSH ++#endif ++ ++/* ++ cork_switch() tries to enable TCP_CORK. IF TCP_CORK is not an option ++ on the system it returns false but sets errno to 0. Otherwise on ++ failure errno is set. ++*/ ++static inline memcached_ternary_t cork_switch(memcached_server_st *ptr, bool enable) ++{ ++#ifdef CORK ++ if (ptr->type != MEMCACHED_CONNECTION_TCP) ++ return MEM_FALSE; ++ ++ int err= setsockopt(ptr->fd, IPPROTO_TCP, CORK, ++ &enable, (socklen_t)sizeof(int)); ++ if (! err) ++ { ++ return MEM_TRUE; ++ } ++ else ++ { ++ ptr->cached_errno= errno; ++ return MEM_FALSE; ++ } ++#else ++ (void)ptr; ++ (void)enable; ++ ++ ptr->cached_errno= 0; ++ ++ return MEM_NOT; ++#endif ++} ++ #endif /* LIBMEMCACHED_COMMON_H */ diff --cc libmemcached/connect.c index 36971f60,00000000..6d08eb70 mode 100644,000000..100644 --- a/libmemcached/connect.c +++ b/libmemcached/connect.c @@@ -1,372 -1,0 +1,372 @@@ +#include "common.h" +#include +#include +#include + +static memcached_return_t set_hostinfo(memcached_server_st *server) +{ + struct addrinfo *ai; + struct addrinfo hints; + int e; + char str_port[NI_MAXSERV]; + + snprintf(str_port, NI_MAXSERV, "%u", (uint32_t)server->port); + + memset(&hints, 0, sizeof(hints)); + + // hints.ai_family= AF_INET; + if (server->type == MEMCACHED_CONNECTION_UDP) + { + hints.ai_protocol= IPPROTO_UDP; + hints.ai_socktype= SOCK_DGRAM; + } + else + { + hints.ai_socktype= SOCK_STREAM; + hints.ai_protocol= IPPROTO_TCP; + } + + e= getaddrinfo(server->hostname, str_port, &hints, &ai); + if (e != 0) + { + WATCHPOINT_STRING(server->hostname); + WATCHPOINT_STRING(gai_strerror(e)); + return MEMCACHED_HOST_LOOKUP_FAILURE; + } + + if (server->address_info) + { + freeaddrinfo(server->address_info); + server->address_info= NULL; + } + server->address_info= ai; + + return MEMCACHED_SUCCESS; +} + +static memcached_return_t set_socket_options(memcached_server_st *ptr) +{ + WATCHPOINT_ASSERT(ptr->fd != -1); + + if (ptr->type == MEMCACHED_CONNECTION_UDP) + return MEMCACHED_SUCCESS; + +#ifdef HAVE_SNDTIMEO + if (ptr->root->snd_timeout) + { + int error; + struct timeval waittime; + + waittime.tv_sec= 0; + waittime.tv_usec= ptr->root->snd_timeout; + + error= setsockopt(ptr->fd, SOL_SOCKET, SO_SNDTIMEO, + &waittime, (socklen_t)sizeof(struct timeval)); + WATCHPOINT_ASSERT(error == 0); + if (error) + return MEMCACHED_FAILURE; + } +#endif + +#ifdef HAVE_RCVTIMEO + if (ptr->root->rcv_timeout) + { + int error; + struct timeval waittime; + + waittime.tv_sec= 0; + waittime.tv_usec= ptr->root->rcv_timeout; + + error= setsockopt(ptr->fd, SOL_SOCKET, SO_RCVTIMEO, + &waittime, (socklen_t)sizeof(struct timeval)); + WATCHPOINT_ASSERT(error == 0); + if (error) + return MEMCACHED_FAILURE; + } +#endif + + if (ptr->root->flags.no_block) + { + int error; + struct linger linger; + + linger.l_onoff= 1; + linger.l_linger= 0; /* By default on close() just drop the socket */ + error= setsockopt(ptr->fd, SOL_SOCKET, SO_LINGER, + &linger, (socklen_t)sizeof(struct linger)); + WATCHPOINT_ASSERT(error == 0); + if (error) + return MEMCACHED_FAILURE; + } + + if (ptr->root->flags.tcp_nodelay) + { + int flag= 1; + int error; + + error= setsockopt(ptr->fd, IPPROTO_TCP, TCP_NODELAY, + &flag, (socklen_t)sizeof(int)); + WATCHPOINT_ASSERT(error == 0); + if (error) + return MEMCACHED_FAILURE; + } + - if (ptr->root->send_size) ++ if (ptr->root->send_size > 0) + { + int error; + + error= setsockopt(ptr->fd, SOL_SOCKET, SO_SNDBUF, + &ptr->root->send_size, (socklen_t)sizeof(int)); + WATCHPOINT_ASSERT(error == 0); + if (error) + return MEMCACHED_FAILURE; + } + - if (ptr->root->recv_size) ++ if (ptr->root->recv_size > 0) + { + int error; + + error= setsockopt(ptr->fd, SOL_SOCKET, SO_RCVBUF, + &ptr->root->recv_size, (socklen_t)sizeof(int)); + WATCHPOINT_ASSERT(error == 0); + if (error) + return MEMCACHED_FAILURE; + } + + /* libmemcached will always use nonblocking IO to avoid write deadlocks */ + int flags; + + do + flags= fcntl(ptr->fd, F_GETFL, 0); + while (flags == -1 && (errno == EINTR || errno == EAGAIN)); + + unlikely (flags == -1) + { + return MEMCACHED_CONNECTION_FAILURE; + } + else if ((flags & O_NONBLOCK) == 0) + { + int rval; + + do + rval= fcntl(ptr->fd, F_SETFL, flags | O_NONBLOCK); + while (rval == -1 && (errno == EINTR || errno == EAGAIN)); + + unlikely (rval == -1) + { + return MEMCACHED_CONNECTION_FAILURE; + } + } + + return MEMCACHED_SUCCESS; +} + +static memcached_return_t unix_socket_connect(memcached_server_st *ptr) +{ + struct sockaddr_un servAddr; + + if (ptr->fd == -1) + { + if ((ptr->fd= socket(AF_UNIX, SOCK_STREAM, 0)) < 0) + { + ptr->cached_errno= errno; + return MEMCACHED_CONNECTION_SOCKET_CREATE_FAILURE; + } + + memset(&servAddr, 0, sizeof (struct sockaddr_un)); + servAddr.sun_family= AF_UNIX; + strcpy(servAddr.sun_path, ptr->hostname); /* Copy filename */ + +test_connect: + if (connect(ptr->fd, + (struct sockaddr *)&servAddr, + sizeof(servAddr)) < 0) + { + switch (errno) + { + case EINPROGRESS: + case EALREADY: + case EINTR: + goto test_connect; + case EISCONN: /* We were spinning waiting on connect */ + break; + default: + WATCHPOINT_ERRNO(errno); + ptr->cached_errno= errno; + return MEMCACHED_ERRNO; + } + } + } + + WATCHPOINT_ASSERT(ptr->fd != -1); + return MEMCACHED_SUCCESS; +} + +static memcached_return_t network_connect(memcached_server_st *ptr) +{ + if (ptr->fd == -1) + { + struct addrinfo *use; + + WATCHPOINT_ASSERT(ptr->cursor_active == 0); + + if (! ptr->options.sockaddr_inited || + (!(ptr->root->flags.use_cache_lookups))) + { + memcached_return_t rc; + + rc= set_hostinfo(ptr); + if (rc != MEMCACHED_SUCCESS) + return rc; + ptr->options.sockaddr_inited= true; + } + + use= ptr->address_info; + /* Create the socket */ + while (use != NULL) + { + /* Memcache server does not support IPV6 in udp mode, so skip if not ipv4 */ + if (ptr->type == MEMCACHED_CONNECTION_UDP && use->ai_family != AF_INET) + { + use= use->ai_next; + continue; + } + + if ((ptr->fd= socket(use->ai_family, + use->ai_socktype, + use->ai_protocol)) < 0) + { + ptr->cached_errno= errno; + WATCHPOINT_ERRNO(errno); + return MEMCACHED_CONNECTION_SOCKET_CREATE_FAILURE; + } + + (void)set_socket_options(ptr); + + /* connect to server */ + while (ptr->fd != -1 && + connect(ptr->fd, use->ai_addr, use->ai_addrlen) < 0) + { + ptr->cached_errno= errno; + if (errno == EINPROGRESS || /* nonblocking mode - first return, */ + errno == EALREADY) /* nonblocking mode - subsequent returns */ + { + struct pollfd fds[1]; + fds[0].fd = ptr->fd; + fds[0].events = POLLOUT; + int error= poll(fds, 1, ptr->root->connect_timeout); + + if (error != 1 || fds[0].revents & POLLERR) + { + if (fds[0].revents & POLLERR) + { + int err; + socklen_t len = sizeof (err); + (void)getsockopt(ptr->fd, SOL_SOCKET, SO_ERROR, &err, &len); + ptr->cached_errno= (err == 0) ? errno : err; + } + + (void)close(ptr->fd); + ptr->fd= -1; + } + } + else if (errno == EISCONN) /* we are connected :-) */ + { + break; + } + else if (errno != EINTR) + { + (void)close(ptr->fd); + ptr->fd= -1; + break; + } + } + + if (ptr->fd != -1) + { + ptr->server_failure_counter= 0; + return MEMCACHED_SUCCESS; + } + use = use->ai_next; + } + } + + if (ptr->fd == -1) + { + /* Failed to connect. schedule next retry */ + if (ptr->root->retry_timeout) + { + struct timeval next_time; + + if (gettimeofday(&next_time, NULL) == 0) + ptr->next_retry= next_time.tv_sec + ptr->root->retry_timeout; + } + ptr->server_failure_counter++; + if (ptr->cached_errno == 0) + return MEMCACHED_TIMEOUT; + + return MEMCACHED_ERRNO; /* The last error should be from connect() */ + } + + ptr->server_failure_counter= 0; + return MEMCACHED_SUCCESS; /* The last error should be from connect() */ +} + + +memcached_return_t memcached_connect(memcached_server_st *ptr) +{ + memcached_return_t rc= MEMCACHED_NO_SERVERS; + LIBMEMCACHED_MEMCACHED_CONNECT_START(); + + /* both retry_timeout and server_failure_limit must be set in order to delay retrying a server on error. */ + WATCHPOINT_ASSERT(ptr->root); + if (ptr->root->retry_timeout && ptr->root->server_failure_limit) + { + struct timeval curr_time; + + gettimeofday(&curr_time, NULL); + + /* if we've had too many consecutive errors on this server, mark it dead. */ + if (ptr->server_failure_counter >= ptr->root->server_failure_limit) + { + ptr->next_retry= curr_time.tv_sec + ptr->root->retry_timeout; + ptr->server_failure_counter= 0; + } + + if (curr_time.tv_sec < ptr->next_retry) + { + if (memcached_behavior_get(ptr->root, MEMCACHED_BEHAVIOR_AUTO_EJECT_HOSTS)) + { + run_distribution(ptr->root); + } + + ptr->root->last_disconnected_server = ptr; + return MEMCACHED_SERVER_MARKED_DEAD; + } + } + + /* We need to clean up the multi startup piece */ + switch (ptr->type) + { + case MEMCACHED_CONNECTION_UNKNOWN: + WATCHPOINT_ASSERT(0); + rc= MEMCACHED_NOT_SUPPORTED; + break; + case MEMCACHED_CONNECTION_UDP: + case MEMCACHED_CONNECTION_TCP: + rc= network_connect(ptr); + break; + case MEMCACHED_CONNECTION_UNIX_SOCKET: + rc= unix_socket_connect(ptr); + break; + case MEMCACHED_CONNECTION_MAX: + default: + WATCHPOINT_ASSERT(0); + } + + unlikely ( rc != MEMCACHED_SUCCESS) ptr->root->last_disconnected_server = ptr; + + LIBMEMCACHED_MEMCACHED_CONNECT_END(); + + return rc; +} diff --cc libmemcached/constants.h index 5c704027,00000000..11b2f677 mode 100644,000000..100644 --- a/libmemcached/constants.h +++ b/libmemcached/constants.h @@@ -1,157 -1,0 +1,158 @@@ +/* LibMemcached + * Copyright (C) 2006-2009 Brian Aker + * All rights reserved. + * + * Use and distribution licensed under the BSD license. See + * the COPYING file in the parent directory for full text. + * + * Summary: Constants for libmemcached + * + */ + +#ifndef __MEMCACHED_CONSTANTS_H__ +#define __MEMCACHED_CONSTANTS_H__ + +/* Public defines */ +#define MEMCACHED_DEFAULT_PORT 11211 +#define MEMCACHED_MAX_KEY 251 /* We add one to have it null terminated */ +#define MEMCACHED_MAX_BUFFER 8196 +#define MEMCACHED_MAX_HOST_LENGTH 64 +#define MEMCACHED_MAX_HOST_SORT_LENGTH 86 /* Used for Ketama */ +#define MEMCACHED_POINTS_PER_SERVER 100 +#define MEMCACHED_POINTS_PER_SERVER_KETAMA 160 +#define MEMCACHED_CONTINUUM_SIZE MEMCACHED_POINTS_PER_SERVER*100 /* This would then set max hosts to 100 */ +#define MEMCACHED_STRIDE 4 +#define MEMCACHED_DEFAULT_TIMEOUT 1000 +#define MEMCACHED_CONTINUUM_ADDITION 10 /* How many extra slots we should build for in the continuum */ +#define MEMCACHED_PREFIX_KEY_MAX_SIZE 128 +#define MEMCACHED_EXPIRATION_NOT_ADD 0xffffffffU +#define MEMCACHED_VERSION_STRING_LENGTH 24 + + +typedef enum { + MEMCACHED_SUCCESS, + MEMCACHED_FAILURE, + MEMCACHED_HOST_LOOKUP_FAILURE, + MEMCACHED_CONNECTION_FAILURE, + MEMCACHED_CONNECTION_BIND_FAILURE, + MEMCACHED_WRITE_FAILURE, + MEMCACHED_READ_FAILURE, + MEMCACHED_UNKNOWN_READ_FAILURE, + MEMCACHED_PROTOCOL_ERROR, + MEMCACHED_CLIENT_ERROR, + MEMCACHED_SERVER_ERROR, + MEMCACHED_CONNECTION_SOCKET_CREATE_FAILURE, + MEMCACHED_DATA_EXISTS, + MEMCACHED_DATA_DOES_NOT_EXIST, + MEMCACHED_NOTSTORED, + MEMCACHED_STORED, + MEMCACHED_NOTFOUND, + MEMCACHED_MEMORY_ALLOCATION_FAILURE, + MEMCACHED_PARTIAL_READ, + MEMCACHED_SOME_ERRORS, + MEMCACHED_NO_SERVERS, + MEMCACHED_END, + MEMCACHED_DELETED, + MEMCACHED_VALUE, + MEMCACHED_STAT, + MEMCACHED_ITEM, + MEMCACHED_ERRNO, + MEMCACHED_FAIL_UNIX_SOCKET, + MEMCACHED_NOT_SUPPORTED, + MEMCACHED_NO_KEY_PROVIDED, /* Deprecated. Use MEMCACHED_BAD_KEY_PROVIDED! */ + MEMCACHED_FETCH_NOTFINISHED, + MEMCACHED_TIMEOUT, + MEMCACHED_BUFFERED, + MEMCACHED_BAD_KEY_PROVIDED, + MEMCACHED_INVALID_HOST_PROTOCOL, + MEMCACHED_SERVER_MARKED_DEAD, + MEMCACHED_UNKNOWN_STAT_KEY, + MEMCACHED_E2BIG, + MEMCACHED_INVALID_ARGUMENTS, + MEMCACHED_MAXIMUM_RETURN /* Always add new error code before */ +} memcached_return_t; + + +typedef enum { + MEMCACHED_DISTRIBUTION_MODULA, + MEMCACHED_DISTRIBUTION_CONSISTENT, + MEMCACHED_DISTRIBUTION_CONSISTENT_KETAMA, + MEMCACHED_DISTRIBUTION_RANDOM, + MEMCACHED_DISTRIBUTION_CONSISTENT_KETAMA_SPY, + MEMCACHED_DISTRIBUTION_CONSISTENT_MAX +} memcached_server_distribution_t; + +typedef enum { + MEMCACHED_BEHAVIOR_NO_BLOCK, + MEMCACHED_BEHAVIOR_TCP_NODELAY, + MEMCACHED_BEHAVIOR_HASH, + MEMCACHED_BEHAVIOR_KETAMA, + MEMCACHED_BEHAVIOR_SOCKET_SEND_SIZE, + MEMCACHED_BEHAVIOR_SOCKET_RECV_SIZE, + MEMCACHED_BEHAVIOR_CACHE_LOOKUPS, + MEMCACHED_BEHAVIOR_SUPPORT_CAS, + MEMCACHED_BEHAVIOR_POLL_TIMEOUT, + MEMCACHED_BEHAVIOR_DISTRIBUTION, + MEMCACHED_BEHAVIOR_BUFFER_REQUESTS, + MEMCACHED_BEHAVIOR_USER_DATA, + MEMCACHED_BEHAVIOR_SORT_HOSTS, + MEMCACHED_BEHAVIOR_VERIFY_KEY, + MEMCACHED_BEHAVIOR_CONNECT_TIMEOUT, + MEMCACHED_BEHAVIOR_RETRY_TIMEOUT, + MEMCACHED_BEHAVIOR_KETAMA_WEIGHTED, + MEMCACHED_BEHAVIOR_KETAMA_HASH, + MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, + MEMCACHED_BEHAVIOR_SND_TIMEOUT, + MEMCACHED_BEHAVIOR_RCV_TIMEOUT, + MEMCACHED_BEHAVIOR_SERVER_FAILURE_LIMIT, + MEMCACHED_BEHAVIOR_IO_MSG_WATERMARK, + MEMCACHED_BEHAVIOR_IO_BYTES_WATERMARK, + MEMCACHED_BEHAVIOR_IO_KEY_PREFETCH, + MEMCACHED_BEHAVIOR_HASH_WITH_PREFIX_KEY, + MEMCACHED_BEHAVIOR_NOREPLY, + MEMCACHED_BEHAVIOR_USE_UDP, + MEMCACHED_BEHAVIOR_AUTO_EJECT_HOSTS, + MEMCACHED_BEHAVIOR_NUMBER_OF_REPLICAS, + MEMCACHED_BEHAVIOR_RANDOMIZE_REPLICA_READ, ++ MEMCACHED_BEHAVIOR_CORK, + MEMCACHED_BEHAVIOR_MAX +} memcached_behavior_t; + +typedef enum { + MEMCACHED_CALLBACK_PREFIX_KEY = 0, + MEMCACHED_CALLBACK_USER_DATA = 1, + MEMCACHED_CALLBACK_CLEANUP_FUNCTION = 2, + MEMCACHED_CALLBACK_CLONE_FUNCTION = 3, +#ifdef MEMCACHED_ENABLE_DEPRECATED + MEMCACHED_CALLBACK_MALLOC_FUNCTION = 4, + MEMCACHED_CALLBACK_REALLOC_FUNCTION = 5, + MEMCACHED_CALLBACK_FREE_FUNCTION = 6, +#endif + MEMCACHED_CALLBACK_GET_FAILURE = 7, + MEMCACHED_CALLBACK_DELETE_TRIGGER = 8, + MEMCACHED_CALLBACK_MAX +} memcached_callback_t; + +typedef enum { + MEMCACHED_HASH_DEFAULT= 0, + MEMCACHED_HASH_MD5, + MEMCACHED_HASH_CRC, + MEMCACHED_HASH_FNV1_64, + MEMCACHED_HASH_FNV1A_64, + MEMCACHED_HASH_FNV1_32, + MEMCACHED_HASH_FNV1A_32, + MEMCACHED_HASH_HSIEH, + MEMCACHED_HASH_MURMUR, + MEMCACHED_HASH_JENKINS, + MEMCACHED_HASH_MAX +} memcached_hash_t; + +typedef enum { + MEMCACHED_CONNECTION_UNKNOWN, + MEMCACHED_CONNECTION_TCP, + MEMCACHED_CONNECTION_UDP, + MEMCACHED_CONNECTION_UNIX_SOCKET, + MEMCACHED_CONNECTION_MAX +} memcached_connection_t; + +#endif /* __MEMCACHED_CONSTANTS_H__ */ diff --cc libmemcached/io.c index 3b8943c8,00000000..5102d95b mode 100644,000000..100644 --- a/libmemcached/io.c +++ b/libmemcached/io.c @@@ -1,659 -1,0 +1,699 @@@ +/* LibMemcached + * Copyright (C) 2006-2009 Brian Aker + * All rights reserved. + * + * Use and distribution licensed under the BSD license. See + * the COPYING file in the parent directory for full text. + * + * Summary: Server IO, Not public! + * + */ + + +#include "common.h" +#include +#include + +typedef enum { + MEM_READ, + MEM_WRITE +} memc_read_or_write; + +static ssize_t io_flush(memcached_server_instance_st *ptr, memcached_return_t *error); +static void increment_udp_message_id(memcached_server_instance_st *ptr); + +static memcached_return_t io_wait(memcached_server_instance_st *ptr, + memc_read_or_write read_or_write) +{ + struct pollfd fds= { + .fd= ptr->fd, + .events = POLLIN + }; + int error; + + unlikely (read_or_write == MEM_WRITE) /* write */ + fds.events= POLLOUT; + + /* + ** We are going to block on write, but at least on Solaris we might block + ** on write if we haven't read anything from our input buffer.. + ** Try to purge the input buffer if we don't do any flow control in the + ** application layer (just sending a lot of data etc) + ** The test is moved down in the purge function to avoid duplication of + ** the test. + */ + if (read_or_write == MEM_WRITE) + { + memcached_return_t rc= memcached_purge(ptr); + if (rc != MEMCACHED_SUCCESS && rc != MEMCACHED_STORED) + return MEMCACHED_FAILURE; + } + + int timeout= ptr->root->poll_timeout; + if (ptr->root->flags.no_block == false) + timeout= -1; + + error= poll(&fds, 1, timeout); + + if (error == 1) + return MEMCACHED_SUCCESS; + else if (error == 0) + return MEMCACHED_TIMEOUT; + + /* Imposssible for anything other then -1 */ + WATCHPOINT_ASSERT(error == -1); + memcached_quit_server(ptr, 1); + + return MEMCACHED_FAILURE; +} + +/** + * Try to fill the input buffer for a server with as much + * data as possible. + * + * @param ptr the server to pack + */ +static bool repack_input_buffer(memcached_server_instance_st *ptr) +{ + if (ptr->read_ptr != ptr->read_buffer) + { + /* Move all of the data to the beginning of the buffer so + ** that we can fit more data into the buffer... + */ + memmove(ptr->read_buffer, ptr->read_ptr, ptr->read_buffer_length); + ptr->read_ptr= ptr->read_buffer; + ptr->read_data_length= ptr->read_buffer_length; + } + + /* There is room in the buffer, try to fill it! */ + if (ptr->read_buffer_length != MEMCACHED_MAX_BUFFER) + { + /* Just try a single read to grab what's available */ + ssize_t nr= read(ptr->fd, + ptr->read_ptr + ptr->read_data_length, + MEMCACHED_MAX_BUFFER - ptr->read_data_length); + + if (nr > 0) + { + ptr->read_data_length+= (size_t)nr; + ptr->read_buffer_length+= (size_t)nr; + return true; + } + } + return false; +} + +/** + * If the we have callbacks connected to this server structure + * we may start process the input queue and fire the callbacks + * for the incomming messages. This function is _only_ called + * when the input buffer is full, so that we _know_ that we have + * at least _one_ message to process. + * + * @param ptr the server to star processing iput messages for + * @return true if we processed anything, false otherwise + */ +static bool process_input_buffer(memcached_server_instance_st *ptr) +{ + /* + ** We might be able to process some of the response messages if we + ** have a callback set up + */ + if (ptr->root->callbacks != NULL && ptr->root->flags.use_udp == false) + { + /* + * We might have responses... try to read them out and fire + * callbacks + */ + memcached_callback_st cb= *ptr->root->callbacks; + + ptr->root->options.is_processing_input= true; + + char buffer[MEMCACHED_DEFAULT_COMMAND_SIZE]; + memcached_return_t error; + error= memcached_response(ptr, buffer, sizeof(buffer), + &ptr->root->result); + + ptr->root->options.is_processing_input = false; + + if (error == MEMCACHED_SUCCESS) + { + for (unsigned int x= 0; x < cb.number_of_callback; x++) + { + error= (*cb.callback[x])(ptr->root, &ptr->root->result, cb.context); + if (error != MEMCACHED_SUCCESS) + break; + } + + /* @todo what should I do with the error message??? */ + } + /* @todo what should I do with other error messages?? */ + return true; + } + + return false; +} + ++static inline void memcached_io_cork_push(memcached_server_st *ptr) ++{ ++#ifdef CORK ++ if (ptr->root->flags.cork == false || ptr->state.is_corked) ++ return; ++ ++ ptr->state.is_corked= ++ cork_switch(ptr, true) == MEM_TRUE ? true : false; ++ ++ WATCHPOINT_ASSERT(ptr->state.is_corked == true); ++#else ++ (void)ptr; ++#endif ++} ++ ++static inline void memcached_io_cork_pop(memcached_server_st *ptr) ++{ ++#ifdef CORK ++ if (ptr->root->flags.cork == false || ptr->state.is_corked == false) ++ return; ++ ++ ptr->state.is_corked= ++ cork_switch(ptr, false) == MEM_FALSE ? false : true; ++ ++ WATCHPOINT_ASSERT(ptr->state.is_corked == false); ++#else ++ (void)ptr; ++#endif ++} ++ +#ifdef UNUSED +void memcached_io_preread(memcached_st *ptr) +{ + unsigned int x; + + return; + + for (x= 0; x < memcached_server_count(ptr); x++) + { + if (memcached_server_response_count(ptr, x) && + ptr->hosts[x].read_data_length < MEMCACHED_MAX_BUFFER ) + { + size_t data_read; + + data_read= read(ptr->hosts[x].fd, + ptr->hosts[x].read_ptr + ptr->hosts[x].read_data_length, + MEMCACHED_MAX_BUFFER - ptr->hosts[x].read_data_length); + if (data_read == -1) + continue; + + ptr->hosts[x].read_buffer_length+= data_read; + ptr->hosts[x].read_data_length+= data_read; + } + } +} +#endif + +memcached_return_t memcached_io_read(memcached_server_instance_st *ptr, + void *buffer, size_t length, ssize_t *nread) +{ + char *buffer_ptr; + + buffer_ptr= buffer; + + while (length) + { + if (!ptr->read_buffer_length) + { + ssize_t data_read; + + while (1) + { + data_read= read(ptr->fd, ptr->read_buffer, MEMCACHED_MAX_BUFFER); + if (data_read > 0) + break; + else if (data_read == -1) + { + ptr->cached_errno= errno; + memcached_return_t rc= MEMCACHED_UNKNOWN_READ_FAILURE; + switch (errno) + { + case EAGAIN: + case EINTR: + if ((rc= io_wait(ptr, MEM_READ)) == MEMCACHED_SUCCESS) + continue; + /* fall through */ + + default: + { + memcached_quit_server(ptr, 1); + *nread= -1; + return rc; + } + } + } + else + { + /* + EOF. Any data received so far is incomplete + so discard it. This always reads by byte in case of TCP + and protocol enforcement happens at memcached_response() + looking for '\n'. We do not care for UDB which requests 8 bytes + at once. Generally, this means that connection went away. Since + for blocking I/O we do not return 0 and for non-blocking case + it will return EGAIN if data is not immediatly available. + */ + memcached_quit_server(ptr, 1); + *nread= -1; + return MEMCACHED_UNKNOWN_READ_FAILURE; + } + } + + ptr->io_bytes_sent = 0; + ptr->read_data_length= (size_t) data_read; + ptr->read_buffer_length= (size_t) data_read; + ptr->read_ptr= ptr->read_buffer; + } + + if (length > 1) + { + size_t difference; + + difference= (length > ptr->read_buffer_length) ? ptr->read_buffer_length : length; + + memcpy(buffer_ptr, ptr->read_ptr, difference); + length -= difference; + ptr->read_ptr+= difference; + ptr->read_buffer_length-= difference; + buffer_ptr+= difference; + } + else + { + *buffer_ptr= *ptr->read_ptr; + ptr->read_ptr++; + ptr->read_buffer_length--; + buffer_ptr++; + break; + } + } + + ptr->server_failure_counter= 0; + *nread = (ssize_t)(buffer_ptr - (char*)buffer); + return MEMCACHED_SUCCESS; +} + +ssize_t memcached_io_write(memcached_server_instance_st *ptr, + const void *buffer, size_t length, char with_flush) +{ + size_t original_length; + const char* buffer_ptr; + + WATCHPOINT_ASSERT(ptr->fd != -1); + + original_length= length; + buffer_ptr= buffer; + ++ /* more writable data is coming if a flush isn't required, so delay send */ ++ if (! with_flush) ++ { ++ memcached_io_cork_push(ptr); ++ } ++ + while (length) + { + char *write_ptr; + size_t should_write; + size_t buffer_end; + + if (ptr->type == MEMCACHED_CONNECTION_UDP) + { + //UDP does not support partial writes + buffer_end= MAX_UDP_DATAGRAM_LENGTH; + should_write= length; + if (ptr->write_buffer_offset + should_write > buffer_end) + return -1; + } + else + { + buffer_end= MEMCACHED_MAX_BUFFER; + should_write= buffer_end - ptr->write_buffer_offset; + should_write= (should_write < length) ? should_write : length; + } + + write_ptr= ptr->write_buffer + ptr->write_buffer_offset; + memcpy(write_ptr, buffer_ptr, should_write); + ptr->write_buffer_offset+= should_write; + buffer_ptr+= should_write; + length-= should_write; + + if (ptr->write_buffer_offset == buffer_end && ptr->type != MEMCACHED_CONNECTION_UDP) + { + memcached_return_t rc; + ssize_t sent_length; + + WATCHPOINT_ASSERT(ptr->fd != -1); + sent_length= io_flush(ptr, &rc); + if (sent_length == -1) + return -1; + + /* If io_flush calls memcached_purge, sent_length may be 0 */ + unlikely (sent_length != 0) + { + WATCHPOINT_ASSERT(sent_length == (ssize_t)buffer_end); + } + } + } + + if (with_flush) + { + memcached_return_t rc; + WATCHPOINT_ASSERT(ptr->fd != -1); + if (io_flush(ptr, &rc) == -1) ++ { + return -1; ++ } ++ ++ memcached_io_cork_pop(ptr); + } + + return (ssize_t) original_length; +} + +memcached_return_t memcached_io_close(memcached_server_instance_st *ptr) +{ + if (ptr->fd == -1) + { + return MEMCACHED_SUCCESS; + } + + /* in case of death shutdown to avoid blocking at close() */ + if (shutdown(ptr->fd, SHUT_RDWR) == -1 && errno != ENOTCONN) + { + WATCHPOINT_NUMBER(ptr->fd); + WATCHPOINT_ERRNO(errno); + WATCHPOINT_ASSERT(errno); + } + + if (close(ptr->fd) == -1) + { + WATCHPOINT_ERRNO(errno); + } + + return MEMCACHED_SUCCESS; +} + +memcached_server_instance_st *memcached_io_get_readable_server(memcached_st *memc) +{ +#define MAX_SERVERS_TO_POLL 100 + struct pollfd fds[MAX_SERVERS_TO_POLL]; + unsigned int host_index= 0; + + for (uint32_t x= 0; + x< memcached_server_count(memc) && host_index < MAX_SERVERS_TO_POLL; + ++x) + { + memcached_server_instance_st *instance= + memcached_server_instance_fetch(memc, x); + + if (instance->read_buffer_length > 0) /* I have data in the buffer */ + return instance; + + if (memcached_server_response_count(instance) > 0) + { + fds[host_index].events = POLLIN; + fds[host_index].revents = 0; + fds[host_index].fd = instance->fd; + ++host_index; + } + } + + if (host_index < 2) + { + /* We have 0 or 1 server with pending events.. */ + for (uint32_t x= 0; x< memcached_server_count(memc); ++x) + { + memcached_server_instance_st *instance= + memcached_server_instance_fetch(memc, x); + + if (memcached_server_response_count(instance) > 0) + { + return instance; + } + } + + return NULL; + } + + int err= poll(fds, host_index, memc->poll_timeout); + switch (err) { + case -1: + memc->cached_errno = errno; + /* FALLTHROUGH */ + case 0: + break; + default: + for (size_t x= 0; x < host_index; ++x) + { + if (fds[x].revents & POLLIN) + { + for (uint32_t y= 0; y < memcached_server_count(memc); ++y) + { + memcached_server_instance_st *instance= + memcached_server_instance_fetch(memc, y); + + if (instance->fd == fds[x].fd) + return instance; + } + } + } + } + + return NULL; +} + +static ssize_t io_flush(memcached_server_instance_st *ptr, + memcached_return_t *error) +{ + /* + ** We might want to purge the input buffer if we haven't consumed + ** any output yet... The test for the limits is the purge is inline + ** in the purge function to avoid duplicating the logic.. + */ + { + memcached_return_t rc; + WATCHPOINT_ASSERT(ptr->fd != -1); + rc= memcached_purge(ptr); + + if (rc != MEMCACHED_SUCCESS && rc != MEMCACHED_STORED) + return -1; + } + ssize_t sent_length; + size_t return_length; + char *local_write_ptr= ptr->write_buffer; + size_t write_length= ptr->write_buffer_offset; + + *error= MEMCACHED_SUCCESS; + + WATCHPOINT_ASSERT(ptr->fd != -1); + + // UDP Sanity check, make sure that we are not sending somthing too big + if (ptr->type == MEMCACHED_CONNECTION_UDP && write_length > MAX_UDP_DATAGRAM_LENGTH) + return -1; + + if (ptr->write_buffer_offset == 0 || (ptr->type == MEMCACHED_CONNECTION_UDP + && ptr->write_buffer_offset == UDP_DATAGRAM_HEADER_LENGTH)) + return 0; + + /* Looking for memory overflows */ +#if defined(DEBUG) + if (write_length == MEMCACHED_MAX_BUFFER) + WATCHPOINT_ASSERT(ptr->write_buffer == local_write_ptr); + WATCHPOINT_ASSERT((ptr->write_buffer + MEMCACHED_MAX_BUFFER) >= (local_write_ptr + write_length)); +#endif + + return_length= 0; + while (write_length) + { + WATCHPOINT_ASSERT(ptr->fd != -1); + WATCHPOINT_ASSERT(write_length > 0); + sent_length= 0; + if (ptr->type == MEMCACHED_CONNECTION_UDP) + increment_udp_message_id(ptr); + sent_length= write(ptr->fd, local_write_ptr, write_length); + + if (sent_length == -1) + { + ptr->cached_errno= errno; + switch (errno) + { + case ENOBUFS: + continue; + case EAGAIN: + { + /* + * We may be blocked on write because the input buffer + * is full. Let's check if we have room in our input + * buffer for more data and retry the write before + * waiting.. + */ + if (repack_input_buffer(ptr) || + process_input_buffer(ptr)) + continue; + + memcached_return_t rc; + rc= io_wait(ptr, MEM_WRITE); + + if (rc == MEMCACHED_SUCCESS || rc == MEMCACHED_TIMEOUT) + continue; + + memcached_quit_server(ptr, 1); + return -1; + } + default: + memcached_quit_server(ptr, 1); + *error= MEMCACHED_ERRNO; + return -1; + } + } + + if (ptr->type == MEMCACHED_CONNECTION_UDP && + (size_t)sent_length != write_length) + { + memcached_quit_server(ptr, 1); + return -1; + } + + ptr->io_bytes_sent += (uint32_t) sent_length; + + local_write_ptr+= sent_length; + write_length-= (uint32_t) sent_length; + return_length+= (uint32_t) sent_length; + } + + WATCHPOINT_ASSERT(write_length == 0); + // Need to study this assert() WATCHPOINT_ASSERT(return_length == + // ptr->write_buffer_offset); + + // if we are a udp server, the begining of the buffer is reserverd for + // the upd frame header + if (ptr->type == MEMCACHED_CONNECTION_UDP) + ptr->write_buffer_offset= UDP_DATAGRAM_HEADER_LENGTH; + else + ptr->write_buffer_offset= 0; + + return (ssize_t) return_length; +} + +/* + Eventually we will just kill off the server with the problem. +*/ +void memcached_io_reset(memcached_server_instance_st *ptr) +{ + memcached_quit_server(ptr, 1); +} + +/** + * Read a given number of bytes from the server and place it into a specific + * buffer. Reset the IO channel on this server if an error occurs. + */ +memcached_return_t memcached_safe_read(memcached_server_instance_st *ptr, + void *dta, + size_t size) +{ + size_t offset= 0; + char *data= dta; + + while (offset < size) + { + ssize_t nread; + memcached_return_t rc= memcached_io_read(ptr, data + offset, size - offset, + &nread); + if (rc != MEMCACHED_SUCCESS) + return rc; + + offset+= (size_t) nread; + } + + return MEMCACHED_SUCCESS; +} + +memcached_return_t memcached_io_readline(memcached_server_instance_st *ptr, + char *buffer_ptr, + size_t size) +{ + bool line_complete= false; + size_t total_nr= 0; + + while (!line_complete) + { + if (ptr->read_buffer_length == 0) + { + /* + * We don't have any data in the buffer, so let's fill the read + * buffer. Call the standard read function to avoid duplicating + * the logic. + */ + ssize_t nread; + memcached_return_t rc= memcached_io_read(ptr, buffer_ptr, 1, &nread); + if (rc != MEMCACHED_SUCCESS) + return rc; + + if (*buffer_ptr == '\n') + line_complete= true; + + ++buffer_ptr; + ++total_nr; + } + + /* Now let's look in the buffer and copy as we go! */ + while (ptr->read_buffer_length && total_nr < size && !line_complete) + { + *buffer_ptr = *ptr->read_ptr; + if (*buffer_ptr == '\n') + line_complete = true; + --ptr->read_buffer_length; + ++ptr->read_ptr; + ++total_nr; + ++buffer_ptr; + } + + if (total_nr == size) + return MEMCACHED_PROTOCOL_ERROR; + } + + return MEMCACHED_SUCCESS; +} + +/* + * The udp request id consists of two seperate sections + * 1) The thread id + * 2) The message number + * The thread id should only be set when the memcached_st struct is created + * and should not be changed. + * + * The message num is incremented for each new message we send, this function + * extracts the message number from message_id, increments it and then + * writes the new value back into the header + */ +static void increment_udp_message_id(memcached_server_instance_st *ptr) +{ + struct udp_datagram_header_st *header= (struct udp_datagram_header_st *)ptr->write_buffer; + uint16_t cur_req= get_udp_datagram_request_id(header); + int msg_num= get_msg_num_from_request_id(cur_req); + int thread_id= get_thread_id_from_request_id(cur_req); + + if (((++msg_num) & UDP_REQUEST_ID_THREAD_MASK) != 0) + msg_num= 0; + + header->request_id= htons((uint16_t) (thread_id | msg_num)); +} + +memcached_return_t memcached_io_init_udp_header(memcached_server_instance_st *ptr, uint16_t thread_id) +{ + if (thread_id > UDP_REQUEST_ID_MAX_THREAD_ID) + return MEMCACHED_FAILURE; + + struct udp_datagram_header_st *header= (struct udp_datagram_header_st *)ptr->write_buffer; + header->request_id= htons((uint16_t) (generate_udp_request_thread_id(thread_id))); + header->num_datagrams= htons(1); + header->sequence_number= htons(0); + + return MEMCACHED_SUCCESS; +} diff --cc libmemcached/memcached.c index 203f0984,4e8ec062..027ef20a --- a/libmemcached/memcached.c +++ b/libmemcached/memcached.c @@@ -37,6 -30,6 +37,10 @@@ memcached_st *memcached_create(memcache ptr->retry_timeout= 0; ptr->distribution= MEMCACHED_DISTRIBUTION_MODULA; ++ ++ ptr->send_size= -1; ++ ptr->recv_size= -1; ++ /* TODO, Document why we picked these defaults */ ptr->io_msg_watermark= 500; ptr->io_bytes_watermark= 65 * 1024; diff --cc libmemcached/memcached.h index e4510d6d,722da814..d86f9bc9 --- a/libmemcached/memcached.h +++ b/libmemcached/memcached.h @@@ -63,27 -88,9 +63,28 @@@ struct memcached_st uint32_t io_bytes_watermark; uint32_t io_key_prefetch; uint32_t number_of_hosts; - uint32_t cursor_server; int cached_errno; - uint32_t flags; + /** + @note these are static and should not change without a call to behavior. + */ + struct { + bool auto_eject_hosts:1; + bool binary_protocol:1; + bool buffer_requests:1; + bool hash_with_prefix_key:1; + bool ketama_weighted:1; + bool no_block:1; + bool no_reply:1; + bool randomize_replica_read:1; + bool reuse_memory:1; + bool support_cas:1; + bool tcp_nodelay:1; + bool use_cache_lookups:1; + bool use_sort_hosts:1; + bool use_udp:1; + bool verify_key:1; ++ bool cork:1; + } flags; int32_t poll_timeout; int32_t connect_timeout; int32_t retry_timeout; diff --cc libmemcached/server.c index 0d343f1d,00000000..f61f572e mode 100644,000000..100644 --- a/libmemcached/server.c +++ b/libmemcached/server.c @@@ -1,194 -1,0 +1,195 @@@ +/* LibMemcached + * Copyright (C) 2006-2009 Brian Aker + * All rights reserved. + * + * Use and distribution licensed under the BSD license. See + * the COPYING file in the parent directory for full text. + * + * Summary: String structure used for libmemcached. + * + */ + +/* + This is a partial implementation for fetching/creating memcached_server_st objects. +*/ +#include "common.h" + +memcached_server_st *memcached_server_create(memcached_st *memc, memcached_server_st *ptr) +{ + if (ptr == NULL) + { + ptr= (memcached_server_st *)memc->call_calloc(memc, 1, sizeof(memcached_server_st)); + + if (!ptr) + return NULL; /* MEMCACHED_MEMORY_ALLOCATION_FAILURE */ + + ptr->options.is_allocated= true; + } + else + { + memset(ptr, 0, sizeof(memcached_server_st)); + } + + ptr->root= memc; + + return ptr; +} + +memcached_server_st *memcached_server_create_with(memcached_st *memc, memcached_server_st *host, + const char *hostname, in_port_t port, + uint32_t weight, memcached_connection_t type) +{ + host= memcached_server_create(memc, host); + + if (host == NULL) + return NULL; + + strncpy(host->hostname, hostname, MEMCACHED_MAX_HOST_LENGTH - 1); + host->root= memc ? memc : NULL; + host->port= port; + host->weight= weight; + host->fd= -1; + host->type= type; + host->read_ptr= host->read_buffer; ++ host->state.is_corked= 0; + if (memc) + host->next_retry= memc->retry_timeout; + if (type == MEMCACHED_CONNECTION_UDP) + { + host->write_buffer_offset= UDP_DATAGRAM_HEADER_LENGTH; + memcached_io_init_udp_header(host, 0); + } + + return host; +} + +void memcached_server_free(memcached_server_st *ptr) +{ + memcached_quit_server(ptr, 0); + + if (ptr->cached_server_error) + free(ptr->cached_server_error); + + if (ptr->address_info) + freeaddrinfo(ptr->address_info); + + + if (memcached_is_allocated(ptr)) + { + ptr->root->call_free(ptr->root, ptr); + } + else + { + memset(ptr, 0, sizeof(memcached_server_st)); + } +} + +/* + If we do not have a valid object to clone from, we toss an error. +*/ +memcached_server_st *memcached_server_clone(memcached_server_st *clone, memcached_server_st *ptr) +{ + memcached_server_st *rv= NULL; + + /* We just do a normal create if ptr is missing */ + if (ptr == NULL) + return NULL; + + rv= memcached_server_create_with(ptr->root, clone, + ptr->hostname, ptr->port, ptr->weight, + ptr->type); + if (rv != NULL) + { + rv->cached_errno= ptr->cached_errno; + if (ptr->cached_server_error) + rv->cached_server_error= strdup(ptr->cached_server_error); + } + + return rv; + +} + +memcached_return_t memcached_server_cursor(memcached_st *ptr, + memcached_server_fn *callback, + void *context, + uint32_t number_of_callbacks) +{ + uint32_t y; + + for (y= 0; y < memcached_server_count(ptr); y++) + { + uint32_t x; + memcached_server_instance_st *instance= + memcached_server_instance_fetch(ptr, y); + + for (x= 0; x < number_of_callbacks; x++) + { + unsigned int iferror; + + iferror= (*callback[x])(ptr, instance, context); + + if (iferror) + continue; + } + } + + return MEMCACHED_SUCCESS; +} + +memcached_server_st *memcached_server_by_key(memcached_st *ptr, const char *key, size_t key_length, memcached_return_t *error) +{ + uint32_t server_key; + memcached_server_instance_st *instance; + + *error= memcached_validate_key_length(key_length, + ptr->flags.binary_protocol); + unlikely (*error != MEMCACHED_SUCCESS) + return NULL; + + unlikely (memcached_server_count(ptr) == 0) + { + *error= MEMCACHED_NO_SERVERS; + return NULL; + } + + if (ptr->flags.verify_key && (memcached_key_test((const char **)&key, &key_length, 1) == MEMCACHED_BAD_KEY_PROVIDED)) + { + *error= MEMCACHED_BAD_KEY_PROVIDED; + return NULL; + } + + server_key= memcached_generate_hash(ptr, key, key_length); + instance= memcached_server_instance_fetch(ptr, server_key); + + return memcached_server_clone(NULL, instance); + +} + +const char *memcached_server_error(memcached_server_st *ptr) +{ + return ptr + ? ptr->cached_server_error + : NULL; +} + +void memcached_server_error_reset(memcached_server_st *ptr) +{ + ptr->cached_server_error[0]= 0; +} + +memcached_server_st *memcached_server_get_last_disconnect(memcached_st *ptr) +{ + return ptr->last_disconnected_server; +} + +uint32_t memcached_server_list_count(memcached_server_st *ptr) +{ + return (ptr == NULL) + ? 0 + : memcached_servers_count(ptr); +} + +void memcached_server_list_free(memcached_server_st *ptr) +{ + server_list_free(NULL, ptr); +} diff --cc libmemcached/server.h index 1b99ecbb,00000000..0051e061 mode 100644,000000..100644 --- a/libmemcached/server.h +++ b/libmemcached/server.h @@@ -1,120 -1,0 +1,123 @@@ +/* LibMemcached + * Copyright (C) 2006-2009 Brian Aker + * All rights reserved. + * + * Use and distribution licensed under the BSD license. See + * the COPYING file in the parent directory for full text. + * + * Summary: String structure used for libmemcached. + * + */ + +#ifndef __MEMCACHED_SERVER_H__ +#define __MEMCACHED_SERVER_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +struct memcached_server_st { + struct { + bool is_allocated:1; + bool sockaddr_inited:1; + } options; + uint32_t number_of_hosts; + uint32_t cursor_active; + in_port_t port; + int cached_errno; + int fd; + uint32_t io_bytes_sent; /* # bytes sent since last read */ + uint32_t server_failure_counter; + uint32_t weight; ++ struct { // Place any "state" sort variables in here. ++ bool is_corked; ++ } state; + uint8_t major_version; + uint8_t micro_version; + uint8_t minor_version; + memcached_connection_t type; + char *read_ptr; + char *cached_server_error; + size_t read_buffer_length; + size_t read_data_length; + size_t write_buffer_offset; + struct addrinfo *address_info; + time_t next_retry; + memcached_st *root; + uint64_t limit_maxbytes; + char read_buffer[MEMCACHED_MAX_BUFFER]; + char write_buffer[MEMCACHED_MAX_BUFFER]; + char hostname[MEMCACHED_MAX_HOST_LENGTH]; +}; + +// Local Only Inline +static inline uint32_t memcached_servers_count(memcached_server_st *servers) +{ + return servers->number_of_hosts; +} + +// Local Only Inline +static inline uint32_t memcached_servers_set_count(memcached_server_st *servers, uint32_t count) +{ + return servers->number_of_hosts= count; +} + + + +#define memcached_server_count(A) (A)->number_of_hosts +#define memcached_server_name(A,B) (B).hostname +#define memcached_server_port(A,B) (B).port +#define memcached_server_list(A) (A)->servers +#define memcached_server_list_set(A,B) (A)->servers=(B) +#define memcached_server_response_count(A) (A)->cursor_active + +LIBMEMCACHED_API +memcached_return_t memcached_server_cursor(memcached_st *ptr, + memcached_server_fn *callback, + void *context, + uint32_t number_of_callbacks); + +LIBMEMCACHED_API +memcached_server_st *memcached_server_by_key(memcached_st *ptr, + const char *key, + size_t key_length, + memcached_return_t *error); + +LIBMEMCACHED_API +const char *memcached_server_error(memcached_server_st *ptr); + +LIBMEMCACHED_API +void memcached_server_error_reset(memcached_server_st *ptr); + +/* These should not currently be used by end users */ +/* TODO: Is the above comment valid? If so, how can we unit test these if they + * aren't exported. If not, we should remove the comment */ +LIBMEMCACHED_LOCAL +memcached_server_st *memcached_server_create(memcached_st *memc, memcached_server_st *ptr); + +LIBMEMCACHED_LOCAL +memcached_server_st *memcached_server_create_with(memcached_st *memc, + memcached_server_st *host, + const char *hostname, + in_port_t port, + uint32_t weight, + memcached_connection_t type); + +LIBMEMCACHED_API +void memcached_server_free(memcached_server_st *ptr); + +LIBMEMCACHED_LOCAL +memcached_server_st *memcached_server_clone(memcached_server_st *clone, + memcached_server_st *ptr); + +LIBMEMCACHED_API +memcached_return_t memcached_server_remove(memcached_server_st *st_ptr); + +LIBMEMCACHED_API +memcached_server_st *memcached_server_get_last_disconnect(memcached_st *ptr); + +#ifdef __cplusplus +} +#endif + +#endif /* __MEMCACHED_SERVER_H__ */ diff --cc tests/mem_functions.c index 9ec6b751,00000000..3b545c86 mode 100644,000000..100644 --- a/tests/mem_functions.c +++ b/tests/mem_functions.c @@@ -1,6013 -1,0 +1,6063 @@@ +/* libMemcached Functions Test + * Copyright (C) 2006-2009 Brian Aker + * All rights reserved. + * + * Use and distribution licensed under the BSD license. See + * the COPYING file in the parent directory for full text. + */ + +/* + Sample test application. +*/ + +#include "libmemcached/common.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "server.h" +#include "clients/generator.h" +#include "clients/execute.h" + +#ifndef INT64_MAX +#define INT64_MAX LONG_MAX +#endif +#ifndef INT32_MAX +#define INT32_MAX INT_MAX +#endif + + +#include "test.h" + +#ifdef HAVE_LIBMEMCACHEDUTIL +#include +#include "libmemcached/memcached_util.h" +#endif + +#include "hash_results.h" + +#define GLOBAL_COUNT 10000 +#define GLOBAL2_COUNT 100 +#define SERVERS_TO_CREATE 5 +static uint32_t global_count; + +static pairs_st *global_pairs; +static const char *global_keys[GLOBAL_COUNT]; +static size_t global_keys_length[GLOBAL_COUNT]; + +static test_return_t init_test(memcached_st *not_used __attribute__((unused))) +{ + memcached_st memc; + + (void)memcached_create(&memc); + memcached_free(&memc); + + return TEST_SUCCESS; +} + +static test_return_t server_list_null_test(memcached_st *ptr __attribute__((unused))) +{ + memcached_server_st *server_list; + memcached_return_t rc; + + server_list= memcached_server_list_append_with_weight(NULL, NULL, 0, 0, NULL); + test_truth(server_list == NULL); + + server_list= memcached_server_list_append_with_weight(NULL, "localhost", 0, 0, NULL); + test_truth(server_list == NULL); + + server_list= memcached_server_list_append_with_weight(NULL, NULL, 0, 0, &rc); + test_truth(server_list == NULL); + + return TEST_SUCCESS; +} + +#define TEST_PORT_COUNT 7 +in_port_t test_ports[TEST_PORT_COUNT]; + +static memcached_return_t server_display_function(memcached_st *ptr __attribute__((unused)), memcached_server_st *server, void *context) +{ + /* Do Nothing */ + size_t bigger= *((size_t *)(context)); + assert(bigger <= server->port); + *((size_t *)(context))= server->port; + + return MEMCACHED_SUCCESS; +} + +static test_return_t server_sort_test(memcached_st *ptr __attribute__((unused))) +{ + size_t bigger= 0; /* Prime the value for the test_truth in server_display_function */ + + memcached_return_t rc; + memcached_server_fn callbacks[1]; + memcached_st *local_memc; + + local_memc= memcached_create(NULL); + test_truth(local_memc); + memcached_behavior_set(local_memc, MEMCACHED_BEHAVIOR_SORT_HOSTS, 1); + + for (size_t x= 0; x < TEST_PORT_COUNT; x++) + { + test_ports[x]= (in_port_t)random() % 64000; + rc= memcached_server_add_with_weight(local_memc, "localhost", test_ports[x], 0); + test_truth(memcached_server_count(local_memc) == x + 1); + test_truth(memcached_servers_count(memcached_server_list(local_memc)) == x+1); + test_truth(rc == MEMCACHED_SUCCESS); + } + + callbacks[0]= server_display_function; + memcached_server_cursor(local_memc, callbacks, (void *)&bigger, 1); + + + memcached_free(local_memc); + + return TEST_SUCCESS; +} + +static test_return_t server_sort2_test(memcached_st *ptr __attribute__((unused))) +{ + size_t bigger= 0; /* Prime the value for the test_truth in server_display_function */ + memcached_return_t rc; + memcached_server_fn callbacks[1]; + memcached_st *local_memc; + memcached_server_instance_st *instance; + + local_memc= memcached_create(NULL); + test_truth(local_memc); + rc= memcached_behavior_set(local_memc, MEMCACHED_BEHAVIOR_SORT_HOSTS, 1); + test_truth(rc == MEMCACHED_SUCCESS); + + rc= memcached_server_add_with_weight(local_memc, "MEMCACHED_BEHAVIOR_SORT_HOSTS", 43043, 0); + test_truth(rc == MEMCACHED_SUCCESS); + instance= memcached_server_instance_fetch(local_memc, 0); + test_truth(instance->port == 43043); + + rc= memcached_server_add_with_weight(local_memc, "MEMCACHED_BEHAVIOR_SORT_HOSTS", 43042, 0); + test_truth(rc == MEMCACHED_SUCCESS); + + instance= memcached_server_instance_fetch(local_memc, 0); + test_truth(instance->port == 43042); + + instance= memcached_server_instance_fetch(local_memc, 1); + test_truth(instance->port == 43043); + + callbacks[0]= server_display_function; + memcached_server_cursor(local_memc, callbacks, (void *)&bigger, 1); + + + memcached_free(local_memc); + + return TEST_SUCCESS; +} + +static memcached_return_t server_display_unsort_function(memcached_st *ptr __attribute__((unused)), memcached_server_st *server, void *context) +{ + /* Do Nothing */ + uint32_t x= *((uint32_t *)(context)); + + assert(test_ports[x] == server->port); + *((uint32_t *)(context))= ++x; + + return MEMCACHED_SUCCESS; +} + +static test_return_t server_unsort_test(memcached_st *ptr __attribute__((unused))) +{ + size_t counter= 0; /* Prime the value for the test_truth in server_display_function */ + size_t bigger= 0; /* Prime the value for the test_truth in server_display_function */ + memcached_return_t rc; + memcached_server_fn callbacks[1]; + memcached_st *local_memc; + + local_memc= memcached_create(NULL); + test_truth(local_memc); + + for (size_t x= 0; x < TEST_PORT_COUNT; x++) + { + test_ports[x]= (in_port_t)(random() % 64000); + rc= memcached_server_add_with_weight(local_memc, "localhost", test_ports[x], 0); + test_truth(memcached_server_count(local_memc) == x+1); + test_truth(memcached_servers_count(memcached_server_list(local_memc)) == x+1); + test_truth(rc == MEMCACHED_SUCCESS); + } + + callbacks[0]= server_display_unsort_function; + memcached_server_cursor(local_memc, callbacks, (void *)&counter, 1); + + /* Now we sort old data! */ + memcached_behavior_set(local_memc, MEMCACHED_BEHAVIOR_SORT_HOSTS, 1); + callbacks[0]= server_display_function; + memcached_server_cursor(local_memc, callbacks, (void *)&bigger, 1); + + + memcached_free(local_memc); + + return TEST_SUCCESS; +} + +static test_return_t allocation_test(memcached_st *not_used __attribute__((unused))) +{ + memcached_st *memc; + memc= memcached_create(NULL); + test_truth(memc); + memcached_free(memc); + + return TEST_SUCCESS; +} + +static test_return_t clone_test(memcached_st *memc) +{ + /* All null? */ + { + memcached_st *memc_clone; + memc_clone= memcached_clone(NULL, NULL); + test_truth(memc_clone); + memcached_free(memc_clone); + } + + /* Can we init from null? */ + { + memcached_st *memc_clone; + memc_clone= memcached_clone(NULL, memc); + test_truth(memc_clone); + + test_truth(memc_clone->call_free == memc->call_free); + test_truth(memc_clone->call_malloc == memc->call_malloc); + test_truth(memc_clone->call_realloc == memc->call_realloc); + test_truth(memc_clone->call_calloc == memc->call_calloc); + test_truth(memc_clone->connect_timeout == memc->connect_timeout); + test_truth(memc_clone->delete_trigger == memc->delete_trigger); + test_truth(memc_clone->distribution == memc->distribution); + { // Test all of the flags + test_truth(memc_clone->flags.no_block == memc->flags.no_block); + test_truth(memc_clone->flags.tcp_nodelay == memc->flags.tcp_nodelay); + test_truth(memc_clone->flags.reuse_memory == memc->flags.reuse_memory); + test_truth(memc_clone->flags.use_cache_lookups == memc->flags.use_cache_lookups); + test_truth(memc_clone->flags.support_cas == memc->flags.support_cas); + test_truth(memc_clone->flags.buffer_requests == memc->flags.buffer_requests); + test_truth(memc_clone->flags.use_sort_hosts == memc->flags.use_sort_hosts); + test_truth(memc_clone->flags.verify_key == memc->flags.verify_key); + test_truth(memc_clone->flags.ketama_weighted == memc->flags.ketama_weighted); + test_truth(memc_clone->flags.binary_protocol == memc->flags.binary_protocol); + test_truth(memc_clone->flags.hash_with_prefix_key == memc->flags.hash_with_prefix_key); + test_truth(memc_clone->flags.no_reply == memc->flags.no_reply); + test_truth(memc_clone->flags.use_udp == memc->flags.use_udp); + test_truth(memc_clone->flags.auto_eject_hosts == memc->flags.auto_eject_hosts); + test_truth(memc_clone->flags.randomize_replica_read == memc->flags.randomize_replica_read); + } + test_truth(memc_clone->get_key_failure == memc->get_key_failure); + test_truth(memc_clone->hash == memc->hash); + test_truth(memc_clone->distribution_hash == memc->distribution_hash); + test_truth(memc_clone->io_bytes_watermark == memc->io_bytes_watermark); + test_truth(memc_clone->io_msg_watermark == memc->io_msg_watermark); + test_truth(memc_clone->io_key_prefetch == memc->io_key_prefetch); + test_truth(memc_clone->on_cleanup == memc->on_cleanup); + test_truth(memc_clone->on_clone == memc->on_clone); + test_truth(memc_clone->poll_timeout == memc->poll_timeout); + test_truth(memc_clone->rcv_timeout == memc->rcv_timeout); + test_truth(memc_clone->recv_size == memc->recv_size); + test_truth(memc_clone->retry_timeout == memc->retry_timeout); + test_truth(memc_clone->send_size == memc->send_size); + test_truth(memc_clone->server_failure_limit == memc->server_failure_limit); + test_truth(memc_clone->snd_timeout == memc->snd_timeout); + test_truth(memc_clone->user_data == memc->user_data); + + memcached_free(memc_clone); + } + + /* Can we init from struct? */ + { + memcached_st declared_clone; + memcached_st *memc_clone; + memset(&declared_clone, 0 , sizeof(memcached_st)); + memc_clone= memcached_clone(&declared_clone, NULL); + test_truth(memc_clone); + memcached_free(memc_clone); + } + + /* Can we init from struct? */ + { + memcached_st declared_clone; + memcached_st *memc_clone; + memset(&declared_clone, 0 , sizeof(memcached_st)); + memc_clone= memcached_clone(&declared_clone, memc); + test_truth(memc_clone); + memcached_free(memc_clone); + } + + return TEST_SUCCESS; +} + +static test_return_t userdata_test(memcached_st *memc) +{ + void* foo= NULL; + test_truth(memcached_set_user_data(memc, foo) == NULL); + test_truth(memcached_get_user_data(memc) == foo); + test_truth(memcached_set_user_data(memc, NULL) == foo); + + return TEST_SUCCESS; +} + +static test_return_t connection_test(memcached_st *memc) +{ + memcached_return_t rc; + + rc= memcached_server_add_with_weight(memc, "localhost", 0, 0); + test_truth(rc == MEMCACHED_SUCCESS); + + return TEST_SUCCESS; +} + +static test_return_t error_test(memcached_st *memc) +{ + memcached_return_t rc; + uint32_t values[] = { 851992627U, 2337886783U, 3196981036U, 4001849190U, + 982370485U, 1263635348U, 4242906218U, 3829656100U, + 1891735253U, 334139633U, 2257084983U, 3088286104U, + 13199785U, 2542027183U, 1097051614U, 199566778U, + 2748246961U, 2465192557U, 1664094137U, 2405439045U, + 1842224848U, 692413798U, 3479807801U, 919913813U, + 4269430871U, 610793021U, 527273862U, 1437122909U, + 2300930706U, 2943759320U, 674306647U, 2400528935U, + 54481931U, 4186304426U, 1741088401U, 2979625118U, + 4159057246U, 3425930182U, 2593724503U}; + + // You have updated the memcache_error messages but not updated docs/tests. + test_truth(MEMCACHED_MAXIMUM_RETURN == 39); + for (rc= MEMCACHED_SUCCESS; rc < MEMCACHED_MAXIMUM_RETURN; rc++) + { + uint32_t hash_val; + const char *msg= memcached_strerror(memc, rc); + hash_val= memcached_generate_hash_value(msg, strlen(msg), + MEMCACHED_HASH_JENKINS); + test_truth(values[rc] == hash_val); + } + + return TEST_SUCCESS; +} + +static test_return_t set_test(memcached_st *memc) +{ + memcached_return_t rc; + const char *key= "foo"; + const char *value= "when we sanitize"; + + rc= memcached_set(memc, key, strlen(key), + value, strlen(value), + (time_t)0, (uint32_t)0); + test_truth(rc == MEMCACHED_SUCCESS || rc == MEMCACHED_BUFFERED); + + return TEST_SUCCESS; +} + +static test_return_t append_test(memcached_st *memc) +{ + memcached_return_t rc; + const char *key= "fig"; + const char *in_value= "we"; + char *out_value= NULL; + size_t value_length; + uint32_t flags; + + rc= memcached_flush(memc, 0); + test_truth(rc == MEMCACHED_SUCCESS); + + rc= memcached_set(memc, key, strlen(key), + in_value, strlen(in_value), + (time_t)0, (uint32_t)0); + test_truth(rc == MEMCACHED_SUCCESS); + + rc= memcached_append(memc, key, strlen(key), + " the", strlen(" the"), + (time_t)0, (uint32_t)0); + test_truth(rc == MEMCACHED_SUCCESS); + + rc= memcached_append(memc, key, strlen(key), + " people", strlen(" people"), + (time_t)0, (uint32_t)0); + test_truth(rc == MEMCACHED_SUCCESS); + + out_value= memcached_get(memc, key, strlen(key), + &value_length, &flags, &rc); + test_truth(!memcmp(out_value, "we the people", strlen("we the people"))); + test_truth(strlen("we the people") == value_length); + test_truth(rc == MEMCACHED_SUCCESS); + free(out_value); + + return TEST_SUCCESS; +} + +static test_return_t append_binary_test(memcached_st *memc) +{ + memcached_return_t rc; + const char *key= "numbers"; + uint32_t store_list[] = { 23, 56, 499, 98, 32847, 0 }; + uint32_t *value; + size_t value_length; + uint32_t flags; + uint32_t x; + + rc= memcached_flush(memc, 0); + test_truth(rc == MEMCACHED_SUCCESS); + + rc= memcached_set(memc, + key, strlen(key), + NULL, 0, + (time_t)0, (uint32_t)0); + test_truth(rc == MEMCACHED_SUCCESS); + + for (x= 0; store_list[x] ; x++) + { + rc= memcached_append(memc, + key, strlen(key), + (char *)&store_list[x], sizeof(uint32_t), + (time_t)0, (uint32_t)0); + test_truth(rc == MEMCACHED_SUCCESS); + } + + value= (uint32_t *)memcached_get(memc, key, strlen(key), + &value_length, &flags, &rc); + test_truth((value_length == (sizeof(uint32_t) * x))); + test_truth(rc == MEMCACHED_SUCCESS); + + for (uint32_t counter= x, *ptr= value; counter; counter--) + { + test_truth(*ptr == store_list[x - counter]); + ptr++; + } + free(value); + + return TEST_SUCCESS; +} + +static test_return_t cas2_test(memcached_st *memc) +{ + memcached_return_t rc; + const char *keys[]= {"fudge", "son", "food"}; + size_t key_length[]= {5, 3, 4}; + const char *value= "we the people"; + size_t value_length= strlen("we the people"); + unsigned int x; + memcached_result_st results_obj; + memcached_result_st *results; + unsigned int set= 1; + + rc= memcached_flush(memc, 0); + test_truth(rc == MEMCACHED_SUCCESS); + + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_SUPPORT_CAS, set); + + for (x= 0; x < 3; x++) + { + rc= memcached_set(memc, keys[x], key_length[x], + keys[x], key_length[x], + (time_t)50, (uint32_t)9); + test_truth(rc == MEMCACHED_SUCCESS); + } + + rc= memcached_mget(memc, keys, key_length, 3); + + results= memcached_result_create(memc, &results_obj); + + results= memcached_fetch_result(memc, &results_obj, &rc); + test_truth(results); + test_truth(results->cas); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(memcached_result_cas(results)); + + test_truth(!memcmp(value, "we the people", strlen("we the people"))); + test_truth(strlen("we the people") == value_length); + test_truth(rc == MEMCACHED_SUCCESS); + + memcached_result_free(&results_obj); + + return TEST_SUCCESS; +} + +static test_return_t cas_test(memcached_st *memc) +{ + memcached_return_t rc; + const char *key= "fun"; + size_t key_length= strlen(key); + const char *value= "we the people"; + const char* keys[2] = { key, NULL }; + size_t keylengths[2] = { strlen(key), 0 }; + size_t value_length= strlen(value); + const char *value2= "change the value"; + size_t value2_length= strlen(value2); + + memcached_result_st results_obj; + memcached_result_st *results; + unsigned int set= 1; + + rc= memcached_flush(memc, 0); + test_truth(rc == MEMCACHED_SUCCESS); + + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_SUPPORT_CAS, set); + + rc= memcached_set(memc, key, strlen(key), + value, strlen(value), + (time_t)0, (uint32_t)0); + test_truth(rc == MEMCACHED_SUCCESS); + + rc= memcached_mget(memc, keys, keylengths, 1); + + results= memcached_result_create(memc, &results_obj); + + results= memcached_fetch_result(memc, &results_obj, &rc); + test_truth(results); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(memcached_result_cas(results)); + test_truth(!memcmp(value, memcached_result_value(results), value_length)); + test_truth(strlen(memcached_result_value(results)) == value_length); + test_truth(rc == MEMCACHED_SUCCESS); + uint64_t cas = memcached_result_cas(results); + + #if 0 + results= memcached_fetch_result(memc, &results_obj, &rc); + test_truth(rc == MEMCACHED_END); + test_truth(results == NULL); +#endif + + rc= memcached_cas(memc, key, key_length, value2, value2_length, 0, 0, cas); + test_truth(rc == MEMCACHED_SUCCESS); + + /* + * The item will have a new cas value, so try to set it again with the old + * value. This should fail! + */ + rc= memcached_cas(memc, key, key_length, value2, value2_length, 0, 0, cas); + test_truth(rc == MEMCACHED_DATA_EXISTS); + + memcached_result_free(&results_obj); + + return TEST_SUCCESS; +} + +static test_return_t prepend_test(memcached_st *memc) +{ + memcached_return_t rc; + const char *key= "fig"; + const char *value= "people"; + char *out_value= NULL; + size_t value_length; + uint32_t flags; + + rc= memcached_flush(memc, 0); + test_truth(rc == MEMCACHED_SUCCESS); + + rc= memcached_set(memc, key, strlen(key), + value, strlen(value), + (time_t)0, (uint32_t)0); + test_truth(rc == MEMCACHED_SUCCESS); + + rc= memcached_prepend(memc, key, strlen(key), + "the ", strlen("the "), + (time_t)0, (uint32_t)0); + test_truth(rc == MEMCACHED_SUCCESS); + + rc= memcached_prepend(memc, key, strlen(key), + "we ", strlen("we "), + (time_t)0, (uint32_t)0); + test_truth(rc == MEMCACHED_SUCCESS); + + out_value= memcached_get(memc, key, strlen(key), + &value_length, &flags, &rc); + test_truth(!memcmp(out_value, "we the people", strlen("we the people"))); + test_truth(strlen("we the people") == value_length); + test_truth(rc == MEMCACHED_SUCCESS); + free(out_value); + + return TEST_SUCCESS; +} + +/* + Set the value, then quit to make sure it is flushed. + Come back in and test that add fails. +*/ +static test_return_t add_test(memcached_st *memc) +{ + memcached_return_t rc; + const char *key= "foo"; + const char *value= "when we sanitize"; + unsigned long long setting_value; + + setting_value= memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_NO_BLOCK); + + rc= memcached_set(memc, key, strlen(key), + value, strlen(value), + (time_t)0, (uint32_t)0); + test_truth(rc == MEMCACHED_SUCCESS || rc == MEMCACHED_BUFFERED); + memcached_quit(memc); + rc= memcached_add(memc, key, strlen(key), + value, strlen(value), + (time_t)0, (uint32_t)0); + + /* Too many broken OS'es have broken loopback in async, so we can't be sure of the result */ + if (setting_value) + { + test_truth(rc == MEMCACHED_NOTSTORED || rc == MEMCACHED_STORED); + } + else + { + test_truth(rc == MEMCACHED_NOTSTORED || rc == MEMCACHED_DATA_EXISTS); + } + + return TEST_SUCCESS; +} + +/* +** There was a problem of leaking filedescriptors in the initial release +** of MacOSX 10.5. This test case triggers the problem. On some Solaris +** systems it seems that the kernel is slow on reclaiming the resources +** because the connects starts to time out (the test doesn't do much +** anyway, so just loop 10 iterations) +*/ +static test_return_t add_wrapper(memcached_st *memc) +{ + unsigned int x; + unsigned int max= 10000; +#ifdef __sun + max= 10; +#endif +#ifdef __APPLE__ + max= 10; +#endif + + for (x= 0; x < max; x++) + add_test(memc); + + return TEST_SUCCESS; +} + +static test_return_t replace_test(memcached_st *memc) +{ + memcached_return_t rc; + const char *key= "foo"; + const char *value= "when we sanitize"; + const char *original= "first we insert some data"; + + rc= memcached_set(memc, key, strlen(key), + original, strlen(original), + (time_t)0, (uint32_t)0); + test_truth(rc == MEMCACHED_SUCCESS || rc == MEMCACHED_BUFFERED); + + rc= memcached_replace(memc, key, strlen(key), + value, strlen(value), + (time_t)0, (uint32_t)0); + test_truth(rc == MEMCACHED_SUCCESS); + + return TEST_SUCCESS; +} + +static test_return_t delete_test(memcached_st *memc) +{ + memcached_return_t rc; + const char *key= "foo"; + const char *value= "when we sanitize"; + + rc= memcached_set(memc, key, strlen(key), + value, strlen(value), + (time_t)0, (uint32_t)0); + test_truth(rc == MEMCACHED_SUCCESS || rc == MEMCACHED_BUFFERED); + + rc= memcached_delete(memc, key, strlen(key), (time_t)0); + test_truth(rc == MEMCACHED_SUCCESS || rc == MEMCACHED_BUFFERED); + + return TEST_SUCCESS; +} + +static test_return_t flush_test(memcached_st *memc) +{ + memcached_return_t rc; + + rc= memcached_flush(memc, 0); + test_truth(rc == MEMCACHED_SUCCESS); + + return TEST_SUCCESS; +} + +static memcached_return_t server_function(memcached_st *ptr __attribute__((unused)), + memcached_server_st *server __attribute__((unused)), + void *context __attribute__((unused))) +{ + /* Do Nothing */ + + return MEMCACHED_SUCCESS; +} + +static test_return_t memcached_server_cursor_test(memcached_st *memc) +{ + char context[8]; + strcpy(context, "foo bad"); + memcached_server_fn callbacks[1]; + + callbacks[0]= server_function; + memcached_server_cursor(memc, callbacks, context, 1); + return TEST_SUCCESS; +} + +static test_return_t bad_key_test(memcached_st *memc) +{ + memcached_return_t rc; + const char *key= "foo bad"; + char *string; + size_t string_length; + uint32_t flags; + memcached_st *memc_clone; + unsigned int set= 1; + size_t max_keylen= 0xffff; + + // Just skip if we are in binary mode. + if (memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL)) + return TEST_SKIPPED; + + memc_clone= memcached_clone(NULL, memc); + test_truth(memc_clone); + + rc= memcached_behavior_set(memc_clone, MEMCACHED_BEHAVIOR_VERIFY_KEY, set); + test_truth(rc == MEMCACHED_SUCCESS); + + /* All keys are valid in the binary protocol (except for length) */ + if (memcached_behavior_get(memc_clone, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL) == 0) + { + string= memcached_get(memc_clone, key, strlen(key), + &string_length, &flags, &rc); + test_truth(rc == MEMCACHED_BAD_KEY_PROVIDED); + test_truth(string_length == 0); + test_truth(!string); + + set= 0; + rc= memcached_behavior_set(memc_clone, MEMCACHED_BEHAVIOR_VERIFY_KEY, set); + test_truth(rc == MEMCACHED_SUCCESS); + string= memcached_get(memc_clone, key, strlen(key), + &string_length, &flags, &rc); + test_truth(rc == MEMCACHED_NOTFOUND); + test_truth(string_length == 0); + test_truth(!string); + + /* Test multi key for bad keys */ + const char *keys[] = { "GoodKey", "Bad Key", "NotMine" }; + size_t key_lengths[] = { 7, 7, 7 }; + set= 1; + rc= memcached_behavior_set(memc_clone, MEMCACHED_BEHAVIOR_VERIFY_KEY, set); + test_truth(rc == MEMCACHED_SUCCESS); + + rc= memcached_mget(memc_clone, keys, key_lengths, 3); + test_truth(rc == MEMCACHED_BAD_KEY_PROVIDED); + + rc= memcached_mget_by_key(memc_clone, "foo daddy", 9, keys, key_lengths, 1); + test_truth(rc == MEMCACHED_BAD_KEY_PROVIDED); + + max_keylen= 250; + + /* The following test should be moved to the end of this function when the + memcached server is updated to allow max size length of the keys in the + binary protocol + */ + rc= memcached_callback_set(memc_clone, MEMCACHED_CALLBACK_PREFIX_KEY, NULL); + test_truth(rc == MEMCACHED_SUCCESS); + + char *longkey= malloc(max_keylen + 1); + if (longkey != NULL) + { + memset(longkey, 'a', max_keylen + 1); + string= memcached_get(memc_clone, longkey, max_keylen, + &string_length, &flags, &rc); + test_truth(rc == MEMCACHED_NOTFOUND); + test_truth(string_length == 0); + test_truth(!string); + + string= memcached_get(memc_clone, longkey, max_keylen + 1, + &string_length, &flags, &rc); + test_truth(rc == MEMCACHED_BAD_KEY_PROVIDED); + test_truth(string_length == 0); + test_truth(!string); + + free(longkey); + } + } + + /* Make sure zero length keys are marked as bad */ + set= 1; + rc= memcached_behavior_set(memc_clone, MEMCACHED_BEHAVIOR_VERIFY_KEY, set); + test_truth(rc == MEMCACHED_SUCCESS); + string= memcached_get(memc_clone, key, 0, + &string_length, &flags, &rc); + test_truth(rc == MEMCACHED_BAD_KEY_PROVIDED); + test_truth(string_length == 0); + test_truth(!string); + + memcached_free(memc_clone); + + return TEST_SUCCESS; +} + +#define READ_THROUGH_VALUE "set for me" +static memcached_return_t read_through_trigger(memcached_st *memc __attribute__((unused)), + char *key __attribute__((unused)), + size_t key_length __attribute__((unused)), + memcached_result_st *result) +{ + + return memcached_result_set_value(result, READ_THROUGH_VALUE, strlen(READ_THROUGH_VALUE)); +} + +static test_return_t read_through(memcached_st *memc) +{ + memcached_return_t rc; + const char *key= "foo"; + char *string; + size_t string_length; + uint32_t flags; + memcached_trigger_key_fn cb= (memcached_trigger_key_fn)read_through_trigger; + + string= memcached_get(memc, key, strlen(key), + &string_length, &flags, &rc); + + test_truth(rc == MEMCACHED_NOTFOUND); + test_false(string_length); + test_false(string); + + rc= memcached_callback_set(memc, MEMCACHED_CALLBACK_GET_FAILURE, + *(void **)&cb); + test_truth(rc == MEMCACHED_SUCCESS); + + string= memcached_get(memc, key, strlen(key), + &string_length, &flags, &rc); + + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(string_length == strlen(READ_THROUGH_VALUE)); + test_strcmp(READ_THROUGH_VALUE, string); + free(string); + + string= memcached_get(memc, key, strlen(key), + &string_length, &flags, &rc); + + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(string_length == strlen(READ_THROUGH_VALUE)); + test_truth(!strcmp(READ_THROUGH_VALUE, string)); + free(string); + + return TEST_SUCCESS; +} + +static memcached_return_t delete_trigger(memcached_st *ptr __attribute__((unused)), + const char *key, + size_t key_length __attribute__((unused))) +{ + assert(key); + + return MEMCACHED_SUCCESS; +} + +static test_return_t delete_through(memcached_st *memc) +{ + memcached_trigger_delete_key_fn callback; + memcached_return_t rc; + + callback= (memcached_trigger_delete_key_fn)delete_trigger; + + rc= memcached_callback_set(memc, MEMCACHED_CALLBACK_DELETE_TRIGGER, *(void**)&callback); + test_truth(rc == MEMCACHED_SUCCESS); + + return TEST_SUCCESS; +} + +static test_return_t get_test(memcached_st *memc) +{ + memcached_return_t rc; + const char *key= "foo"; + char *string; + size_t string_length; + uint32_t flags; + + rc= memcached_delete(memc, key, strlen(key), (time_t)0); + test_truth(rc == MEMCACHED_BUFFERED || rc == MEMCACHED_NOTFOUND); + + string= memcached_get(memc, key, strlen(key), + &string_length, &flags, &rc); + + test_truth(rc == MEMCACHED_NOTFOUND); + test_false(string_length); + test_false(string); + + return TEST_SUCCESS; +} + +static test_return_t get_test2(memcached_st *memc) +{ + memcached_return_t rc; + const char *key= "foo"; + const char *value= "when we sanitize"; + char *string; + size_t string_length; + uint32_t flags; + + rc= memcached_set(memc, key, strlen(key), + value, strlen(value), + (time_t)0, (uint32_t)0); + test_truth(rc == MEMCACHED_SUCCESS || rc == MEMCACHED_BUFFERED); + + string= memcached_get(memc, key, strlen(key), + &string_length, &flags, &rc); + + test_truth(string); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(string_length == strlen(value)); + test_truth(!memcmp(string, value, string_length)); + + free(string); + + return TEST_SUCCESS; +} + +static test_return_t set_test2(memcached_st *memc) +{ + memcached_return_t rc; + const char *key= "foo"; + const char *value= "train in the brain"; + size_t value_length= strlen(value); + unsigned int x; + + for (x= 0; x < 10; x++) + { + rc= memcached_set(memc, key, strlen(key), + value, value_length, + (time_t)0, (uint32_t)0); + test_truth(rc == MEMCACHED_SUCCESS || rc == MEMCACHED_BUFFERED); + } + + return TEST_SUCCESS; +} + +static test_return_t set_test3(memcached_st *memc) +{ + memcached_return_t rc; + char *value; + size_t value_length= 8191; + unsigned int x; + + value = (char*)malloc(value_length); + test_truth(value); + + for (x= 0; x < value_length; x++) + value[x] = (char) (x % 127); + + /* The dump test relies on there being at least 32 items in memcached */ + for (x= 0; x < 32; x++) + { + char key[16]; + + sprintf(key, "foo%u", x); + + rc= memcached_set(memc, key, strlen(key), + value, value_length, + (time_t)0, (uint32_t)0); + test_truth(rc == MEMCACHED_SUCCESS || rc == MEMCACHED_BUFFERED); + } + + free(value); + + return TEST_SUCCESS; +} + +static test_return_t get_test3(memcached_st *memc) +{ + memcached_return_t rc; + const char *key= "foo"; + char *value; + size_t value_length= 8191; + char *string; + size_t string_length; + uint32_t flags; + uint32_t x; + + value = (char*)malloc(value_length); + test_truth(value); + + for (x= 0; x < value_length; x++) + value[x] = (char) (x % 127); + + rc= memcached_set(memc, key, strlen(key), + value, value_length, + (time_t)0, (uint32_t)0); + test_truth(rc == MEMCACHED_SUCCESS || rc == MEMCACHED_BUFFERED); + + string= memcached_get(memc, key, strlen(key), + &string_length, &flags, &rc); + + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(string); + test_truth(string_length == value_length); + test_truth(!memcmp(string, value, string_length)); + + free(string); + free(value); + + return TEST_SUCCESS; +} + +static test_return_t get_test4(memcached_st *memc) +{ + memcached_return_t rc; + const char *key= "foo"; + char *value; + size_t value_length= 8191; + char *string; + size_t string_length; + uint32_t flags; + uint32_t x; + + value = (char*)malloc(value_length); + test_truth(value); + + for (x= 0; x < value_length; x++) + value[x] = (char) (x % 127); + + rc= memcached_set(memc, key, strlen(key), + value, value_length, + (time_t)0, (uint32_t)0); + test_truth(rc == MEMCACHED_SUCCESS || rc == MEMCACHED_BUFFERED); + + for (x= 0; x < 10; x++) + { + string= memcached_get(memc, key, strlen(key), + &string_length, &flags, &rc); + + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(string); + test_truth(string_length == value_length); + test_truth(!memcmp(string, value, string_length)); + free(string); + } + + free(value); + + return TEST_SUCCESS; +} + +/* + * This test verifies that memcached_read_one_response doesn't try to + * dereference a NIL-pointer if you issue a multi-get and don't read out all + * responses before you execute a storage command. + */ +static test_return_t get_test5(memcached_st *memc) +{ + /* + ** Request the same key twice, to ensure that we hash to the same server + ** (so that we have multiple response values queued up) ;-) + */ + const char *keys[]= { "key", "key" }; + size_t lengths[]= { 3, 3 }; + uint32_t flags; + size_t rlen; + + memcached_return_t rc= memcached_set(memc, keys[0], lengths[0], + keys[0], lengths[0], 0, 0); + test_truth(rc == MEMCACHED_SUCCESS); + rc= memcached_mget(memc, keys, lengths, 2); + + memcached_result_st results_obj; + memcached_result_st *results; + results=memcached_result_create(memc, &results_obj); + test_truth(results); + results=memcached_fetch_result(memc, &results_obj, &rc); + test_truth(results); + memcached_result_free(&results_obj); + + /* Don't read out the second result, but issue a set instead.. */ + rc= memcached_set(memc, keys[0], lengths[0], keys[0], lengths[0], 0, 0); + test_truth(rc == MEMCACHED_SUCCESS); + + char *val= memcached_get_by_key(memc, keys[0], lengths[0], "yek", 3, + &rlen, &flags, &rc); + test_truth(val == NULL); + test_truth(rc == MEMCACHED_NOTFOUND); + val= memcached_get(memc, keys[0], lengths[0], &rlen, &flags, &rc); + test_truth(val != NULL); + test_truth(rc == MEMCACHED_SUCCESS); + free(val); + + return TEST_SUCCESS; +} + +static test_return_t mget_end(memcached_st *memc) +{ + const char *keys[]= { "foo", "foo2" }; + size_t lengths[]= { 3, 4 }; + const char *values[]= { "fjord", "41" }; + + memcached_return_t rc; + + // Set foo and foo2 + for (int i= 0; i < 2; i++) + { + rc= memcached_set(memc, keys[i], lengths[i], values[i], strlen(values[i]), + (time_t)0, (uint32_t)0); + test_truth(rc == MEMCACHED_SUCCESS); + } + + char *string; + size_t string_length; + uint32_t flags; + + // retrieve both via mget + rc= memcached_mget(memc, keys, lengths, 2); + test_truth(rc == MEMCACHED_SUCCESS); + + char key[MEMCACHED_MAX_KEY]; + size_t key_length; + + // this should get both + for (int i = 0; i < 2; i++) + { + string= memcached_fetch(memc, key, &key_length, &string_length, + &flags, &rc); + test_truth(rc == MEMCACHED_SUCCESS); + int val = 0; + if (key_length == 4) + val= 1; + test_truth(string_length == strlen(values[val])); + test_truth(strncmp(values[val], string, string_length) == 0); + free(string); + } + + // this should indicate end + string= memcached_fetch(memc, key, &key_length, &string_length, &flags, &rc); + test_truth(rc == MEMCACHED_END); + + // now get just one + rc= memcached_mget(memc, keys, lengths, 1); + test_truth(rc == MEMCACHED_SUCCESS); + + string= memcached_fetch(memc, key, &key_length, &string_length, &flags, &rc); + test_truth(key_length == lengths[0]); + test_truth(strncmp(keys[0], key, key_length) == 0); + test_truth(string_length == strlen(values[0])); + test_truth(strncmp(values[0], string, string_length) == 0); + test_truth(rc == MEMCACHED_SUCCESS); + free(string); + + // this should indicate end + string= memcached_fetch(memc, key, &key_length, &string_length, &flags, &rc); + test_truth(rc == MEMCACHED_END); + + return TEST_SUCCESS; +} + +/* Do not copy the style of this code, I just access hosts to testthis function */ +static test_return_t stats_servername_test(memcached_st *memc) +{ + memcached_return_t rc; + memcached_stat_st memc_stat; + memcached_server_instance_st *instance= + memcached_server_instance_fetch(memc, 0); + + rc= memcached_stat_servername(&memc_stat, NULL, + instance->hostname, + instance->port); + + return TEST_SUCCESS; +} + +static test_return_t increment_test(memcached_st *memc) +{ + uint64_t new_number; + memcached_return_t rc; + const char *key= "number"; + const char *value= "0"; + + rc= memcached_set(memc, key, strlen(key), + value, strlen(value), + (time_t)0, (uint32_t)0); + test_truth(rc == MEMCACHED_SUCCESS || rc == MEMCACHED_BUFFERED); + + rc= memcached_increment(memc, key, strlen(key), + 1, &new_number); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(new_number == 1); + + rc= memcached_increment(memc, key, strlen(key), + 1, &new_number); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(new_number == 2); + + return TEST_SUCCESS; +} + +static test_return_t increment_with_initial_test(memcached_st *memc) +{ + if (memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL) != 0) + { + uint64_t new_number; + memcached_return_t rc; + const char *key= "number"; + uint64_t initial= 0; + + rc= memcached_increment_with_initial(memc, key, strlen(key), + 1, initial, 0, &new_number); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(new_number == initial); + + rc= memcached_increment_with_initial(memc, key, strlen(key), + 1, initial, 0, &new_number); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(new_number == (initial + 1)); + } + return TEST_SUCCESS; +} + +static test_return_t decrement_test(memcached_st *memc) +{ + uint64_t new_number; + memcached_return_t rc; + const char *key= "number"; + const char *value= "3"; + + rc= memcached_set(memc, key, strlen(key), + value, strlen(value), + (time_t)0, (uint32_t)0); + test_truth(rc == MEMCACHED_SUCCESS || rc == MEMCACHED_BUFFERED); + + rc= memcached_decrement(memc, key, strlen(key), + 1, &new_number); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(new_number == 2); + + rc= memcached_decrement(memc, key, strlen(key), + 1, &new_number); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(new_number == 1); + + return TEST_SUCCESS; +} + +static test_return_t decrement_with_initial_test(memcached_st *memc) +{ + if (memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL) != 0) + { + uint64_t new_number; + memcached_return_t rc; + const char *key= "number"; + uint64_t initial= 3; + + rc= memcached_decrement_with_initial(memc, key, strlen(key), + 1, initial, 0, &new_number); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(new_number == initial); + + rc= memcached_decrement_with_initial(memc, key, strlen(key), + 1, initial, 0, &new_number); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(new_number == (initial - 1)); + } + return TEST_SUCCESS; +} + +static test_return_t increment_by_key_test(memcached_st *memc) +{ + uint64_t new_number; + memcached_return_t rc; + const char *master_key= "foo"; + const char *key= "number"; + const char *value= "0"; + + rc= memcached_set_by_key(memc, master_key, strlen(master_key), + key, strlen(key), + value, strlen(value), + (time_t)0, (uint32_t)0); + test_truth(rc == MEMCACHED_SUCCESS || rc == MEMCACHED_BUFFERED); + + rc= memcached_increment_by_key(memc, master_key, strlen(master_key), key, strlen(key), + 1, &new_number); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(new_number == 1); + + rc= memcached_increment_by_key(memc, master_key, strlen(master_key), key, strlen(key), + 1, &new_number); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(new_number == 2); + + return TEST_SUCCESS; +} + +static test_return_t increment_with_initial_by_key_test(memcached_st *memc) +{ + if (memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL) != 0) + { + uint64_t new_number; + memcached_return_t rc; + const char *master_key= "foo"; + const char *key= "number"; + uint64_t initial= 0; + + rc= memcached_increment_with_initial_by_key(memc, master_key, strlen(master_key), + key, strlen(key), + 1, initial, 0, &new_number); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(new_number == initial); + + rc= memcached_increment_with_initial_by_key(memc, master_key, strlen(master_key), + key, strlen(key), + 1, initial, 0, &new_number); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(new_number == (initial + 1)); + } + return TEST_SUCCESS; +} + +static test_return_t decrement_by_key_test(memcached_st *memc) +{ + uint64_t new_number; + memcached_return_t rc; + const char *master_key= "foo"; + const char *key= "number"; + const char *value= "3"; + + rc= memcached_set_by_key(memc, master_key, strlen(master_key), + key, strlen(key), + value, strlen(value), + (time_t)0, (uint32_t)0); + test_truth(rc == MEMCACHED_SUCCESS || rc == MEMCACHED_BUFFERED); + + rc= memcached_decrement_by_key(memc, master_key, strlen(master_key), + key, strlen(key), + 1, &new_number); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(new_number == 2); + + rc= memcached_decrement_by_key(memc, master_key, strlen(master_key), + key, strlen(key), + 1, &new_number); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(new_number == 1); + + return TEST_SUCCESS; +} + +static test_return_t decrement_with_initial_by_key_test(memcached_st *memc) +{ + if (memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL) != 0) + { + uint64_t new_number; + memcached_return_t rc; + const char *master_key= "foo"; + const char *key= "number"; + uint64_t initial= 3; + + rc= memcached_decrement_with_initial_by_key(memc, master_key, strlen(master_key), + key, strlen(key), + 1, initial, 0, &new_number); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(new_number == initial); + + rc= memcached_decrement_with_initial_by_key(memc, master_key, strlen(master_key), + key, strlen(key), + 1, initial, 0, &new_number); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(new_number == (initial - 1)); + } + return TEST_SUCCESS; +} + +static test_return_t quit_test(memcached_st *memc) +{ + memcached_return_t rc; + const char *key= "fudge"; + const char *value= "sanford and sun"; + + rc= memcached_set(memc, key, strlen(key), + value, strlen(value), + (time_t)10, (uint32_t)3); + test_truth(rc == MEMCACHED_SUCCESS || rc == MEMCACHED_BUFFERED); + memcached_quit(memc); + + rc= memcached_set(memc, key, strlen(key), + value, strlen(value), + (time_t)50, (uint32_t)9); + test_truth(rc == MEMCACHED_SUCCESS || rc == MEMCACHED_BUFFERED); + + return TEST_SUCCESS; +} + +static test_return_t mget_result_test(memcached_st *memc) +{ + memcached_return_t rc; + const char *keys[]= {"fudge", "son", "food"}; + size_t key_length[]= {5, 3, 4}; + unsigned int x; + + memcached_result_st results_obj; + memcached_result_st *results; + + results= memcached_result_create(memc, &results_obj); + test_truth(results); + test_truth(&results_obj == results); + + /* We need to empty the server before continueing test */ + rc= memcached_flush(memc, 0); + test_truth(rc == MEMCACHED_SUCCESS); + + rc= memcached_mget(memc, keys, key_length, 3); + test_truth(rc == MEMCACHED_SUCCESS); + + while ((results= memcached_fetch_result(memc, &results_obj, &rc)) != NULL) + { + test_truth(results); + } + + while ((results= memcached_fetch_result(memc, &results_obj, &rc)) != NULL) + test_truth(!results); + test_truth(rc == MEMCACHED_END); + + for (x= 0; x < 3; x++) + { + rc= memcached_set(memc, keys[x], key_length[x], + keys[x], key_length[x], + (time_t)50, (uint32_t)9); + test_truth(rc == MEMCACHED_SUCCESS || rc == MEMCACHED_BUFFERED); + } + + rc= memcached_mget(memc, keys, key_length, 3); + test_truth(rc == MEMCACHED_SUCCESS); + + while ((results= memcached_fetch_result(memc, &results_obj, &rc))) + { + test_truth(results); + test_truth(&results_obj == results); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(memcached_result_key_length(results) == memcached_result_length(results)); + test_truth(!memcmp(memcached_result_key_value(results), + memcached_result_value(results), + memcached_result_length(results))); + } + + memcached_result_free(&results_obj); + + return TEST_SUCCESS; +} + +static test_return_t mget_result_alloc_test(memcached_st *memc) +{ + memcached_return_t rc; + const char *keys[]= {"fudge", "son", "food"}; + size_t key_length[]= {5, 3, 4}; + unsigned int x; + + memcached_result_st *results; + + /* We need to empty the server before continueing test */ + rc= memcached_flush(memc, 0); + test_truth(rc == MEMCACHED_SUCCESS); + + rc= memcached_mget(memc, keys, key_length, 3); + test_truth(rc == MEMCACHED_SUCCESS); + + while ((results= memcached_fetch_result(memc, NULL, &rc)) != NULL) + { + test_truth(results); + } + test_truth(!results); + test_truth(rc == MEMCACHED_END); + + for (x= 0; x < 3; x++) + { + rc= memcached_set(memc, keys[x], key_length[x], + keys[x], key_length[x], + (time_t)50, (uint32_t)9); + test_truth(rc == MEMCACHED_SUCCESS || rc == MEMCACHED_BUFFERED); + } + + rc= memcached_mget(memc, keys, key_length, 3); + test_truth(rc == MEMCACHED_SUCCESS); + + x= 0; + while ((results= memcached_fetch_result(memc, NULL, &rc))) + { + test_truth(results); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(memcached_result_key_length(results) == memcached_result_length(results)); + test_truth(!memcmp(memcached_result_key_value(results), + memcached_result_value(results), + memcached_result_length(results))); + memcached_result_free(results); + x++; + } + + return TEST_SUCCESS; +} + +/* Count the results */ +static memcached_return_t callback_counter(memcached_st *ptr __attribute__((unused)), + memcached_result_st *result __attribute__((unused)), + void *context) +{ + size_t *counter= (size_t *)context; + + *counter= *counter + 1; + + return MEMCACHED_SUCCESS; +} + +static test_return_t mget_result_function(memcached_st *memc) +{ + memcached_return_t rc; + const char *keys[]= {"fudge", "son", "food"}; + size_t key_length[]= {5, 3, 4}; + unsigned int x; + size_t counter; + memcached_execute_fn callbacks[1]; + + /* We need to empty the server before continueing test */ + rc= memcached_flush(memc, 0); + for (x= 0; x < 3; x++) + { + rc= memcached_set(memc, keys[x], key_length[x], + keys[x], key_length[x], + (time_t)50, (uint32_t)9); + test_truth(rc == MEMCACHED_SUCCESS || rc == MEMCACHED_BUFFERED); + } + + rc= memcached_mget(memc, keys, key_length, 3); + test_truth(rc == MEMCACHED_SUCCESS); + + callbacks[0]= &callback_counter; + counter= 0; + rc= memcached_fetch_execute(memc, callbacks, (void *)&counter, 1); + + test_truth(counter == 3); + + return TEST_SUCCESS; +} + +static test_return_t mget_test(memcached_st *memc) +{ + memcached_return_t rc; + const char *keys[]= {"fudge", "son", "food"}; + size_t key_length[]= {5, 3, 4}; + unsigned int x; + uint32_t flags; + + char return_key[MEMCACHED_MAX_KEY]; + size_t return_key_length; + char *return_value; + size_t return_value_length; + + /* We need to empty the server before continueing test */ + rc= memcached_flush(memc, 0); + test_truth(rc == MEMCACHED_SUCCESS); + + rc= memcached_mget(memc, keys, key_length, 3); + test_truth(rc == MEMCACHED_SUCCESS); + + while ((return_value= memcached_fetch(memc, return_key, &return_key_length, + &return_value_length, &flags, &rc)) != NULL) + { + test_truth(return_value); + } + test_truth(!return_value); + test_truth(return_value_length == 0); + test_truth(rc == MEMCACHED_END); + + for (x= 0; x < 3; x++) + { + rc= memcached_set(memc, keys[x], key_length[x], + keys[x], key_length[x], + (time_t)50, (uint32_t)9); + test_truth(rc == MEMCACHED_SUCCESS || rc == MEMCACHED_BUFFERED); + } + + rc= memcached_mget(memc, keys, key_length, 3); + test_truth(rc == MEMCACHED_SUCCESS); + + x= 0; + while ((return_value= memcached_fetch(memc, return_key, &return_key_length, + &return_value_length, &flags, &rc))) + { + test_truth(return_value); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(return_key_length == return_value_length); + test_truth(!memcmp(return_value, return_key, return_value_length)); + free(return_value); + x++; + } + + return TEST_SUCCESS; +} + +static test_return_t mget_execute(memcached_st *memc) +{ + bool binary= false; + + if (memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL) != 0) + binary= true; + + /* + * I only want to hit _one_ server so I know the number of requests I'm + * sending in the pipeline. + */ + uint32_t number_of_hosts= memc->number_of_hosts; + memc->number_of_hosts= 1; + + size_t max_keys= binary ? 20480 : 1; + + + char **keys= calloc(max_keys, sizeof(char*)); + size_t *key_length=calloc(max_keys, sizeof(size_t)); + + /* First add all of the items.. */ + char blob[1024] = {0}; + memcached_return_t rc; + for (size_t x= 0; x < max_keys; ++x) + { + char k[251]; + + key_length[x]= (size_t)snprintf(k, sizeof(k), "0200%zu", x); + keys[x]= strdup(k); + test_truth(keys[x] != NULL); + rc= memcached_add(memc, keys[x], key_length[x], blob, sizeof(blob), 0, 0); + test_truth(rc == MEMCACHED_SUCCESS || rc == MEMCACHED_BUFFERED); + } + + /* Try to get all of them with a large multiget */ + size_t counter= 0; + memcached_execute_fn callbacks[1]= { [0]= &callback_counter }; + rc= memcached_mget_execute(memc, (const char**)keys, key_length, + max_keys, callbacks, &counter, 1); + + if (binary) + { + test_truth(rc == MEMCACHED_SUCCESS); + + rc= memcached_fetch_execute(memc, callbacks, (void *)&counter, 1); + test_truth(rc == MEMCACHED_END); + + /* Verify that we got all of the items */ + test_truth(counter == max_keys); + } + else + { + test_truth(rc == MEMCACHED_NOT_SUPPORTED); + test_truth(counter == 0); + } + + /* Release all allocated resources */ + for (size_t x= 0; x < max_keys; ++x) + { + free(keys[x]); + } + free(keys); + free(key_length); + + memc->number_of_hosts= number_of_hosts; + return TEST_SUCCESS; +} + +static test_return_t get_stats_keys(memcached_st *memc) +{ + char **stat_list; + char **ptr; + memcached_stat_st memc_stat; + memcached_return_t rc; + + stat_list= memcached_stat_get_keys(memc, &memc_stat, &rc); + test_truth(rc == MEMCACHED_SUCCESS); + for (ptr= stat_list; *ptr; ptr++) + test_truth(*ptr); + + free(stat_list); + + return TEST_SUCCESS; +} + +static test_return_t version_string_test(memcached_st *memc __attribute__((unused))) +{ + const char *version_string; + + version_string= memcached_lib_version(); + + test_truth(!strcmp(version_string, LIBMEMCACHED_VERSION_STRING)); + + return TEST_SUCCESS; +} + +static test_return_t get_stats(memcached_st *memc) +{ + unsigned int x; + char **stat_list; + char **ptr; + memcached_return_t rc; + memcached_stat_st *memc_stat; + + memc_stat= memcached_stat(memc, NULL, &rc); + test_truth(rc == MEMCACHED_SUCCESS); + + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(memc_stat); + + for (x= 0; x < memcached_server_count(memc); x++) + { + stat_list= memcached_stat_get_keys(memc, memc_stat+x, &rc); + test_truth(rc == MEMCACHED_SUCCESS); + for (ptr= stat_list; *ptr; ptr++); + + free(stat_list); + } + + memcached_stat_free(NULL, memc_stat); + + return TEST_SUCCESS; +} + +static test_return_t add_host_test(memcached_st *memc) +{ + unsigned int x; + memcached_server_st *servers; + memcached_return_t rc; + char servername[]= "0.example.com"; + + servers= memcached_server_list_append_with_weight(NULL, servername, 400, 0, &rc); + test_truth(servers); + test_truth(1 == memcached_server_list_count(servers)); + + for (x= 2; x < 20; x++) + { + char buffer[SMALL_STRING_LEN]; + + snprintf(buffer, SMALL_STRING_LEN, "%u.example.com", 400+x); + servers= memcached_server_list_append_with_weight(servers, buffer, 401, 0, + &rc); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(x == memcached_server_list_count(servers)); + } + + rc= memcached_server_push(memc, servers); + test_truth(rc == MEMCACHED_SUCCESS); + rc= memcached_server_push(memc, servers); + test_truth(rc == MEMCACHED_SUCCESS); + + memcached_server_list_free(servers); + + return TEST_SUCCESS; +} + +static memcached_return_t clone_test_callback(memcached_st *parent __attribute__((unused)), memcached_st *memc_clone __attribute__((unused))) +{ + return MEMCACHED_SUCCESS; +} + +static memcached_return_t cleanup_test_callback(memcached_st *ptr __attribute__((unused))) +{ + return MEMCACHED_SUCCESS; +} + +static test_return_t callback_test(memcached_st *memc) +{ + /* Test User Data */ + { + int x= 5; + int *test_ptr; + memcached_return_t rc; + + rc= memcached_callback_set(memc, MEMCACHED_CALLBACK_USER_DATA, &x); + test_truth(rc == MEMCACHED_SUCCESS); + test_ptr= (int *)memcached_callback_get(memc, MEMCACHED_CALLBACK_USER_DATA, &rc); + test_truth(*test_ptr == x); + } + + /* Test Clone Callback */ + { + memcached_clone_fn clone_cb= (memcached_clone_fn)clone_test_callback; + void *clone_cb_ptr= *(void **)&clone_cb; + void *temp_function= NULL; + memcached_return_t rc; + + rc= memcached_callback_set(memc, MEMCACHED_CALLBACK_CLONE_FUNCTION, + clone_cb_ptr); + test_truth(rc == MEMCACHED_SUCCESS); + temp_function= memcached_callback_get(memc, MEMCACHED_CALLBACK_CLONE_FUNCTION, &rc); + test_truth(temp_function == clone_cb_ptr); + } + + /* Test Cleanup Callback */ + { + memcached_cleanup_fn cleanup_cb= + (memcached_cleanup_fn)cleanup_test_callback; + void *cleanup_cb_ptr= *(void **)&cleanup_cb; + void *temp_function= NULL; + memcached_return_t rc; + + rc= memcached_callback_set(memc, MEMCACHED_CALLBACK_CLONE_FUNCTION, + cleanup_cb_ptr); + test_truth(rc == MEMCACHED_SUCCESS); + temp_function= memcached_callback_get(memc, MEMCACHED_CALLBACK_CLONE_FUNCTION, &rc); + test_truth(temp_function == cleanup_cb_ptr); + } + + return TEST_SUCCESS; +} + +/* We don't test the behavior itself, we test the switches */ +static test_return_t behavior_test(memcached_st *memc) +{ + uint64_t value; + uint32_t set= 1; + + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_NO_BLOCK, set); + value= memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_NO_BLOCK); + test_truth(value == 1); + + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_TCP_NODELAY, set); + value= memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_TCP_NODELAY); + test_truth(value == 1); + + set= MEMCACHED_HASH_MD5; + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_HASH, set); + value= memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_HASH); + test_truth(value == MEMCACHED_HASH_MD5); + + set= 0; + + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_NO_BLOCK, set); + value= memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_NO_BLOCK); + test_truth(value == 0); + + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_TCP_NODELAY, set); + value= memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_TCP_NODELAY); + test_truth(value == 0); + + set= MEMCACHED_HASH_DEFAULT; + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_HASH, set); + value= memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_HASH); + test_truth(value == MEMCACHED_HASH_DEFAULT); + + set= MEMCACHED_HASH_CRC; + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_HASH, set); + value= memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_HASH); + test_truth(value == MEMCACHED_HASH_CRC); + + value= memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_SOCKET_SEND_SIZE); + test_truth(value > 0); + + value= memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_SOCKET_RECV_SIZE); + test_truth(value > 0); + + value= memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_NUMBER_OF_REPLICAS); + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_NUMBER_OF_REPLICAS, value + 1); + test_truth((value + 1) == memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_NUMBER_OF_REPLICAS)); + + return TEST_SUCCESS; +} + ++static test_return_t MEMCACHED_BEHAVIOR_CORK_test(memcached_st *memc) ++{ ++ memcached_return_t rc; ++ bool set= true; ++ bool value; ++ ++ rc= memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_CORK, set); ++#ifdef TCP_CORK ++ test_truth(rc == MEMCACHED_SUCCESS); ++#else ++ test_truth(rc == MEMCACHED_NOT_SUPPORTED); ++#endif ++ ++ value= (bool)memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_CORK); ++#ifdef TCP_CORK ++ test_truth((bool)value == set); ++#else ++ test_false((bool)value == set); ++#endif ++ ++ return TEST_SUCCESS; ++} ++ +static test_return_t fetch_all_results(memcached_st *memc) +{ + memcached_return_t rc= MEMCACHED_SUCCESS; + char return_key[MEMCACHED_MAX_KEY]; + size_t return_key_length; + char *return_value; + size_t return_value_length; + uint32_t flags; + + while ((return_value= memcached_fetch(memc, return_key, &return_key_length, + &return_value_length, &flags, &rc))) + { + test_truth(return_value); + test_truth(rc == MEMCACHED_SUCCESS); + free(return_value); + } + + return ((rc == MEMCACHED_END) || (rc == MEMCACHED_SUCCESS)) ? TEST_SUCCESS : TEST_FAILURE; +} + +/* Test case provided by Cal Haldenbrand */ +static test_return_t user_supplied_bug1(memcached_st *memc) +{ + unsigned int setter= 1; + + unsigned long long total= 0; + uint32_t size= 0; + char key[10]; + char randomstuff[6 * 1024]; + memcached_return_t rc; + + memset(randomstuff, 0, 6 * 1024); + + /* We just keep looking at the same values over and over */ + srandom(10); + + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_NO_BLOCK, setter); + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_TCP_NODELAY, setter); + + + /* add key */ + for (uint32_t x= 0 ; total < 20 * 1024576 ; x++ ) + { + unsigned int j= 0; + + size= (uint32_t)(rand() % ( 5 * 1024 ) ) + 400; + memset(randomstuff, 0, 6 * 1024); + test_truth(size < 6 * 1024); /* Being safe here */ + + for (j= 0 ; j < size ;j++) + randomstuff[j] = (signed char) ((rand() % 26) + 97); + + total += size; + snprintf(key, sizeof(key), "%u", x); + rc = memcached_set(memc, key, strlen(key), + randomstuff, strlen(randomstuff), 10, 0); + test_truth(rc == MEMCACHED_SUCCESS || rc == MEMCACHED_BUFFERED); + /* If we fail, lets try again */ + if (rc != MEMCACHED_SUCCESS && rc != MEMCACHED_BUFFERED) + rc = memcached_set(memc, key, strlen(key), + randomstuff, strlen(randomstuff), 10, 0); + test_truth(rc == MEMCACHED_SUCCESS || rc == MEMCACHED_BUFFERED); + } + + return TEST_SUCCESS; +} + +/* Test case provided by Cal Haldenbrand */ +static test_return_t user_supplied_bug2(memcached_st *memc) +{ + unsigned int setter; + size_t total= 0; + + setter= 1; + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_NO_BLOCK, setter); + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_TCP_NODELAY, setter); +#ifdef NOT_YET + setter = 20 * 1024576; + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_SOCKET_SEND_SIZE, setter); + setter = 20 * 1024576; + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_SOCKET_RECV_SIZE, setter); + getter = memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_SOCKET_SEND_SIZE); + getter = memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_SOCKET_RECV_SIZE); + + for (x= 0, errors= 0; total < 20 * 1024576 ; x++) +#endif + + for (uint32_t x= 0, errors= 0; total < 24576 ; x++) + { + memcached_return_t rc= MEMCACHED_SUCCESS; + char buffer[SMALL_STRING_LEN]; + uint32_t flags= 0; + size_t val_len= 0; + char *getval; + + memset(buffer, 0, SMALL_STRING_LEN); + + snprintf(buffer, sizeof(buffer), "%u", x); + getval= memcached_get(memc, buffer, strlen(buffer), + &val_len, &flags, &rc); + if (rc != MEMCACHED_SUCCESS) + { + if (rc == MEMCACHED_NOTFOUND) + errors++; + else + { + test_truth(rc); + } + + continue; + } + total+= val_len; + errors= 0; + free(getval); + } + + return TEST_SUCCESS; +} + +/* Do a large mget() over all the keys we think exist */ +#define KEY_COUNT 3000 // * 1024576 +static test_return_t user_supplied_bug3(memcached_st *memc) +{ + memcached_return_t rc; + unsigned int setter; + unsigned int x; + char **keys; + size_t key_lengths[KEY_COUNT]; + + setter= 1; + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_NO_BLOCK, setter); + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_TCP_NODELAY, setter); +#ifdef NOT_YET + setter = 20 * 1024576; + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_SOCKET_SEND_SIZE, setter); + setter = 20 * 1024576; + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_SOCKET_RECV_SIZE, setter); + getter = memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_SOCKET_SEND_SIZE); + getter = memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_SOCKET_RECV_SIZE); +#endif + + keys= calloc(KEY_COUNT, sizeof(char *)); + test_truth(keys); + for (x= 0; x < KEY_COUNT; x++) + { + char buffer[30]; + + snprintf(buffer, 30, "%u", x); + keys[x]= strdup(buffer); + key_lengths[x]= strlen(keys[x]); + } + + rc= memcached_mget(memc, (const char **)keys, key_lengths, KEY_COUNT); + test_truth(rc == MEMCACHED_SUCCESS); + + test_truth(fetch_all_results(memc) == TEST_SUCCESS); + + for (x= 0; x < KEY_COUNT; x++) + free(keys[x]); + free(keys); + + return TEST_SUCCESS; +} + +/* Make sure we behave properly if server list has no values */ +static test_return_t user_supplied_bug4(memcached_st *memc) +{ + memcached_return_t rc; + const char *keys[]= {"fudge", "son", "food"}; + size_t key_length[]= {5, 3, 4}; + unsigned int x; + uint32_t flags; + char return_key[MEMCACHED_MAX_KEY]; + size_t return_key_length; + char *return_value; + size_t return_value_length; + + /* Here we free everything before running a bunch of mget tests */ + memcached_servers_reset(memc); + + + /* We need to empty the server before continueing test */ + rc= memcached_flush(memc, 0); + test_truth(rc == MEMCACHED_NO_SERVERS); + + rc= memcached_mget(memc, keys, key_length, 3); + test_truth(rc == MEMCACHED_NO_SERVERS); + + while ((return_value= memcached_fetch(memc, return_key, &return_key_length, + &return_value_length, &flags, &rc)) != NULL) + { + test_truth(return_value); + } + test_truth(!return_value); + test_truth(return_value_length == 0); + test_truth(rc == MEMCACHED_NO_SERVERS); + + for (x= 0; x < 3; x++) + { + rc= memcached_set(memc, keys[x], key_length[x], + keys[x], key_length[x], + (time_t)50, (uint32_t)9); + test_truth(rc == MEMCACHED_NO_SERVERS); + } + + rc= memcached_mget(memc, keys, key_length, 3); + test_truth(rc == MEMCACHED_NO_SERVERS); + + x= 0; + while ((return_value= memcached_fetch(memc, return_key, &return_key_length, + &return_value_length, &flags, &rc))) + { + test_truth(return_value); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(return_key_length == return_value_length); + test_truth(!memcmp(return_value, return_key, return_value_length)); + free(return_value); + x++; + } + + return TEST_SUCCESS; +} + +#define VALUE_SIZE_BUG5 1048064 +static test_return_t user_supplied_bug5(memcached_st *memc) +{ + memcached_return_t rc; + const char *keys[]= {"036790384900", "036790384902", "036790384904", "036790384906"}; + size_t key_length[]= {strlen("036790384900"), strlen("036790384902"), strlen("036790384904"), strlen("036790384906")}; + char return_key[MEMCACHED_MAX_KEY]; + size_t return_key_length; + char *value; + size_t value_length; + uint32_t flags; + unsigned int count; + unsigned int x; + char insert_data[VALUE_SIZE_BUG5]; + + for (x= 0; x < VALUE_SIZE_BUG5; x++) + insert_data[x]= (signed char)rand(); + + memcached_flush(memc, 0); + value= memcached_get(memc, keys[0], key_length[0], + &value_length, &flags, &rc); + test_truth(value == NULL); + rc= memcached_mget(memc, keys, key_length, 4); + + count= 0; + while ((value= memcached_fetch(memc, return_key, &return_key_length, + &value_length, &flags, &rc))) + count++; + test_truth(count == 0); + + for (x= 0; x < 4; x++) + { + rc= memcached_set(memc, keys[x], key_length[x], + insert_data, VALUE_SIZE_BUG5, + (time_t)0, (uint32_t)0); + test_truth(rc == MEMCACHED_SUCCESS); + } + + for (x= 0; x < 10; x++) + { + value= memcached_get(memc, keys[0], key_length[0], + &value_length, &flags, &rc); + test_truth(value); + free(value); + + rc= memcached_mget(memc, keys, key_length, 4); + count= 0; + while ((value= memcached_fetch(memc, return_key, &return_key_length, + &value_length, &flags, &rc))) + { + count++; + free(value); + } + test_truth(count == 4); + } + + return TEST_SUCCESS; +} + +static test_return_t user_supplied_bug6(memcached_st *memc) +{ + memcached_return_t rc; + const char *keys[]= {"036790384900", "036790384902", "036790384904", "036790384906"}; + size_t key_length[]= {strlen("036790384900"), strlen("036790384902"), strlen("036790384904"), strlen("036790384906")}; + char return_key[MEMCACHED_MAX_KEY]; + size_t return_key_length; + char *value; + size_t value_length; + uint32_t flags; + unsigned int count; + unsigned int x; + char insert_data[VALUE_SIZE_BUG5]; + + for (x= 0; x < VALUE_SIZE_BUG5; x++) + insert_data[x]= (signed char)rand(); + + memcached_flush(memc, 0); + value= memcached_get(memc, keys[0], key_length[0], + &value_length, &flags, &rc); + test_truth(value == NULL); + test_truth(rc == MEMCACHED_NOTFOUND); + rc= memcached_mget(memc, keys, key_length, 4); + test_truth(rc == MEMCACHED_SUCCESS); + + count= 0; + while ((value= memcached_fetch(memc, return_key, &return_key_length, + &value_length, &flags, &rc))) + count++; + test_truth(count == 0); + test_truth(rc == MEMCACHED_END); + + for (x= 0; x < 4; x++) + { + rc= memcached_set(memc, keys[x], key_length[x], + insert_data, VALUE_SIZE_BUG5, + (time_t)0, (uint32_t)0); + test_truth(rc == MEMCACHED_SUCCESS); + } + + for (x= 0; x < 2; x++) + { + value= memcached_get(memc, keys[0], key_length[0], + &value_length, &flags, &rc); + test_truth(value); + free(value); + + rc= memcached_mget(memc, keys, key_length, 4); + test_truth(rc == MEMCACHED_SUCCESS); + count= 3; + /* We test for purge of partial complete fetches */ + for (count= 3; count; count--) + { + value= memcached_fetch(memc, return_key, &return_key_length, + &value_length, &flags, &rc); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(!(memcmp(value, insert_data, value_length))); + test_truth(value_length); + free(value); + } + } + + return TEST_SUCCESS; +} + +static test_return_t user_supplied_bug8(memcached_st *memc __attribute__((unused))) +{ + memcached_return_t rc; + memcached_st *mine; + memcached_st *memc_clone; + + memcached_server_st *servers; + const char *server_list= "memcache1.memcache.bk.sapo.pt:11211, memcache1.memcache.bk.sapo.pt:11212, memcache1.memcache.bk.sapo.pt:11213, memcache1.memcache.bk.sapo.pt:11214, memcache2.memcache.bk.sapo.pt:11211, memcache2.memcache.bk.sapo.pt:11212, memcache2.memcache.bk.sapo.pt:11213, memcache2.memcache.bk.sapo.pt:11214"; + + servers= memcached_servers_parse(server_list); + test_truth(servers); + + mine= memcached_create(NULL); + rc= memcached_server_push(mine, servers); + test_truth(rc == MEMCACHED_SUCCESS); + memcached_server_list_free(servers); + + test_truth(mine); + memc_clone= memcached_clone(NULL, mine); + + memcached_quit(mine); + memcached_quit(memc_clone); + + + memcached_free(mine); + memcached_free(memc_clone); + + return TEST_SUCCESS; +} + +/* Test flag store/retrieve */ +static test_return_t user_supplied_bug7(memcached_st *memc) +{ + memcached_return_t rc; + const char *keys= "036790384900"; + size_t key_length= strlen(keys); + char return_key[MEMCACHED_MAX_KEY]; + size_t return_key_length; + char *value; + size_t value_length; + uint32_t flags; + unsigned int x; + char insert_data[VALUE_SIZE_BUG5]; + + for (x= 0; x < VALUE_SIZE_BUG5; x++) + insert_data[x]= (signed char)rand(); + + memcached_flush(memc, 0); + + flags= 245; + rc= memcached_set(memc, keys, key_length, + insert_data, VALUE_SIZE_BUG5, + (time_t)0, flags); + test_truth(rc == MEMCACHED_SUCCESS); + + flags= 0; + value= memcached_get(memc, keys, key_length, + &value_length, &flags, &rc); + test_truth(flags == 245); + test_truth(value); + free(value); + + rc= memcached_mget(memc, &keys, &key_length, 1); + + flags= 0; + value= memcached_fetch(memc, return_key, &return_key_length, + &value_length, &flags, &rc); + test_truth(flags == 245); + test_truth(value); + free(value); + + + return TEST_SUCCESS; +} + +static test_return_t user_supplied_bug9(memcached_st *memc) +{ + memcached_return_t rc; + const char *keys[]= {"UDATA:edevil@sapo.pt", "fudge&*@#", "for^#@&$not"}; + size_t key_length[3]; + unsigned int x; + uint32_t flags; + unsigned count= 0; + + char return_key[MEMCACHED_MAX_KEY]; + size_t return_key_length; + char *return_value; + size_t return_value_length; + + + key_length[0]= strlen("UDATA:edevil@sapo.pt"); + key_length[1]= strlen("fudge&*@#"); + key_length[2]= strlen("for^#@&$not"); + + + for (x= 0; x < 3; x++) + { + rc= memcached_set(memc, keys[x], key_length[x], + keys[x], key_length[x], + (time_t)50, (uint32_t)9); + test_truth(rc == MEMCACHED_SUCCESS); + } + + rc= memcached_mget(memc, keys, key_length, 3); + test_truth(rc == MEMCACHED_SUCCESS); + + /* We need to empty the server before continueing test */ + while ((return_value= memcached_fetch(memc, return_key, &return_key_length, + &return_value_length, &flags, &rc)) != NULL) + { + test_truth(return_value); + free(return_value); + count++; + } + test_truth(count == 3); + + return TEST_SUCCESS; +} + +/* We are testing with aggressive timeout to get failures */ +static test_return_t user_supplied_bug10(memcached_st *memc) +{ + const char *key= "foo"; + char *value; + size_t value_length= 512; + unsigned int x; + size_t key_len= 3; + memcached_return_t rc; + unsigned int set= 1; + memcached_st *mclone= memcached_clone(NULL, memc); + int32_t timeout; + + memcached_behavior_set(mclone, MEMCACHED_BEHAVIOR_NO_BLOCK, set); + memcached_behavior_set(mclone, MEMCACHED_BEHAVIOR_TCP_NODELAY, set); + timeout= 2; + memcached_behavior_set(mclone, MEMCACHED_BEHAVIOR_POLL_TIMEOUT, + (uint64_t)timeout); + + value = (char*)malloc(value_length * sizeof(char)); + + for (x= 0; x < value_length; x++) + value[x]= (char) (x % 127); + + for (x= 1; x <= 100000; ++x) + { + rc= memcached_set(mclone, key, key_len,value, value_length, 0, 0); + + test_truth(rc == MEMCACHED_SUCCESS || rc == MEMCACHED_WRITE_FAILURE || + rc == MEMCACHED_BUFFERED || rc == MEMCACHED_TIMEOUT); + + if (rc == MEMCACHED_WRITE_FAILURE || rc == MEMCACHED_TIMEOUT) + x--; + } + + free(value); + memcached_free(mclone); + + return TEST_SUCCESS; +} + +/* + We are looking failures in the async protocol +*/ +static test_return_t user_supplied_bug11(memcached_st *memc) +{ + const char *key= "foo"; + char *value; + size_t value_length= 512; + unsigned int x; + size_t key_len= 3; + memcached_return_t rc; + unsigned int set= 1; + int32_t timeout; + memcached_st *mclone= memcached_clone(NULL, memc); + + memcached_behavior_set(mclone, MEMCACHED_BEHAVIOR_NO_BLOCK, set); + memcached_behavior_set(mclone, MEMCACHED_BEHAVIOR_TCP_NODELAY, set); + timeout= -1; + memcached_behavior_set(mclone, MEMCACHED_BEHAVIOR_POLL_TIMEOUT, + (size_t)timeout); + + timeout= (int32_t)memcached_behavior_get(mclone, MEMCACHED_BEHAVIOR_POLL_TIMEOUT); + + test_truth(timeout == -1); + + value = (char*)malloc(value_length * sizeof(char)); + + for (x= 0; x < value_length; x++) + value[x]= (char) (x % 127); + + for (x= 1; x <= 100000; ++x) + { + rc= memcached_set(mclone, key, key_len,value, value_length, 0, 0); + } + + free(value); + memcached_free(mclone); + + return TEST_SUCCESS; +} + +/* + Bug found where incr was not returning MEMCACHED_NOTFOUND when object did not exist. +*/ +static test_return_t user_supplied_bug12(memcached_st *memc) +{ + memcached_return_t rc; + uint32_t flags; + size_t value_length; + char *value; + uint64_t number_value; + + value= memcached_get(memc, "autoincrement", strlen("autoincrement"), + &value_length, &flags, &rc); + test_truth(value == NULL); + test_truth(rc == MEMCACHED_NOTFOUND); + + rc= memcached_increment(memc, "autoincrement", strlen("autoincrement"), + 1, &number_value); + + test_truth(value == NULL); + /* The binary protocol will set the key if it doesn't exist */ + if (memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL) == 1) + { + test_truth(rc == MEMCACHED_SUCCESS); + } + else + { + test_truth(rc == MEMCACHED_NOTFOUND); + } + + rc= memcached_set(memc, "autoincrement", strlen("autoincrement"), "1", 1, 0, 0); + + value= memcached_get(memc, "autoincrement", strlen("autoincrement"), + &value_length, &flags, &rc); + test_truth(value); + test_truth(rc == MEMCACHED_SUCCESS); + free(value); + + rc= memcached_increment(memc, "autoincrement", strlen("autoincrement"), + 1, &number_value); + test_truth(number_value == 2); + test_truth(rc == MEMCACHED_SUCCESS); + + return TEST_SUCCESS; +} + +/* + Bug found where command total one more than MEMCACHED_MAX_BUFFER + set key34567890 0 0 8169 \r\n is sent followed by buffer of size 8169, followed by 8169 + */ +static test_return_t user_supplied_bug13(memcached_st *memc) +{ + char key[] = "key34567890"; + char *overflow; + memcached_return_t rc; + size_t overflowSize; + + char commandFirst[]= "set key34567890 0 0 "; + char commandLast[] = " \r\n"; /* first line of command sent to server */ + size_t commandLength; + size_t testSize; + + commandLength = strlen(commandFirst) + strlen(commandLast) + 4; /* 4 is number of characters in size, probably 8196 */ + + overflowSize = MEMCACHED_MAX_BUFFER - commandLength; + + for (testSize= overflowSize - 1; testSize < overflowSize + 1; testSize++) + { + overflow= malloc(testSize); + test_truth(overflow != NULL); + + memset(overflow, 'x', testSize); + rc= memcached_set(memc, key, strlen(key), + overflow, testSize, 0, 0); + test_truth(rc == MEMCACHED_SUCCESS); + free(overflow); + } + + return TEST_SUCCESS; +} + + +/* + Test values of many different sizes + Bug found where command total one more than MEMCACHED_MAX_BUFFER + set key34567890 0 0 8169 \r\n + is sent followed by buffer of size 8169, followed by 8169 + */ +static test_return_t user_supplied_bug14(memcached_st *memc) +{ + size_t setter= 1; + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_TCP_NODELAY, setter); + memcached_return_t rc; + const char *key= "foo"; + char *value; + size_t value_length= 18000; + char *string; + size_t string_length; + uint32_t flags; + unsigned int x; + size_t current_length; + + value = (char*)malloc(value_length); + test_truth(value); + + for (x= 0; x < value_length; x++) + value[x] = (char) (x % 127); + + for (current_length= 0; current_length < value_length; current_length++) + { + rc= memcached_set(memc, key, strlen(key), + value, current_length, + (time_t)0, (uint32_t)0); + test_truth(rc == MEMCACHED_SUCCESS || rc == MEMCACHED_BUFFERED); + + string= memcached_get(memc, key, strlen(key), + &string_length, &flags, &rc); + + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(string_length == current_length); + test_truth(!memcmp(string, value, string_length)); + + free(string); + } + + free(value); + + return TEST_SUCCESS; +} + +/* + Look for zero length value problems + */ +static test_return_t user_supplied_bug15(memcached_st *memc) +{ + uint32_t x; + memcached_return_t rc; + const char *key= "mykey"; + char *value; + size_t length; + uint32_t flags; + + for (x= 0; x < 2; x++) + { + rc= memcached_set(memc, key, strlen(key), + NULL, 0, + (time_t)0, (uint32_t)0); + + test_truth(rc == MEMCACHED_SUCCESS); + + value= memcached_get(memc, key, strlen(key), + &length, &flags, &rc); + + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(value == NULL); + test_truth(length == 0); + test_truth(flags == 0); + + value= memcached_get(memc, key, strlen(key), + &length, &flags, &rc); + + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(value == NULL); + test_truth(length == 0); + test_truth(flags == 0); + } + + return TEST_SUCCESS; +} + +/* Check the return sizes on FLAGS to make sure it stores 32bit unsigned values correctly */ +static test_return_t user_supplied_bug16(memcached_st *memc) +{ + memcached_return_t rc; + const char *key= "mykey"; + char *value; + size_t length; + uint32_t flags; + + rc= memcached_set(memc, key, strlen(key), + NULL, 0, + (time_t)0, UINT32_MAX); + + test_truth(rc == MEMCACHED_SUCCESS); + + value= memcached_get(memc, key, strlen(key), + &length, &flags, &rc); + + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(value == NULL); + test_truth(length == 0); + test_truth(flags == UINT32_MAX); + + return TEST_SUCCESS; +} + +#ifndef __sun +/* Check the validity of chinese key*/ +static test_return_t user_supplied_bug17(memcached_st *memc) +{ + memcached_return_t rc; + const char *key= "豆瓣"; + const char *value="我们在炎热抑郁的夏天无法停止豆瓣"; + char *value2; + size_t length; + uint32_t flags; + + rc= memcached_set(memc, key, strlen(key), + value, strlen(value), + (time_t)0, 0); + + test_truth(rc == MEMCACHED_SUCCESS); + + value2= memcached_get(memc, key, strlen(key), + &length, &flags, &rc); + + test_truth(length==strlen(value)); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(memcmp(value, value2, length)==0); + free(value2); + + return TEST_SUCCESS; +} +#endif + +/* + From Andrei on IRC +*/ + +static test_return_t user_supplied_bug19(memcached_st *memc) +{ + memcached_st *m; + memcached_server_st *s; + memcached_return_t res; + + (void)memc; + + m= memcached_create(NULL); + memcached_server_add_with_weight(m, "localhost", 11311, 100); + memcached_server_add_with_weight(m, "localhost", 11312, 100); + + s= memcached_server_by_key(m, "a", 1, &res); + memcached_server_free(s); + + memcached_free(m); + + return TEST_SUCCESS; +} + +/* CAS test from Andei */ +static test_return_t user_supplied_bug20(memcached_st *memc) +{ + memcached_return_t status; + memcached_result_st *result, result_obj; + const char *key = "abc"; + size_t key_len = strlen("abc"); + const char *value = "foobar"; + size_t value_len = strlen(value); + + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_SUPPORT_CAS, 1); + + status = memcached_set(memc, key, key_len, value, value_len, (time_t)0, (uint32_t)0); + test_truth(status == MEMCACHED_SUCCESS); + + status = memcached_mget(memc, &key, &key_len, 1); + test_truth(status == MEMCACHED_SUCCESS); + + result= memcached_result_create(memc, &result_obj); + test_truth(result); + + memcached_result_create(memc, &result_obj); + result= memcached_fetch_result(memc, &result_obj, &status); + + test_truth(result); + test_truth(status == MEMCACHED_SUCCESS); + + memcached_result_free(result); + + return TEST_SUCCESS; +} + +#include "ketama_test_cases.h" +static test_return_t user_supplied_bug18(memcached_st *trash) +{ + memcached_return_t rc; + uint64_t value; + int x; + memcached_server_st *server_pool; + memcached_st *memc; + + (void)trash; + + memc= memcached_create(NULL); + test_truth(memc); + + rc= memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_KETAMA_WEIGHTED, 1); + test_truth(rc == MEMCACHED_SUCCESS); + + value= memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_KETAMA_WEIGHTED); + test_truth(value == 1); + + rc= memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_KETAMA_HASH, MEMCACHED_HASH_MD5); + test_truth(rc == MEMCACHED_SUCCESS); + + value= memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_KETAMA_HASH); + test_truth(value == MEMCACHED_HASH_MD5); + + server_pool = memcached_servers_parse("10.0.1.1:11211 600,10.0.1.2:11211 300,10.0.1.3:11211 200,10.0.1.4:11211 350,10.0.1.5:11211 1000,10.0.1.6:11211 800,10.0.1.7:11211 950,10.0.1.8:11211 100"); + memcached_server_push(memc, server_pool); + + /* verify that the server list was parsed okay. */ + test_truth(memcached_server_count(memc) == 8); + test_truth(strcmp(server_pool[0].hostname, "10.0.1.1") == 0); + test_truth(server_pool[0].port == 11211); + test_truth(server_pool[0].weight == 600); + test_truth(strcmp(server_pool[2].hostname, "10.0.1.3") == 0); + test_truth(server_pool[2].port == 11211); + test_truth(server_pool[2].weight == 200); + test_truth(strcmp(server_pool[7].hostname, "10.0.1.8") == 0); + test_truth(server_pool[7].port == 11211); + test_truth(server_pool[7].weight == 100); + + /* VDEAAAAA hashes to fffcd1b5, after the last continuum point, and lets + * us test the boundary wraparound. + */ + test_truth(memcached_generate_hash(memc, (char *)"VDEAAAAA", 8) == memc->continuum[0].index); + + /* verify the standard ketama set. */ + for (x= 0; x < 99; x++) + { + uint32_t server_idx = memcached_generate_hash(memc, ketama_test_cases[x].key, strlen(ketama_test_cases[x].key)); + memcached_server_instance_st *instance= + memcached_server_instance_fetch(memc, server_idx); + char *hostname = instance->hostname; + test_strcmp(hostname, ketama_test_cases[x].server); + } + + memcached_server_list_free(server_pool); + memcached_free(memc); + + return TEST_SUCCESS; +} + +/* Large mget() of missing keys with binary proto + * + * If many binary quiet commands (such as getq's in an mget) fill the output + * buffer and the server chooses not to respond, memcached_flush hangs. See + * http://lists.tangent.org/pipermail/libmemcached/2009-August/000918.html + */ + +/* sighandler_t function that always asserts false */ +static void fail(int unused __attribute__((unused))) +{ + assert(0); +} + + +static test_return_t _user_supplied_bug21(memcached_st* memc, size_t key_count) +{ + memcached_return_t rc; + unsigned int x; + char **keys; + size_t* key_lengths; + void (*oldalarm)(int); + memcached_st *memc_clone; + + memc_clone= memcached_clone(NULL, memc); + test_truth(memc_clone); + + /* only binproto uses getq for mget */ + memcached_behavior_set(memc_clone, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, 1); + + /* empty the cache to ensure misses (hence non-responses) */ + rc= memcached_flush(memc_clone, 0); + test_truth(rc == MEMCACHED_SUCCESS); + + key_lengths= calloc(key_count, sizeof(size_t)); + keys= calloc(key_count, sizeof(char *)); + test_truth(keys); + for (x= 0; x < key_count; x++) + { + char buffer[30]; + + snprintf(buffer, 30, "%u", x); + keys[x]= strdup(buffer); + key_lengths[x]= strlen(keys[x]); + } + + oldalarm= signal(SIGALRM, fail); + alarm(5); + + rc= memcached_mget(memc_clone, (const char **)keys, key_lengths, key_count); + test_truth(rc == MEMCACHED_SUCCESS); + + alarm(0); + signal(SIGALRM, oldalarm); + + test_truth(fetch_all_results(memc) == TEST_SUCCESS); + + for (x= 0; x < key_count; x++) + free(keys[x]); + free(keys); + free(key_lengths); + + memcached_free(memc_clone); + + return TEST_SUCCESS; +} + +static test_return_t pre_binary(memcached_st *memc); + +static test_return_t user_supplied_bug21(memcached_st *memc) +{ + test_return_t test_rc; + test_rc= pre_binary(memc); + + if (test_rc != TEST_SUCCESS) + return test_rc; + + test_return_t rc; + + /* should work as of r580 */ + rc= _user_supplied_bug21(memc, 10); + test_truth(rc == TEST_SUCCESS); + + /* should fail as of r580 */ + rc= _user_supplied_bug21(memc, 1000); + test_truth(rc == TEST_SUCCESS); + + return TEST_SUCCESS; +} + +static test_return_t auto_eject_hosts(memcached_st *trash) +{ + (void) trash; + memcached_server_instance_st *instance; + + memcached_return_t rc; + memcached_st *memc= memcached_create(NULL); + test_truth(memc); + + rc= memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_KETAMA_WEIGHTED, 1); + test_truth(rc == MEMCACHED_SUCCESS); + + uint64_t value= memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_KETAMA_WEIGHTED); + test_truth(value == 1); + + rc= memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_KETAMA_HASH, MEMCACHED_HASH_MD5); + test_truth(rc == MEMCACHED_SUCCESS); + + value= memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_KETAMA_HASH); + test_truth(value == MEMCACHED_HASH_MD5); + + /* server should be removed when in delay */ + rc= memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_AUTO_EJECT_HOSTS, 1); + test_truth(rc == MEMCACHED_SUCCESS); + + value= memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_AUTO_EJECT_HOSTS); + test_truth(value == 1); + + memcached_server_st *server_pool; + server_pool = memcached_servers_parse("10.0.1.1:11211 600,10.0.1.2:11211 300,10.0.1.3:11211 200,10.0.1.4:11211 350,10.0.1.5:11211 1000,10.0.1.6:11211 800,10.0.1.7:11211 950,10.0.1.8:11211 100"); + memcached_server_push(memc, server_pool); + + /* verify that the server list was parsed okay. */ + test_truth(memcached_server_count(memc) == 8); + test_truth(strcmp(server_pool[0].hostname, "10.0.1.1") == 0); + test_truth(server_pool[0].port == 11211); + test_truth(server_pool[0].weight == 600); + test_truth(strcmp(server_pool[2].hostname, "10.0.1.3") == 0); + test_truth(server_pool[2].port == 11211); + test_truth(server_pool[2].weight == 200); + test_truth(strcmp(server_pool[7].hostname, "10.0.1.8") == 0); + test_truth(server_pool[7].port == 11211); + test_truth(server_pool[7].weight == 100); + + instance= memcached_server_instance_fetch(memc, 2); + instance->next_retry = time(NULL) + 15; + memc->next_distribution_rebuild= time(NULL) - 1; + + for (size_t x= 0; x < 99; x++) + { + uint32_t server_idx = memcached_generate_hash(memc, ketama_test_cases[x].key, strlen(ketama_test_cases[x].key)); + test_truth(server_idx != 2); + } + + /* and re-added when it's back. */ + instance->next_retry = time(NULL) - 1; + memc->next_distribution_rebuild= time(NULL) - 1; + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_DISTRIBUTION, + memc->distribution); + for (size_t x= 0; x < 99; x++) + { + uint32_t server_idx = memcached_generate_hash(memc, ketama_test_cases[x].key, strlen(ketama_test_cases[x].key)); + // We re-use instance from above. + instance= + memcached_server_instance_fetch(memc, server_idx); + char *hostname = instance->hostname; + test_truth(strcmp(hostname, ketama_test_cases[x].server) == 0); + } + + memcached_server_list_free(server_pool); + memcached_free(memc); + + return TEST_SUCCESS; +} + +static test_return_t output_ketama_weighted_keys(memcached_st *trash) +{ + (void) trash; + + memcached_return_t rc; + memcached_st *memc= memcached_create(NULL); + test_truth(memc); + + + rc= memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_KETAMA_WEIGHTED, 1); + test_truth(rc == MEMCACHED_SUCCESS); + + uint64_t value= memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_KETAMA_WEIGHTED); + test_truth(value == 1); + + rc= memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_KETAMA_HASH, MEMCACHED_HASH_MD5); + test_truth(rc == MEMCACHED_SUCCESS); + + value= memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_KETAMA_HASH); + test_truth(value == MEMCACHED_HASH_MD5); + + + test_truth(memcached_behavior_set_distribution(memc, MEMCACHED_DISTRIBUTION_CONSISTENT_KETAMA_SPY) == MEMCACHED_SUCCESS); + + memcached_server_st *server_pool; + server_pool = memcached_servers_parse("10.0.1.1:11211,10.0.1.2:11211,10.0.1.3:11211,10.0.1.4:11211,10.0.1.5:11211,10.0.1.6:11211,10.0.1.7:11211,10.0.1.8:11211,192.168.1.1:11211,192.168.100.1:11211"); + memcached_server_push(memc, server_pool); + + // @todo this needs to be refactored to actually test something. +#if 0 + FILE *fp; + if ((fp = fopen("ketama_keys.txt", "w"))) + { + // noop + } else { + printf("cannot write to file ketama_keys.txt"); + return TEST_FAILURE; + } + + for (int x= 0; x < 10000; x++) + { + char key[10]; + sprintf(key, "%d", x); + + uint32_t server_idx = memcached_generate_hash(memc, key, strlen(key)); + char *hostname = memc->hosts[server_idx].hostname; + in_port_t port = memc->hosts[server_idx].port; + fprintf(fp, "key %s is on host /%s:%u\n", key, hostname, port); + } + fclose(fp); +#endif + memcached_server_list_free(server_pool); + memcached_free(memc); + + return TEST_SUCCESS; +} + + +static test_return_t result_static(memcached_st *memc) +{ + memcached_result_st result; + memcached_result_st *result_ptr; + + result_ptr= memcached_result_create(memc, &result); + test_truth(result.options.is_allocated == false); + test_truth(memcached_is_initialized(&result) == true); + test_truth(result_ptr); + test_truth(result_ptr == &result); + + memcached_result_free(&result); + + test_truth(result.options.is_allocated == false); + test_truth(memcached_is_initialized(&result) == false); + + return TEST_SUCCESS; +} + +static test_return_t result_alloc(memcached_st *memc) +{ + memcached_result_st *result_ptr; + + result_ptr= memcached_result_create(memc, NULL); + test_truth(result_ptr); + test_truth(result_ptr->options.is_allocated == true); + test_truth(memcached_is_initialized(result_ptr) == true); + memcached_result_free(result_ptr); + + return TEST_SUCCESS; +} + +static test_return_t string_static_null(memcached_st *memc) +{ + memcached_string_st string; + memcached_string_st *string_ptr; + + string_ptr= memcached_string_create(memc, &string, 0); + test_truth(string.options.is_initialized == true); + test_truth(string_ptr); + + /* The following two better be the same! */ + test_truth(memcached_is_allocated(string_ptr) == false); + test_truth(memcached_is_allocated(&string) == false); + test_truth(&string == string_ptr); + + test_truth(string.options.is_initialized == true); + test_truth(memcached_is_initialized(&string) == true); + memcached_string_free(&string); + test_truth(memcached_is_initialized(&string) == false); + + return TEST_SUCCESS; +} + +static test_return_t string_alloc_null(memcached_st *memc) +{ + memcached_string_st *string; + + string= memcached_string_create(memc, NULL, 0); + test_truth(string); + test_truth(memcached_is_allocated(string) == true); + test_truth(memcached_is_initialized(string) == true); + memcached_string_free(string); + + return TEST_SUCCESS; +} + +static test_return_t string_alloc_with_size(memcached_st *memc) +{ + memcached_string_st *string; + + string= memcached_string_create(memc, NULL, 1024); + test_truth(string); + test_truth(memcached_is_allocated(string) == true); + test_truth(memcached_is_initialized(string) == true); + memcached_string_free(string); + + return TEST_SUCCESS; +} + +static test_return_t string_alloc_with_size_toobig(memcached_st *memc) +{ + memcached_string_st *string; + + string= memcached_string_create(memc, NULL, SIZE_MAX); + test_truth(string == NULL); + + return TEST_SUCCESS; +} + +static test_return_t string_alloc_append(memcached_st *memc) +{ + unsigned int x; + char buffer[SMALL_STRING_LEN]; + memcached_string_st *string; + + /* Ring the bell! */ + memset(buffer, 6, SMALL_STRING_LEN); + + string= memcached_string_create(memc, NULL, 100); + test_truth(string); + test_truth(memcached_is_allocated(string) == true); + test_truth(memcached_is_initialized(string) == true); + + for (x= 0; x < 1024; x++) + { + memcached_return_t rc; + rc= memcached_string_append(string, buffer, SMALL_STRING_LEN); + test_truth(rc == MEMCACHED_SUCCESS); + } + test_truth(memcached_is_allocated(string) == true); + memcached_string_free(string); + + return TEST_SUCCESS; +} + +static test_return_t string_alloc_append_toobig(memcached_st *memc) +{ + memcached_return_t rc; + unsigned int x; + char buffer[SMALL_STRING_LEN]; + memcached_string_st *string; + + /* Ring the bell! */ + memset(buffer, 6, SMALL_STRING_LEN); + + string= memcached_string_create(memc, NULL, 100); + test_truth(string); + test_truth(memcached_is_allocated(string) == true); + test_truth(memcached_is_initialized(string) == true); + + for (x= 0; x < 1024; x++) + { + rc= memcached_string_append(string, buffer, SMALL_STRING_LEN); + test_truth(rc == MEMCACHED_SUCCESS); + } + rc= memcached_string_append(string, buffer, SIZE_MAX); + test_truth(rc == MEMCACHED_MEMORY_ALLOCATION_FAILURE); + test_truth(memcached_is_allocated(string) == true); + memcached_string_free(string); + + return TEST_SUCCESS; +} + +static test_return_t cleanup_pairs(memcached_st *memc __attribute__((unused))) +{ + pairs_free(global_pairs); + + return TEST_SUCCESS; +} + +static test_return_t generate_pairs(memcached_st *memc __attribute__((unused))) +{ + global_pairs= pairs_generate(GLOBAL_COUNT, 400); + global_count= GLOBAL_COUNT; + + for (size_t x= 0; x < global_count; x++) + { + global_keys[x]= global_pairs[x].key; + global_keys_length[x]= global_pairs[x].key_length; + } + + return TEST_SUCCESS; +} + +static test_return_t generate_large_pairs(memcached_st *memc __attribute__((unused))) +{ + global_pairs= pairs_generate(GLOBAL2_COUNT, MEMCACHED_MAX_BUFFER+10); + global_count= GLOBAL2_COUNT; + + for (size_t x= 0; x < global_count; x++) + { + global_keys[x]= global_pairs[x].key; + global_keys_length[x]= global_pairs[x].key_length; + } + + return TEST_SUCCESS; +} + +static test_return_t generate_data(memcached_st *memc) +{ + execute_set(memc, global_pairs, global_count); + + return TEST_SUCCESS; +} + +static test_return_t generate_data_with_stats(memcached_st *memc) +{ + memcached_stat_st *stat_p; + memcached_return_t rc; + uint32_t host_index= 0; + execute_set(memc, global_pairs, global_count); + + //TODO: hosts used size stats + stat_p= memcached_stat(memc, NULL, &rc); + test_truth(stat_p); + + for (host_index= 0; host_index < SERVERS_TO_CREATE; host_index++) + { + /* This test was changes so that "make test" would work properlly */ +#ifdef DEBUG + memcached_server_instance_st *instance= + memcached_server_instance_fetch(memc, host_index); + + printf("\nserver %u|%s|%u bytes: %llu\n", host_index, instance->hostname, instance->port, (unsigned long long)(stat_p + host_index)->bytes); +#endif + test_truth((unsigned long long)(stat_p + host_index)->bytes); + } + + memcached_stat_free(NULL, stat_p); + + return TEST_SUCCESS; +} +static test_return_t generate_buffer_data(memcached_st *memc) +{ + size_t latch= 0; + + latch= 1; + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_BUFFER_REQUESTS, latch); + generate_data(memc); + + return TEST_SUCCESS; +} + +static test_return_t get_read_count(memcached_st *memc) +{ + memcached_return_t rc; + memcached_st *memc_clone; + + memc_clone= memcached_clone(NULL, memc); + test_truth(memc_clone); + + memcached_server_add_with_weight(memc_clone, "localhost", 6666, 0); + + { + char *return_value; + size_t return_value_length; + uint32_t flags; + uint32_t count; + + for (size_t x= count= 0; x < global_count; x++) + { + return_value= memcached_get(memc_clone, global_keys[x], global_keys_length[x], + &return_value_length, &flags, &rc); + if (rc == MEMCACHED_SUCCESS) + { + count++; + if (return_value) + free(return_value); + } + } + } + + memcached_free(memc_clone); + + return TEST_SUCCESS; +} + +static test_return_t get_read(memcached_st *memc) +{ + memcached_return_t rc; + + { + char *return_value; + size_t return_value_length; + uint32_t flags; + + for (size_t x= 0; x < global_count; x++) + { + return_value= memcached_get(memc, global_keys[x], global_keys_length[x], + &return_value_length, &flags, &rc); + /* + test_truth(return_value); + test_truth(rc == MEMCACHED_SUCCESS); + */ + if (rc == MEMCACHED_SUCCESS && return_value) + free(return_value); + } + } + + return TEST_SUCCESS; +} + +static test_return_t mget_read(memcached_st *memc) +{ + memcached_return_t rc; + + rc= memcached_mget(memc, global_keys, global_keys_length, global_count); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(fetch_all_results(memc) == TEST_SUCCESS); + + return TEST_SUCCESS; +} + +static test_return_t mget_read_result(memcached_st *memc) +{ + memcached_return_t rc; + + rc= memcached_mget(memc, global_keys, global_keys_length, global_count); + test_truth(rc == MEMCACHED_SUCCESS); + /* Turn this into a help function */ + { + memcached_result_st results_obj; + memcached_result_st *results; + + results= memcached_result_create(memc, &results_obj); + + while ((results= memcached_fetch_result(memc, &results_obj, &rc))) + { + test_truth(results); + test_truth(rc == MEMCACHED_SUCCESS); + } + + memcached_result_free(&results_obj); + } + + return TEST_SUCCESS; +} + +static test_return_t mget_read_function(memcached_st *memc) +{ + memcached_return_t rc; + size_t counter; + memcached_execute_fn callbacks[1]; + + rc= memcached_mget(memc, global_keys, global_keys_length, global_count); + test_truth(rc == MEMCACHED_SUCCESS); + + callbacks[0]= &callback_counter; + counter= 0; + rc= memcached_fetch_execute(memc, callbacks, (void *)&counter, 1); + + return TEST_SUCCESS; +} + +static test_return_t delete_generate(memcached_st *memc) +{ + for (size_t x= 0; x < global_count; x++) + { + (void)memcached_delete(memc, global_keys[x], global_keys_length[x], (time_t)0); + } + + return TEST_SUCCESS; +} + +static test_return_t delete_buffer_generate(memcached_st *memc) +{ + uint64_t latch= 0; + + latch= 1; + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_BUFFER_REQUESTS, latch); + + for (size_t x= 0; x < global_count; x++) + { + (void)memcached_delete(memc, global_keys[x], global_keys_length[x], (time_t)0); + } + + return TEST_SUCCESS; +} + +static test_return_t add_host_test1(memcached_st *memc) +{ + memcached_return_t rc; + char servername[]= "0.example.com"; + memcached_server_st *servers; + + servers= memcached_server_list_append_with_weight(NULL, servername, 400, 0, &rc); + test_truth(servers); + test_truth(1 == memcached_server_list_count(servers)); + + for (size_t x= 2; x < 20; x++) + { + char buffer[SMALL_STRING_LEN]; + + snprintf(buffer, SMALL_STRING_LEN, "%zu.example.com", 400+x); + servers= memcached_server_list_append_with_weight(servers, buffer, 401, 0, + &rc); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(x == memcached_server_list_count(servers)); + } + + rc= memcached_server_push(memc, servers); + test_truth(rc == MEMCACHED_SUCCESS); + rc= memcached_server_push(memc, servers); + test_truth(rc == MEMCACHED_SUCCESS); + + memcached_server_list_free(servers); + + return TEST_SUCCESS; +} + - static test_return_t pre_nonblock(memcached_st *memc) ++static test_return_t pre_nonblock(memcached_st *memc) +{ + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_NO_BLOCK, 0); + + return TEST_SUCCESS; +} + ++static test_return_t pre_cork(memcached_st *memc) ++{ ++ memcached_return_t rc; ++ bool set= true; ++ rc= memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_CORK, set); ++ ++ if (rc == MEMCACHED_SUCCESS) ++ return TEST_SUCCESS; ++ ++ return TEST_SKIPPED; ++} ++ ++static test_return_t pre_cork_and_nonblock(memcached_st *memc) ++{ ++ test_return_t rc; ++ ++ rc= pre_cork(memc); ++ ++ if (rc != TEST_SUCCESS) ++ return rc; ++ ++ return pre_nonblock(memc); ++} ++ +static test_return_t pre_nonblock_binary(memcached_st *memc) +{ + memcached_return_t rc= MEMCACHED_FAILURE; + memcached_st *memc_clone; + memcached_server_instance_st *instance; + + memc_clone= memcached_clone(NULL, memc); + test_truth(memc_clone); + // The memcached_version needs to be done on a clone, because the server + // will not toggle protocol on an connection. + memcached_version(memc_clone); + + instance= memcached_server_instance_fetch(memc_clone, 0); + + if (instance->major_version >= 1 && instance->minor_version > 2) + { + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_NO_BLOCK, 0); + rc = memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, 1); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL) == 1); + } + else + { + return TEST_SKIPPED; + } + + memcached_free(memc_clone); + + return rc == MEMCACHED_SUCCESS ? TEST_SUCCESS : TEST_SKIPPED; +} + +static test_return_t pre_murmur(memcached_st *memc) +{ + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_HASH, (uint64_t)MEMCACHED_HASH_MURMUR); + + return TEST_SUCCESS; +} + +static test_return_t pre_jenkins(memcached_st *memc) +{ + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_HASH, (uint64_t)MEMCACHED_HASH_JENKINS); + + return TEST_SUCCESS; +} + + +static test_return_t pre_md5(memcached_st *memc) +{ + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_HASH, (uint64_t)MEMCACHED_HASH_MD5); + + return TEST_SUCCESS; +} + +static test_return_t pre_crc(memcached_st *memc) +{ + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_HASH, (uint64_t)MEMCACHED_HASH_CRC); + + return TEST_SUCCESS; +} + +static test_return_t pre_hsieh(memcached_st *memc) +{ +#ifdef HAVE_HSIEH_HASH + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_HASH, (uint64_t)MEMCACHED_HASH_HSIEH); + return TEST_SUCCESS; +#else + (void) memc; + return TEST_SKIPPED; +#endif +} + +static test_return_t pre_hash_fnv1_64(memcached_st *memc) +{ + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_HASH, (uint64_t)MEMCACHED_HASH_MURMUR); + + return TEST_SUCCESS; +} + +static test_return_t pre_hash_fnv1a_64(memcached_st *memc) +{ + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_HASH, (uint64_t)MEMCACHED_HASH_FNV1A_64); + + return TEST_SUCCESS; +} + +static test_return_t pre_hash_fnv1_32(memcached_st *memc) +{ + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_HASH, (uint64_t)MEMCACHED_HASH_FNV1_32); + + return TEST_SUCCESS; +} + +static test_return_t pre_hash_fnv1a_32(memcached_st *memc) +{ + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_HASH, (uint64_t)MEMCACHED_HASH_FNV1A_32); + + return TEST_SUCCESS; +} + +static test_return_t pre_behavior_ketama(memcached_st *memc) +{ + memcached_return_t rc; + uint64_t value; + + rc= memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_KETAMA, 1); + test_truth(rc == MEMCACHED_SUCCESS); + + value= memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_KETAMA); + test_truth(value == 1); + + return TEST_SUCCESS; +} + +static test_return_t pre_behavior_ketama_weighted(memcached_st *memc) +{ + memcached_return_t rc; + uint64_t value; + + rc= memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_KETAMA_WEIGHTED, 1); + test_truth(rc == MEMCACHED_SUCCESS); + + value= memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_KETAMA_WEIGHTED); + test_truth(value == 1); + + rc= memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_KETAMA_HASH, MEMCACHED_HASH_MD5); + test_truth(rc == MEMCACHED_SUCCESS); + + value= memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_KETAMA_HASH); + test_truth(value == MEMCACHED_HASH_MD5); + + return TEST_SUCCESS; +} + +/** + @note This should be testing to see if the server really supports the binary protocol. +*/ +static test_return_t pre_binary(memcached_st *memc) +{ + memcached_return_t rc= MEMCACHED_FAILURE; + memcached_st *memc_clone; + memcached_server_instance_st *instance; + + memc_clone= memcached_clone(NULL, memc); + test_truth(memc_clone); + // The memcached_version needs to be done on a clone, because the server + // will not toggle protocol on an connection. + memcached_version(memc_clone); + + instance= memcached_server_instance_fetch(memc_clone, 0); + + if (instance->major_version >= 1 && instance->minor_version > 2) + { + rc = memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, 1); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL) == 1); + } + + memcached_free(memc_clone); + + return rc == MEMCACHED_SUCCESS ? TEST_SUCCESS : TEST_SKIPPED; +} + + +static test_return_t pre_replication(memcached_st *memc) +{ + test_return_t test_rc; + test_rc= pre_binary(memc); + + if (test_rc != TEST_SUCCESS) + return test_rc; + + /* + * Make sure that we store the item on all servers + * (master + replicas == number of servers) + */ + memcached_return_t rc; + rc= memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_NUMBER_OF_REPLICAS, + memcached_server_count(memc) - 1); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_NUMBER_OF_REPLICAS) == memcached_server_count(memc) - 1); + + return rc == MEMCACHED_SUCCESS ? TEST_SUCCESS : TEST_SKIPPED; +} + + +static test_return_t pre_replication_noblock(memcached_st *memc) +{ + test_return_t rc; + + rc= pre_replication(memc); + if (rc != TEST_SUCCESS) + return rc; + + rc= pre_nonblock(memc); + + return rc; +} + + +static void my_free(memcached_st *ptr __attribute__((unused)), void *mem) +{ +#ifdef HARD_MALLOC_TESTS + void *real_ptr= (mem == NULL) ? mem : (void*)((caddr_t)mem - 8); + free(real_ptr); +#else + free(mem); +#endif +} + + +static void *my_malloc(memcached_st *ptr __attribute__((unused)), const size_t size) +{ +#ifdef HARD_MALLOC_TESTS + void *ret= malloc(size + 8); + if (ret != NULL) + { + ret= (void*)((caddr_t)ret + 8); + } +#else + void *ret= malloc(size); +#endif + + if (ret != NULL) + { + memset(ret, 0xff, size); + } + + return ret; +} + + +static void *my_realloc(memcached_st *ptr __attribute__((unused)), void *mem, const size_t size) +{ +#ifdef HARD_MALLOC_TESTS + void *real_ptr= (mem == NULL) ? NULL : (void*)((caddr_t)mem - 8); + void *nmem= realloc(real_ptr, size + 8); + + void *ret= NULL; + if (nmem != NULL) + { + ret= (void*)((caddr_t)nmem + 8); + } + + return ret; +#else + return realloc(mem, size); +#endif +} + + +static void *my_calloc(memcached_st *ptr __attribute__((unused)), size_t nelem, const size_t size) +{ +#ifdef HARD_MALLOC_TESTS + void *mem= my_malloc(ptr, nelem * size); + if (mem) + { + memset(mem, 0, nelem * size); + } + + return mem; +#else + return calloc(nelem, size); +#endif +} + + +static test_return_t set_prefix(memcached_st *memc) +{ + memcached_return_t rc; + const char *key= "mine"; + char *value; + + /* Make sure be default none exists */ + value= memcached_callback_get(memc, MEMCACHED_CALLBACK_PREFIX_KEY, &rc); + test_truth(rc == MEMCACHED_FAILURE); + + /* Test a clean set */ + rc= memcached_callback_set(memc, MEMCACHED_CALLBACK_PREFIX_KEY, (void *)key); + test_truth(rc == MEMCACHED_SUCCESS); + + value= memcached_callback_get(memc, MEMCACHED_CALLBACK_PREFIX_KEY, &rc); + test_truth(memcmp(value, key, 4) == 0); + test_truth(rc == MEMCACHED_SUCCESS); + + /* Test that we can turn it off */ + rc= memcached_callback_set(memc, MEMCACHED_CALLBACK_PREFIX_KEY, NULL); + test_truth(rc == MEMCACHED_SUCCESS); + + value= memcached_callback_get(memc, MEMCACHED_CALLBACK_PREFIX_KEY, &rc); + test_truth(rc == MEMCACHED_FAILURE); + + /* Now setup for main test */ + rc= memcached_callback_set(memc, MEMCACHED_CALLBACK_PREFIX_KEY, (void *)key); + test_truth(rc == MEMCACHED_SUCCESS); + + value= memcached_callback_get(memc, MEMCACHED_CALLBACK_PREFIX_KEY, &rc); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(memcmp(value, key, 4) == 0); + + /* Set to Zero, and then Set to something too large */ + { + char long_key[255]; + memset(long_key, 0, 255); + + rc= memcached_callback_set(memc, MEMCACHED_CALLBACK_PREFIX_KEY, NULL); + test_truth(rc == MEMCACHED_SUCCESS); + + value= memcached_callback_get(memc, MEMCACHED_CALLBACK_PREFIX_KEY, &rc); + test_truth(rc == MEMCACHED_FAILURE); + test_truth(value == NULL); + + /* Test a long key for failure */ + /* TODO, extend test to determine based on setting, what result should be */ + strcpy(long_key, "Thisismorethentheallottednumberofcharacters"); + rc= memcached_callback_set(memc, MEMCACHED_CALLBACK_PREFIX_KEY, long_key); + //test_truth(rc == MEMCACHED_BAD_KEY_PROVIDED); + test_truth(rc == MEMCACHED_SUCCESS); + + /* Now test a key with spaces (which will fail from long key, since bad key is not set) */ + strcpy(long_key, "This is more then the allotted number of characters"); + rc= memcached_callback_set(memc, MEMCACHED_CALLBACK_PREFIX_KEY, long_key); + test_truth(rc == MEMCACHED_BAD_KEY_PROVIDED); + + /* Test for a bad prefix, but with a short key */ + rc= memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_VERIFY_KEY, 1); + test_truth(rc == MEMCACHED_SUCCESS); + + strcpy(long_key, "dog cat"); + rc= memcached_callback_set(memc, MEMCACHED_CALLBACK_PREFIX_KEY, long_key); + test_truth(rc == MEMCACHED_BAD_KEY_PROVIDED); + } + + return TEST_SUCCESS; +} + + +#ifdef MEMCACHED_ENABLE_DEPRECATED +static test_return_t deprecated_set_memory_alloc(memcached_st *memc) +{ + void *test_ptr= NULL; + void *cb_ptr= NULL; + { + memcached_malloc_fn malloc_cb= + (memcached_malloc_fn)my_malloc; + cb_ptr= *(void **)&malloc_cb; + memcached_return_t rc; + + rc= memcached_callback_set(memc, MEMCACHED_CALLBACK_MALLOC_FUNCTION, cb_ptr); + test_truth(rc == MEMCACHED_SUCCESS); + test_ptr= memcached_callback_get(memc, MEMCACHED_CALLBACK_MALLOC_FUNCTION, &rc); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(test_ptr == cb_ptr); + } + + { + memcached_realloc_fn realloc_cb= + (memcached_realloc_fn)my_realloc; + cb_ptr= *(void **)&realloc_cb; + memcached_return_t rc; + + rc= memcached_callback_set(memc, MEMCACHED_CALLBACK_REALLOC_FUNCTION, cb_ptr); + test_truth(rc == MEMCACHED_SUCCESS); + test_ptr= memcached_callback_get(memc, MEMCACHED_CALLBACK_REALLOC_FUNCTION, &rc); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(test_ptr == cb_ptr); + } + + { + memcached_free_fn free_cb= + (memcached_free_fn)my_free; + cb_ptr= *(void **)&free_cb; + memcached_return_t rc; + + rc= memcached_callback_set(memc, MEMCACHED_CALLBACK_FREE_FUNCTION, cb_ptr); + test_truth(rc == MEMCACHED_SUCCESS); + test_ptr= memcached_callback_get(memc, MEMCACHED_CALLBACK_FREE_FUNCTION, &rc); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(test_ptr == cb_ptr); + } + + return TEST_SUCCESS; +} +#endif + + +static test_return_t set_memory_alloc(memcached_st *memc) +{ + memcached_return_t rc; + rc= memcached_set_memory_allocators(memc, NULL, my_free, + my_realloc, my_calloc); + test_truth(rc == MEMCACHED_FAILURE); + + rc= memcached_set_memory_allocators(memc, my_malloc, my_free, + my_realloc, my_calloc); + + memcached_malloc_fn mem_malloc; + memcached_free_fn mem_free; + memcached_realloc_fn mem_realloc; + memcached_calloc_fn mem_calloc; + memcached_get_memory_allocators(memc, &mem_malloc, &mem_free, + &mem_realloc, &mem_calloc); + + test_truth(mem_malloc == my_malloc); + test_truth(mem_realloc == my_realloc); + test_truth(mem_calloc == my_calloc); + test_truth(mem_free == my_free); + + return TEST_SUCCESS; +} + +static test_return_t enable_consistent_crc(memcached_st *memc) +{ + test_return_t rc; + memcached_server_distribution_t value= MEMCACHED_DISTRIBUTION_CONSISTENT; + memcached_hash_t hash; + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_DISTRIBUTION, value); + if ((rc= pre_crc(memc)) != TEST_SUCCESS) + return rc; + + value= (memcached_server_distribution_t)memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_DISTRIBUTION); + test_truth(value == MEMCACHED_DISTRIBUTION_CONSISTENT); + + hash= (memcached_hash_t)memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_HASH); + + if (hash != MEMCACHED_HASH_CRC) + return TEST_SKIPPED; + + return TEST_SUCCESS; +} + +static test_return_t enable_consistent_hsieh(memcached_st *memc) +{ + test_return_t rc; + memcached_server_distribution_t value= MEMCACHED_DISTRIBUTION_CONSISTENT; + memcached_hash_t hash; + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_DISTRIBUTION, value); + if ((rc= pre_hsieh(memc)) != TEST_SUCCESS) + return rc; + + value= (memcached_server_distribution_t)memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_DISTRIBUTION); + test_truth(value == MEMCACHED_DISTRIBUTION_CONSISTENT); + + hash= (memcached_hash_t)memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_HASH); + + if (hash != MEMCACHED_HASH_HSIEH) + return TEST_SKIPPED; + + + return TEST_SUCCESS; +} + +static test_return_t enable_cas(memcached_st *memc) +{ + unsigned int set= 1; + + memcached_server_instance_st *instance= + memcached_server_instance_fetch(memc, 0); + + memcached_version(memc); + + if ((instance->major_version >= 1 && (instance->minor_version == 2 && instance->micro_version >= 4)) + || instance->minor_version > 2) + { + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_SUPPORT_CAS, set); + + return TEST_SUCCESS; + } + + return TEST_SKIPPED; +} + +static test_return_t check_for_1_2_3(memcached_st *memc) +{ + memcached_version(memc); + memcached_server_instance_st *instance= + memcached_server_instance_fetch(memc, 0); + + if ((instance->major_version >= 1 && (instance->minor_version == 2 && instance->micro_version >= 4)) + || instance->minor_version > 2) + return TEST_SUCCESS; + + return TEST_SKIPPED; +} + +static test_return_t pre_unix_socket(memcached_st *memc) +{ + memcached_return_t rc; + struct stat buf; + + memcached_servers_reset(memc); + + if (stat("/tmp/memcached.socket", &buf)) + return TEST_SKIPPED; + + rc= memcached_server_add_unix_socket_with_weight(memc, "/tmp/memcached.socket", 0); + + return ( rc == MEMCACHED_SUCCESS ? TEST_SUCCESS : TEST_FAILURE ); +} + +static test_return_t pre_nodelay(memcached_st *memc) +{ + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_NO_BLOCK, 0); + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_TCP_NODELAY, 0); + + return TEST_SUCCESS; +} + +static test_return_t pre_settimer(memcached_st *memc) +{ + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_SND_TIMEOUT, 1000); + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_RCV_TIMEOUT, 1000); + + return TEST_SUCCESS; +} + +static test_return_t poll_timeout(memcached_st *memc) +{ + size_t timeout; + + timeout= 100; + + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_POLL_TIMEOUT, timeout); + + timeout= (size_t)memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_POLL_TIMEOUT); + + test_truth(timeout == 100); + + return TEST_SUCCESS; +} + +static test_return_t noreply_test(memcached_st *memc) +{ + memcached_return_t ret; + ret= memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_NOREPLY, 1); + test_truth(ret == MEMCACHED_SUCCESS); + ret= memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_BUFFER_REQUESTS, 1); + test_truth(ret == MEMCACHED_SUCCESS); + ret= memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_SUPPORT_CAS, 1); + test_truth(ret == MEMCACHED_SUCCESS); + test_truth(memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_NOREPLY) == 1); + test_truth(memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_BUFFER_REQUESTS) == 1); + test_truth(memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_SUPPORT_CAS) == 1); + + for (int count=0; count < 5; ++count) + { + for (size_t x= 0; x < 100; ++x) + { + char key[10]; + size_t len= (size_t)sprintf(key, "%zu", x); + switch (count) + { + case 0: + ret= memcached_add(memc, key, len, key, len, 0, 0); + break; + case 1: + ret= memcached_replace(memc, key, len, key, len, 0, 0); + break; + case 2: + ret= memcached_set(memc, key, len, key, len, 0, 0); + break; + case 3: + ret= memcached_append(memc, key, len, key, len, 0, 0); + break; + case 4: + ret= memcached_prepend(memc, key, len, key, len, 0, 0); + break; + default: + test_truth(count); + break; + } + test_truth(ret == MEMCACHED_SUCCESS || ret == MEMCACHED_BUFFERED); + } + + /* + ** NOTE: Don't ever do this in your code! this is not a supported use of the + ** API and is _ONLY_ done this way to verify that the library works the + ** way it is supposed to do!!!! + */ + int no_msg=0; + for (uint32_t x= 0; x < memcached_server_count(memc); ++x) + { + memcached_server_instance_st *instance= + memcached_server_instance_fetch(memc, x); + no_msg+=(int)(instance->cursor_active); + } + + test_truth(no_msg == 0); + test_truth(memcached_flush_buffers(memc) == MEMCACHED_SUCCESS); + + /* + ** Now validate that all items was set properly! + */ + for (size_t x= 0; x < 100; ++x) + { + char key[10]; + + size_t len= (size_t)sprintf(key, "%zu", x); + size_t length; + uint32_t flags; + char* value=memcached_get(memc, key, strlen(key), + &length, &flags, &ret); + test_truth(ret == MEMCACHED_SUCCESS && value != NULL); + switch (count) + { + case 0: /* FALLTHROUGH */ + case 1: /* FALLTHROUGH */ + case 2: + test_truth(strncmp(value, key, len) == 0); + test_truth(len == length); + break; + case 3: + test_truth(length == len * 2); + break; + case 4: + test_truth(length == len * 3); + break; + default: + test_truth(count); + break; + } + free(value); + } + } + + /* Try setting an illegal cas value (should not return an error to + * the caller (because we don't expect a return message from the server) + */ + const char* keys[]= {"0"}; + size_t lengths[]= {1}; + size_t length; + uint32_t flags; + memcached_result_st results_obj; + memcached_result_st *results; + ret= memcached_mget(memc, keys, lengths, 1); + test_truth(ret == MEMCACHED_SUCCESS); + + results= memcached_result_create(memc, &results_obj); + test_truth(results); + results= memcached_fetch_result(memc, &results_obj, &ret); + test_truth(results); + test_truth(ret == MEMCACHED_SUCCESS); + uint64_t cas= memcached_result_cas(results); + memcached_result_free(&results_obj); + + ret= memcached_cas(memc, keys[0], lengths[0], keys[0], lengths[0], 0, 0, cas); + test_truth(ret == MEMCACHED_SUCCESS); + + /* + * The item will have a new cas value, so try to set it again with the old + * value. This should fail! + */ + ret= memcached_cas(memc, keys[0], lengths[0], keys[0], lengths[0], 0, 0, cas); + test_truth(ret == MEMCACHED_SUCCESS); + test_truth(memcached_flush_buffers(memc) == MEMCACHED_SUCCESS); + char* value=memcached_get(memc, keys[0], lengths[0], &length, &flags, &ret); + test_truth(ret == MEMCACHED_SUCCESS && value != NULL); + free(value); + + return TEST_SUCCESS; +} + +static test_return_t analyzer_test(memcached_st *memc) +{ + memcached_return_t rc; + memcached_stat_st *memc_stat; + memcached_analysis_st *report; + + memc_stat= memcached_stat(memc, NULL, &rc); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(memc_stat); + + report= memcached_analyze(memc, memc_stat, &rc); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(report); + + free(report); + memcached_stat_free(NULL, memc_stat); + + return TEST_SUCCESS; +} + +/* Count the objects */ +static memcached_return_t callback_dump_counter(memcached_st *ptr __attribute__((unused)), + const char *key __attribute__((unused)), + size_t key_length __attribute__((unused)), + void *context) +{ + size_t *counter= (size_t *)context; + + *counter= *counter + 1; + + return MEMCACHED_SUCCESS; +} + +static test_return_t dump_test(memcached_st *memc) +{ + memcached_return_t rc; + size_t counter= 0; + memcached_dump_fn callbacks[1]; + test_return_t main_rc; + + callbacks[0]= &callback_dump_counter; + + /* No support for Binary protocol yet */ + if (memc->flags.binary_protocol) + return TEST_SUCCESS; + + main_rc= set_test3(memc); + + test_truth (main_rc == TEST_SUCCESS); + + rc= memcached_dump(memc, callbacks, (void *)&counter, 1); + test_truth(rc == MEMCACHED_SUCCESS); + + /* We may have more then 32 if our previous flush has not completed */ + test_truth(counter >= 32); + + return TEST_SUCCESS; +} + +#ifdef HAVE_LIBMEMCACHEDUTIL +static void* connection_release(void *arg) +{ + struct { + memcached_pool_st* pool; + memcached_st* mmc; + } *resource= arg; + + usleep(250); + assert(memcached_pool_push(resource->pool, resource->mmc) == MEMCACHED_SUCCESS); + return arg; +} + +static test_return_t connection_pool_test(memcached_st *memc) +{ + memcached_pool_st* pool= memcached_pool_create(memc, 5, 10); + test_truth(pool != NULL); + memcached_st* mmc[10]; + memcached_return_t rc; + + for (size_t x= 0; x < 10; ++x) + { + mmc[x]= memcached_pool_pop(pool, false, &rc); + test_truth(mmc[x] != NULL); + test_truth(rc == MEMCACHED_SUCCESS); + } + + test_truth(memcached_pool_pop(pool, false, &rc) == NULL); + test_truth(rc == MEMCACHED_SUCCESS); + + pthread_t tid; + struct { + memcached_pool_st* pool; + memcached_st* mmc; + } item= { .pool = pool, .mmc = mmc[9] }; + pthread_create(&tid, NULL, connection_release, &item); + mmc[9]= memcached_pool_pop(pool, true, &rc); + test_truth(rc == MEMCACHED_SUCCESS); + pthread_join(tid, NULL); + test_truth(mmc[9] == item.mmc); + const char *key= "key"; + size_t keylen= strlen(key); + + // verify that I can do ops with all connections + rc= memcached_set(mmc[0], key, keylen, "0", 1, 0, 0); + test_truth(rc == MEMCACHED_SUCCESS); + + for (size_t x= 0; x < 10; ++x) + { + uint64_t number_value; + rc= memcached_increment(mmc[x], key, keylen, 1, &number_value); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(number_value == (x+1)); + } + + // Release them.. + for (size_t x= 0; x < 10; ++x) + { + test_truth(memcached_pool_push(pool, mmc[x]) == MEMCACHED_SUCCESS); + } + + + /* verify that I can set behaviors on the pool when I don't have all + * of the connections in the pool. It should however be enabled + * when I push the item into the pool + */ + mmc[0]= memcached_pool_pop(pool, false, &rc); + test_truth(mmc[0] != NULL); + + rc= memcached_pool_behavior_set(pool, MEMCACHED_BEHAVIOR_IO_MSG_WATERMARK, 9999); + test_truth(rc == MEMCACHED_SUCCESS); + + mmc[1]= memcached_pool_pop(pool, false, &rc); + test_truth(mmc[1] != NULL); + + test_truth(memcached_behavior_get(mmc[1], MEMCACHED_BEHAVIOR_IO_MSG_WATERMARK) == 9999); + test_truth(memcached_pool_push(pool, mmc[1]) == MEMCACHED_SUCCESS); + test_truth(memcached_pool_push(pool, mmc[0]) == MEMCACHED_SUCCESS); + + mmc[0]= memcached_pool_pop(pool, false, &rc); + test_truth(memcached_behavior_get(mmc[0], MEMCACHED_BEHAVIOR_IO_MSG_WATERMARK) == 9999); + test_truth(memcached_pool_push(pool, mmc[0]) == MEMCACHED_SUCCESS); + + + test_truth(memcached_pool_destroy(pool) == memc); + return TEST_SUCCESS; +} +#endif + +static test_return_t replication_set_test(memcached_st *memc) +{ + memcached_return_t rc; + memcached_st *memc_clone= memcached_clone(NULL, memc); + memcached_behavior_set(memc_clone, MEMCACHED_BEHAVIOR_NUMBER_OF_REPLICAS, 0); + + rc= memcached_set(memc, "bubba", 5, "0", 1, 0, 0); + test_truth(rc == MEMCACHED_SUCCESS); + + /* + ** We are using the quiet commands to store the replicas, so we need + ** to ensure that all of them are processed before we can continue. + ** In the test we go directly from storing the object to trying to + ** receive the object from all of the different servers, so we + ** could end up in a race condition (the memcached server hasn't yet + ** processed the quiet command from the replication set when it process + ** the request from the other client (created by the clone)). As a + ** workaround for that we call memcached_quit to send the quit command + ** to the server and wait for the response ;-) If you use the test code + ** as an example for your own code, please note that you shouldn't need + ** to do this ;-) + */ + memcached_quit(memc); + + /* + ** "bubba" should now be stored on all of our servers. We don't have an + ** easy to use API to address each individual server, so I'll just iterate + ** through a bunch of "master keys" and I should most likely hit all of the + ** servers... + */ + for (int x= 'a'; x <= 'z'; ++x) + { + char key[2]= { [0]= (char)x }; + size_t len; + uint32_t flags; + char *val= memcached_get_by_key(memc_clone, key, 1, "bubba", 5, + &len, &flags, &rc); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(val != NULL); + free(val); + } + + memcached_free(memc_clone); + + return TEST_SUCCESS; +} + +static test_return_t replication_get_test(memcached_st *memc) +{ + memcached_return_t rc; + + /* + * Don't do the following in your code. I am abusing the internal details + * within the library, and this is not a supported interface. + * This is to verify correct behavior in the library + */ + for (uint32_t host= 0; host < memcached_server_count(memc); ++host) + { + memcached_st *memc_clone= memcached_clone(NULL, memc); + memcached_server_instance_st *instance= + memcached_server_instance_fetch(memc_clone, host); + + instance->port= 0; + + for (int x= 'a'; x <= 'z'; ++x) + { + char key[2]= { [0]= (char)x }; + size_t len; + uint32_t flags; + char *val= memcached_get_by_key(memc_clone, key, 1, "bubba", 5, + &len, &flags, &rc); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(val != NULL); + free(val); + } + + memcached_free(memc_clone); + } + + return TEST_SUCCESS; +} + +static test_return_t replication_mget_test(memcached_st *memc) +{ + memcached_return_t rc; + memcached_st *memc_clone= memcached_clone(NULL, memc); + memcached_behavior_set(memc_clone, MEMCACHED_BEHAVIOR_NUMBER_OF_REPLICAS, 0); + + const char *keys[]= { "bubba", "key1", "key2", "key3" }; + size_t len[]= { 5, 4, 4, 4 }; + + for (size_t x= 0; x< 4; ++x) + { + rc= memcached_set(memc, keys[x], len[x], "0", 1, 0, 0); + test_truth(rc == MEMCACHED_SUCCESS); + } + + /* + ** We are using the quiet commands to store the replicas, so we need + ** to ensure that all of them are processed before we can continue. + ** In the test we go directly from storing the object to trying to + ** receive the object from all of the different servers, so we + ** could end up in a race condition (the memcached server hasn't yet + ** processed the quiet command from the replication set when it process + ** the request from the other client (created by the clone)). As a + ** workaround for that we call memcached_quit to send the quit command + ** to the server and wait for the response ;-) If you use the test code + ** as an example for your own code, please note that you shouldn't need + ** to do this ;-) + */ + memcached_quit(memc); + + /* + * Don't do the following in your code. I am abusing the internal details + * within the library, and this is not a supported interface. + * This is to verify correct behavior in the library + */ + memcached_result_st result_obj; + for (uint32_t host= 0; host < memc_clone->number_of_hosts; host++) + { + memcached_st *new_clone= memcached_clone(NULL, memc); + memcached_server_instance_st *instance= + memcached_server_instance_fetch(new_clone, host); + instance->port= 0; + + for (int x= 'a'; x <= 'z'; ++x) + { + char key[2]= { [0]= (char)x, [1]= 0 }; + + rc= memcached_mget_by_key(new_clone, key, 1, keys, len, 4); + test_truth(rc == MEMCACHED_SUCCESS); + + memcached_result_st *results= memcached_result_create(new_clone, &result_obj); + test_truth(results); + + int hits= 0; + while ((results= memcached_fetch_result(new_clone, &result_obj, &rc)) != NULL) + { + hits++; + } + test_truth(hits == 4); + memcached_result_free(&result_obj); + } + + memcached_free(new_clone); + } + + memcached_free(memc_clone); + + return TEST_SUCCESS; +} + +static test_return_t replication_randomize_mget_test(memcached_st *memc) +{ + memcached_result_st result_obj; + memcached_return_t rc; + memcached_st *memc_clone= memcached_clone(NULL, memc); + memcached_behavior_set(memc_clone, MEMCACHED_BEHAVIOR_NUMBER_OF_REPLICAS, 3); + memcached_behavior_set(memc_clone, MEMCACHED_BEHAVIOR_RANDOMIZE_REPLICA_READ, 1); + + const char *keys[]= { "key1", "key2", "key3", "key4", "key5", "key6", "key7" }; + size_t len[]= { 4, 4, 4, 4, 4, 4, 4 }; + + for (int x=0; x< 7; ++x) + { + rc= memcached_set(memc, keys[x], len[x], "1", 1, 0, 0); + test_truth(rc == MEMCACHED_SUCCESS); + } + + memcached_quit(memc); + + for (size_t x= 0; x< 7; ++x) + { + const char key[2]= { [0]= (const char)x }; + + rc= memcached_mget_by_key(memc_clone, key, 1, keys, len, 7); + test_truth(rc == MEMCACHED_SUCCESS); + + memcached_result_st *results= memcached_result_create(memc_clone, &result_obj); + test_truth(results); + + int hits= 0; + while ((results= memcached_fetch_result(memc_clone, &result_obj, &rc)) != NULL) + { + ++hits; + } + test_truth(hits == 7); + memcached_result_free(&result_obj); + } + memcached_free(memc_clone); + return TEST_SUCCESS; +} + +static test_return_t replication_delete_test(memcached_st *memc) +{ + memcached_return_t rc; + memcached_st *memc_clone= memcached_clone(NULL, memc); + /* Delete the items from all of the servers except 1 */ + uint64_t repl= memcached_behavior_get(memc, + MEMCACHED_BEHAVIOR_NUMBER_OF_REPLICAS); + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_NUMBER_OF_REPLICAS, --repl); + + const char *keys[]= { "bubba", "key1", "key2", "key3" }; + size_t len[]= { 5, 4, 4, 4 }; + + for (size_t x= 0; x< 4; ++x) + { + rc= memcached_delete_by_key(memc, keys[0], len[0], keys[x], len[x], 0); + test_truth(rc == MEMCACHED_SUCCESS); + } + + /* + * Don't do the following in your code. I am abusing the internal details + * within the library, and this is not a supported interface. + * This is to verify correct behavior in the library + */ + uint32_t hash= memcached_generate_hash(memc, keys[0], len[0]); + for (uint32_t x= 0; x < (repl + 1); ++x) + { + memcached_server_instance_st *instance= + memcached_server_instance_fetch(memc_clone, x); + + instance->port= 0; + if (++hash == memc_clone->number_of_hosts) + hash= 0; + } + + memcached_result_st result_obj; + for (uint32_t host= 0; host < memc_clone->number_of_hosts; ++host) + { + for (size_t x= 'a'; x <= 'z'; ++x) + { + const char key[2]= { [0]= (const char)x }; + + rc= memcached_mget_by_key(memc_clone, key, 1, keys, len, 4); + test_truth(rc == MEMCACHED_SUCCESS); + + memcached_result_st *results= memcached_result_create(memc_clone, &result_obj); + test_truth(results); + + int hits= 0; + while ((results= memcached_fetch_result(memc_clone, &result_obj, &rc)) != NULL) + { + ++hits; + } + test_truth(hits == 4); + memcached_result_free(&result_obj); + } + } + memcached_free(memc_clone); + + return TEST_SUCCESS; +} + +static void increment_request_id(uint16_t *id) +{ + (*id)++; + if ((*id & UDP_REQUEST_ID_THREAD_MASK) != 0) + *id= 0; +} + +static uint16_t *get_udp_request_ids(memcached_st *memc) +{ + uint16_t *ids= malloc(sizeof(uint16_t) * memcached_server_count(memc)); + assert(ids != NULL); + + for (uint32_t x= 0; x < memcached_server_count(memc); x++) + { + memcached_server_instance_st *instance= + memcached_server_instance_fetch(memc, x); + + ids[x]= get_udp_datagram_request_id((struct udp_datagram_header_st *) instance->write_buffer); + } + + return ids; +} + +static test_return_t post_udp_op_check(memcached_st *memc, uint16_t *expected_req_ids) +{ + memcached_server_st *cur_server = memcached_server_list(memc); + uint16_t *cur_req_ids = get_udp_request_ids(memc); + + for (size_t x= 0; x < memcached_server_count(memc); x++) + { + test_truth(cur_server[x].cursor_active == 0); + test_truth(cur_req_ids[x] == expected_req_ids[x]); + } + free(expected_req_ids); + free(cur_req_ids); + + return TEST_SUCCESS; +} + +/* +** There is a little bit of a hack here, instead of removing +** the servers, I just set num host to 0 and them add then new udp servers +**/ +static test_return_t init_udp(memcached_st *memc) +{ + memcached_version(memc); + memcached_server_instance_st *instance= + memcached_server_instance_fetch(memc, 0); + + /* For the time being, only support udp test for >= 1.2.6 && < 1.3 */ + if (instance->major_version != 1 || instance->minor_version != 2 + || instance->micro_version < 6) + return TEST_SKIPPED; + + uint32_t num_hosts= memcached_server_count(memc); + memcached_server_st servers[num_hosts]; + memcpy(servers, memcached_server_list(memc), sizeof(memcached_server_st) * num_hosts); + for (uint32_t x= 0; x < num_hosts; x++) + { + memcached_server_instance_st *set_instance= + memcached_server_instance_fetch(memc, x); + + memcached_server_free(set_instance); + } + + memc->number_of_hosts= 0; + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_USE_UDP, 1); + for (uint32_t x= 0; x < num_hosts; x++) + { + memcached_server_instance_st *set_instance= + memcached_server_instance_fetch(memc, x); + + test_truth(memcached_server_add_udp(memc, servers[x].hostname, servers[x].port) == MEMCACHED_SUCCESS); + test_truth(set_instance->write_buffer_offset == UDP_DATAGRAM_HEADER_LENGTH); + } + + return TEST_SUCCESS; +} + +static test_return_t binary_init_udp(memcached_st *memc) +{ + test_return_t test_rc; + test_rc= pre_binary(memc); + + if (test_rc != TEST_SUCCESS) + return test_rc; + + return init_udp(memc); +} + +/* Make sure that I cant add a tcp server to a udp client */ +static test_return_t add_tcp_server_udp_client_test(memcached_st *memc) +{ + (void)memc; +#if 0 + memcached_server_st server; + memcached_server_instance_st *instance= + memcached_server_instance_fetch(memc, 0); + memcached_server_clone(&server, &memc->hosts[0]); + test_truth(memcached_server_remove(&(memc->hosts[0])) == MEMCACHED_SUCCESS); + test_truth(memcached_server_add(memc, server.hostname, server.port) == MEMCACHED_INVALID_HOST_PROTOCOL); +#endif + return TEST_SUCCESS; +} + +/* Make sure that I cant add a udp server to a tcp client */ +static test_return_t add_udp_server_tcp_client_test(memcached_st *memc) +{ + (void)memc; +#if 0 + memcached_server_st server; + memcached_server_instance_st *instance= + memcached_server_instance_fetch(memc, 0); + memcached_server_clone(&server, &memc->hosts[0]); + test_truth(memcached_server_remove(&(memc->hosts[0])) == MEMCACHED_SUCCESS); + + memcached_st tcp_client; + memcached_create(&tcp_client); + test_truth(memcached_server_add_udp(&tcp_client, server.hostname, server.port) == MEMCACHED_INVALID_HOST_PROTOCOL); +#endif + + return TEST_SUCCESS; +} + +static test_return_t set_udp_behavior_test(memcached_st *memc) +{ + + memcached_quit(memc); + memc->number_of_hosts= 0; + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_DISTRIBUTION, memc->distribution); + test_truth(memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_USE_UDP, 1) == MEMCACHED_SUCCESS); + test_truth(memc->flags.use_udp); + test_truth(memc->flags.no_reply); + + test_truth(memcached_server_count(memc) == 0); + + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_USE_UDP,0); + test_truth(! (memc->flags.use_udp)); + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_NOREPLY,0); + test_truth(! (memc->flags.no_reply)); + + return TEST_SUCCESS; +} + +static test_return_t udp_set_test(memcached_st *memc) +{ + unsigned int num_iters= 1025; //request id rolls over at 1024 + + for (size_t x= 0; x < num_iters;x++) + { + memcached_return_t rc; + const char *key= "foo"; + const char *value= "when we sanitize"; + uint16_t *expected_ids= get_udp_request_ids(memc); + unsigned int server_key= memcached_generate_hash(memc, key, strlen(key)); + memcached_server_instance_st *instance= + memcached_server_instance_fetch(memc, server_key); + size_t init_offset= instance->write_buffer_offset; + + rc= memcached_set(memc, key, strlen(key), + value, strlen(value), + (time_t)0, (uint32_t)0); + test_truth(rc == MEMCACHED_SUCCESS || rc == MEMCACHED_BUFFERED); + /** NB, the check below assumes that if new write_ptr is less than + * the original write_ptr that we have flushed. For large payloads, this + * maybe an invalid assumption, but for the small payload we have it is OK + */ + if (rc == MEMCACHED_SUCCESS || + instance->write_buffer_offset < init_offset) + increment_request_id(&expected_ids[server_key]); + + if (rc == MEMCACHED_SUCCESS) + { + test_truth(instance->write_buffer_offset == UDP_DATAGRAM_HEADER_LENGTH); + } + else + { + test_truth(instance->write_buffer_offset != UDP_DATAGRAM_HEADER_LENGTH); + test_truth(instance->write_buffer_offset <= MAX_UDP_DATAGRAM_LENGTH); + } + test_truth(post_udp_op_check(memc, expected_ids) == TEST_SUCCESS); + } + return TEST_SUCCESS; +} + +static test_return_t udp_buffered_set_test(memcached_st *memc) +{ + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_BUFFER_REQUESTS, 1); + return udp_set_test(memc); +} + +static test_return_t udp_set_too_big_test(memcached_st *memc) +{ + memcached_return_t rc; + const char *key= "bar"; + char value[MAX_UDP_DATAGRAM_LENGTH]; + uint16_t *expected_ids= get_udp_request_ids(memc); + rc= memcached_set(memc, key, strlen(key), + value, MAX_UDP_DATAGRAM_LENGTH, + (time_t)0, (uint32_t)0); + test_truth(rc == MEMCACHED_WRITE_FAILURE); + + return post_udp_op_check(memc,expected_ids); +} + +static test_return_t udp_delete_test(memcached_st *memc) +{ + unsigned int num_iters= 1025; //request id rolls over at 1024 + + for (size_t x= 0; x < num_iters;x++) + { + memcached_return_t rc; + const char *key= "foo"; + uint16_t *expected_ids=get_udp_request_ids(memc); + unsigned int server_key= memcached_generate_hash(memc, key, strlen(key)); + memcached_server_instance_st *instance= + memcached_server_instance_fetch(memc, server_key); + size_t init_offset= instance->write_buffer_offset; + + rc= memcached_delete(memc, key, strlen(key), 0); + test_truth(rc == MEMCACHED_SUCCESS || rc == MEMCACHED_BUFFERED); + + if (rc == MEMCACHED_SUCCESS || instance->write_buffer_offset < init_offset) + increment_request_id(&expected_ids[server_key]); + if (rc == MEMCACHED_SUCCESS) + { + test_truth(instance->write_buffer_offset == UDP_DATAGRAM_HEADER_LENGTH); + } + else + { + test_truth(instance->write_buffer_offset != UDP_DATAGRAM_HEADER_LENGTH); + test_truth(instance->write_buffer_offset <= MAX_UDP_DATAGRAM_LENGTH); + } + test_truth(post_udp_op_check(memc,expected_ids) == TEST_SUCCESS); + } + return TEST_SUCCESS; +} + +static test_return_t udp_buffered_delete_test(memcached_st *memc) +{ + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_BUFFER_REQUESTS, 1); + return udp_delete_test(memc); +} + +static test_return_t udp_verbosity_test(memcached_st *memc) +{ + memcached_return_t rc; + uint16_t *expected_ids= get_udp_request_ids(memc); + + for (size_t x= 0; x < memcached_server_count(memc); x++) + { + increment_request_id(&expected_ids[x]); + } + + rc= memcached_verbosity(memc,3); + test_truth(rc == MEMCACHED_SUCCESS); + return post_udp_op_check(memc,expected_ids); +} + +static test_return_t udp_quit_test(memcached_st *memc) +{ + uint16_t *expected_ids= get_udp_request_ids(memc); + memcached_quit(memc); + return post_udp_op_check(memc, expected_ids); +} + +static test_return_t udp_flush_test(memcached_st *memc) +{ + memcached_return_t rc; + uint16_t *expected_ids= get_udp_request_ids(memc); + + for (size_t x= 0; x < memcached_server_count(memc); x++) + { + increment_request_id(&expected_ids[x]); + } + + rc= memcached_flush(memc,0); + test_truth(rc == MEMCACHED_SUCCESS); + return post_udp_op_check(memc,expected_ids); +} + +static test_return_t udp_incr_test(memcached_st *memc) +{ + memcached_return_t rc; + const char *key= "incr"; + const char *value= "1"; + rc= memcached_set(memc, key, strlen(key), + value, strlen(value), + (time_t)0, (uint32_t)0); + + test_truth(rc == MEMCACHED_SUCCESS); + uint16_t *expected_ids= get_udp_request_ids(memc); + unsigned int server_key= memcached_generate_hash(memc, key, strlen(key)); + increment_request_id(&expected_ids[server_key]); + uint64_t newvalue; + rc= memcached_increment(memc, key, strlen(key), 1, &newvalue); + test_truth(rc == MEMCACHED_SUCCESS); + return post_udp_op_check(memc, expected_ids); +} + +static test_return_t udp_decr_test(memcached_st *memc) +{ + memcached_return_t rc; + const char *key= "decr"; + const char *value= "1"; + rc= memcached_set(memc, key, strlen(key), + value, strlen(value), + (time_t)0, (uint32_t)0); + + test_truth(rc == MEMCACHED_SUCCESS); + uint16_t *expected_ids= get_udp_request_ids(memc); + unsigned int server_key= memcached_generate_hash(memc, key, strlen(key)); + increment_request_id(&expected_ids[server_key]); + uint64_t newvalue; + rc= memcached_decrement(memc, key, strlen(key), 1, &newvalue); + test_truth(rc == MEMCACHED_SUCCESS); + return post_udp_op_check(memc, expected_ids); +} + + +static test_return_t udp_stat_test(memcached_st *memc) +{ + memcached_stat_st * rv= NULL; + memcached_return_t rc; + char args[]= ""; + uint16_t *expected_ids = get_udp_request_ids(memc); + rv = memcached_stat(memc, args, &rc); + free(rv); + test_truth(rc == MEMCACHED_NOT_SUPPORTED); + return post_udp_op_check(memc, expected_ids); +} + +static test_return_t udp_version_test(memcached_st *memc) +{ + memcached_return_t rc; + uint16_t *expected_ids = get_udp_request_ids(memc); + rc = memcached_version(memc); + test_truth(rc == MEMCACHED_NOT_SUPPORTED); + return post_udp_op_check(memc, expected_ids); +} + +static test_return_t udp_get_test(memcached_st *memc) +{ + memcached_return_t rc; + const char *key= "foo"; + size_t vlen; + uint16_t *expected_ids = get_udp_request_ids(memc); + char *val= memcached_get(memc, key, strlen(key), &vlen, (uint32_t)0, &rc); + test_truth(rc == MEMCACHED_NOT_SUPPORTED); + test_truth(val == NULL); + return post_udp_op_check(memc, expected_ids); +} + +static test_return_t udp_mixed_io_test(memcached_st *memc) +{ + test_st current_op; + test_st mixed_io_ops [] ={ + {"udp_set_test", 0, + (test_callback_fn)udp_set_test}, + {"udp_set_too_big_test", 0, + (test_callback_fn)udp_set_too_big_test}, + {"udp_delete_test", 0, + (test_callback_fn)udp_delete_test}, + {"udp_verbosity_test", 0, + (test_callback_fn)udp_verbosity_test}, + {"udp_quit_test", 0, + (test_callback_fn)udp_quit_test}, + {"udp_flush_test", 0, + (test_callback_fn)udp_flush_test}, + {"udp_incr_test", 0, + (test_callback_fn)udp_incr_test}, + {"udp_decr_test", 0, + (test_callback_fn)udp_decr_test}, + {"udp_version_test", 0, + (test_callback_fn)udp_version_test} + }; + for (size_t x= 0; x < 500; x++) + { + current_op= mixed_io_ops[random() % 9]; + test_truth(current_op.test_fn(memc) == TEST_SUCCESS); + } + return TEST_SUCCESS; +} + +#if 0 +static test_return_t hash_sanity_test (memcached_st *memc) +{ + (void)memc; + + assert(MEMCACHED_HASH_DEFAULT == MEMCACHED_HASH_DEFAULT); + assert(MEMCACHED_HASH_MD5 == MEMCACHED_HASH_MD5); + assert(MEMCACHED_HASH_CRC == MEMCACHED_HASH_CRC); + assert(MEMCACHED_HASH_FNV1_64 == MEMCACHED_HASH_FNV1_64); + assert(MEMCACHED_HASH_FNV1A_64 == MEMCACHED_HASH_FNV1A_64); + assert(MEMCACHED_HASH_FNV1_32 == MEMCACHED_HASH_FNV1_32); + assert(MEMCACHED_HASH_FNV1A_32 == MEMCACHED_HASH_FNV1A_32); +#ifdef HAVE_HSIEH_HASH + assert(MEMCACHED_HASH_HSIEH == MEMCACHED_HASH_HSIEH); +#endif + assert(MEMCACHED_HASH_MURMUR == MEMCACHED_HASH_MURMUR); + assert(MEMCACHED_HASH_JENKINS == MEMCACHED_HASH_JENKINS); + assert(MEMCACHED_HASH_MAX == MEMCACHED_HASH_MAX); + + return TEST_SUCCESS; +} +#endif + +static test_return_t hsieh_avaibility_test (memcached_st *memc) +{ + memcached_return_t expected_rc= MEMCACHED_FAILURE; +#ifdef HAVE_HSIEH_HASH + expected_rc= MEMCACHED_SUCCESS; +#endif + memcached_return_t rc= memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_HASH, + (uint64_t)MEMCACHED_HASH_HSIEH); + test_truth(rc == expected_rc); + return TEST_SUCCESS; +} + +static test_return_t md5_run (memcached_st *memc __attribute__((unused))) +{ + uint32_t x; + const char **ptr; + + for (ptr= list_to_hash, x= 0; *ptr; ptr++, x++) + { + uint32_t hash_val; + + hash_val= memcached_generate_hash_value(*ptr, strlen(*ptr), MEMCACHED_HASH_MD5); + test_truth(md5_values[x] == hash_val); + } + + return TEST_SUCCESS; +} + +static test_return_t crc_run (memcached_st *memc __attribute__((unused))) +{ + uint32_t x; + const char **ptr; + + for (ptr= list_to_hash, x= 0; *ptr; ptr++, x++) + { + uint32_t hash_val; + + hash_val= memcached_generate_hash_value(*ptr, strlen(*ptr), MEMCACHED_HASH_CRC); + test_truth(crc_values[x] == hash_val); + } + + return TEST_SUCCESS; +} + +static test_return_t fnv1_64_run (memcached_st *memc __attribute__((unused))) +{ + uint32_t x; + const char **ptr; + + for (ptr= list_to_hash, x= 0; *ptr; ptr++, x++) + { + uint32_t hash_val; + + hash_val= memcached_generate_hash_value(*ptr, strlen(*ptr), MEMCACHED_HASH_FNV1_64); + test_truth(fnv1_64_values[x] == hash_val); + } + + return TEST_SUCCESS; +} + +static test_return_t fnv1a_64_run (memcached_st *memc __attribute__((unused))) +{ + uint32_t x; + const char **ptr; + + for (ptr= list_to_hash, x= 0; *ptr; ptr++, x++) + { + uint32_t hash_val; + + hash_val= memcached_generate_hash_value(*ptr, strlen(*ptr), MEMCACHED_HASH_FNV1A_64); + test_truth(fnv1a_64_values[x] == hash_val); + } + + return TEST_SUCCESS; +} + +static test_return_t fnv1_32_run (memcached_st *memc __attribute__((unused))) +{ + uint32_t x; + const char **ptr; + + + for (ptr= list_to_hash, x= 0; *ptr; ptr++, x++) + { + uint32_t hash_val; + + hash_val= memcached_generate_hash_value(*ptr, strlen(*ptr), MEMCACHED_HASH_FNV1_32); + test_truth(fnv1_32_values[x] == hash_val); + } + + return TEST_SUCCESS; +} + +static test_return_t fnv1a_32_run (memcached_st *memc __attribute__((unused))) +{ + uint32_t x; + const char **ptr; + + for (ptr= list_to_hash, x= 0; *ptr; ptr++, x++) + { + uint32_t hash_val; + + hash_val= memcached_generate_hash_value(*ptr, strlen(*ptr), MEMCACHED_HASH_FNV1A_32); + test_truth(fnv1a_32_values[x] == hash_val); + } + + return TEST_SUCCESS; +} + +static test_return_t hsieh_run (memcached_st *memc __attribute__((unused))) +{ + uint32_t x; + const char **ptr; + + for (ptr= list_to_hash, x= 0; *ptr; ptr++, x++) + { + uint32_t hash_val; + + hash_val= memcached_generate_hash_value(*ptr, strlen(*ptr), MEMCACHED_HASH_HSIEH); + test_truth(hsieh_values[x] == hash_val); + } + + return TEST_SUCCESS; +} + +static test_return_t murmur_run (memcached_st *memc __attribute__((unused))) +{ +#ifdef __sparc + return TEST_SKIPPED; +#else + uint32_t x; + const char **ptr; + + for (ptr= list_to_hash, x= 0; *ptr; ptr++, x++) + { + uint32_t hash_val; + + hash_val= memcached_generate_hash_value(*ptr, strlen(*ptr), MEMCACHED_HASH_MURMUR); + test_truth(murmur_values[x] == hash_val); + } + + return TEST_SUCCESS; +#endif +} + +static test_return_t jenkins_run (memcached_st *memc __attribute__((unused))) +{ + uint32_t x; + const char **ptr; + + + for (ptr= list_to_hash, x= 0; *ptr; ptr++, x++) + { + uint32_t hash_val; + + hash_val= memcached_generate_hash_value(*ptr, strlen(*ptr), MEMCACHED_HASH_JENKINS); + test_truth(jenkins_values[x] == hash_val); + } + + return TEST_SUCCESS; +} + + +static test_return_t ketama_compatibility_libmemcached(memcached_st *trash) +{ + memcached_return_t rc; + uint64_t value; + int x; + memcached_server_st *server_pool; + memcached_st *memc; + + (void)trash; + + memc= memcached_create(NULL); + test_truth(memc); + + rc= memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_KETAMA_WEIGHTED, 1); + test_truth(rc == MEMCACHED_SUCCESS); + + value= memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_KETAMA_WEIGHTED); + test_truth(value == 1); + + test_truth(memcached_behavior_set_distribution(memc, MEMCACHED_DISTRIBUTION_CONSISTENT_KETAMA) == MEMCACHED_SUCCESS); + test_truth(memcached_behavior_get_distribution(memc) == MEMCACHED_DISTRIBUTION_CONSISTENT_KETAMA); + + + server_pool = memcached_servers_parse("10.0.1.1:11211 600,10.0.1.2:11211 300,10.0.1.3:11211 200,10.0.1.4:11211 350,10.0.1.5:11211 1000,10.0.1.6:11211 800,10.0.1.7:11211 950,10.0.1.8:11211 100"); + memcached_server_push(memc, server_pool); + + /* verify that the server list was parsed okay. */ + test_truth(memcached_server_count(memc) == 8); + test_strcmp(server_pool[0].hostname, "10.0.1.1"); + test_truth(server_pool[0].port == 11211); + test_truth(server_pool[0].weight == 600); + test_strcmp(server_pool[2].hostname, "10.0.1.3"); + test_truth(server_pool[2].port == 11211); + test_truth(server_pool[2].weight == 200); + test_strcmp(server_pool[7].hostname, "10.0.1.8"); + test_truth(server_pool[7].port == 11211); + test_truth(server_pool[7].weight == 100); + + /* VDEAAAAA hashes to fffcd1b5, after the last continuum point, and lets + * us test the boundary wraparound. + */ + test_truth(memcached_generate_hash(memc, (char *)"VDEAAAAA", 8) == memc->continuum[0].index); + + /* verify the standard ketama set. */ + for (x= 0; x < 99; x++) + { + uint32_t server_idx = memcached_generate_hash(memc, ketama_test_cases[x].key, strlen(ketama_test_cases[x].key)); + memcached_server_instance_st *instance= + memcached_server_instance_fetch(memc, server_idx); + char *hostname = instance->hostname; + + test_strcmp(hostname, ketama_test_cases[x].server); + } + + memcached_server_list_free(server_pool); + memcached_free(memc); + + return TEST_SUCCESS; +} + +static test_return_t ketama_compatibility_spymemcached(memcached_st *trash) +{ + memcached_return_t rc; + uint64_t value; + int x; + memcached_server_st *server_pool; + memcached_st *memc; + + (void)trash; + + memc= memcached_create(NULL); + test_truth(memc); + + rc= memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_KETAMA_WEIGHTED, 1); + test_truth(rc == MEMCACHED_SUCCESS); + + value= memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_KETAMA_WEIGHTED); + test_truth(value == 1); + + test_truth(memcached_behavior_set_distribution(memc, MEMCACHED_DISTRIBUTION_CONSISTENT_KETAMA_SPY) == MEMCACHED_SUCCESS); + test_truth(memcached_behavior_get_distribution(memc) == MEMCACHED_DISTRIBUTION_CONSISTENT_KETAMA_SPY); + + server_pool = memcached_servers_parse("10.0.1.1:11211 600,10.0.1.2:11211 300,10.0.1.3:11211 200,10.0.1.4:11211 350,10.0.1.5:11211 1000,10.0.1.6:11211 800,10.0.1.7:11211 950,10.0.1.8:11211 100"); + memcached_server_push(memc, server_pool); + + /* verify that the server list was parsed okay. */ + test_truth(memcached_server_count(memc) == 8); + test_strcmp(server_pool[0].hostname, "10.0.1.1"); + test_truth(server_pool[0].port == 11211); + test_truth(server_pool[0].weight == 600); + test_strcmp(server_pool[2].hostname, "10.0.1.3"); + test_truth(server_pool[2].port == 11211); + test_truth(server_pool[2].weight == 200); + test_strcmp(server_pool[7].hostname, "10.0.1.8"); + test_truth(server_pool[7].port == 11211); + test_truth(server_pool[7].weight == 100); + + /* VDEAAAAA hashes to fffcd1b5, after the last continuum point, and lets + * us test the boundary wraparound. + */ + test_truth(memcached_generate_hash(memc, (char *)"VDEAAAAA", 8) == memc->continuum[0].index); + + /* verify the standard ketama set. */ + for (x= 0; x < 99; x++) + { + uint32_t server_idx = memcached_generate_hash(memc, ketama_test_cases_spy[x].key, strlen(ketama_test_cases_spy[x].key)); + memcached_server_instance_st *instance= + memcached_server_instance_fetch(memc, server_idx); + char *hostname = instance->hostname; + test_strcmp(hostname, ketama_test_cases_spy[x].server); + } + + memcached_server_list_free(server_pool); + memcached_free(memc); + + return TEST_SUCCESS; +} + +static test_return_t regression_bug_434484(memcached_st *memc) +{ + test_return_t test_rc; + test_rc= pre_binary(memc); + + if (test_rc != TEST_SUCCESS) + return test_rc; + + memcached_return_t ret; + const char *key= "regression_bug_434484"; + size_t keylen= strlen(key); + + ret= memcached_append(memc, key, keylen, key, keylen, 0, 0); + test_truth(ret == MEMCACHED_NOTSTORED); + + size_t size= 2048 * 1024; + void *data= calloc(1, size); + test_truth(data != NULL); + ret= memcached_set(memc, key, keylen, data, size, 0, 0); + test_truth(ret == MEMCACHED_E2BIG); + free(data); + + return TEST_SUCCESS; +} + +static test_return_t regression_bug_434843(memcached_st *memc) +{ + test_return_t test_rc; + test_rc= pre_binary(memc); + + if (test_rc != TEST_SUCCESS) + return test_rc; + + memcached_return_t rc; + size_t counter= 0; + memcached_execute_fn callbacks[1]= { [0]= &callback_counter }; + + /* + * I only want to hit only _one_ server so I know the number of requests I'm + * sending in the pipleine to the server. Let's try to do a multiget of + * 1024 (that should satisfy most users don't you think?). Future versions + * will include a mget_execute function call if you need a higher number. + */ + uint32_t number_of_hosts= memcached_server_count(memc); + memc->number_of_hosts= 1; + const size_t max_keys= 1024; + char **keys= calloc(max_keys, sizeof(char*)); + size_t *key_length=calloc(max_keys, sizeof(size_t)); + + for (size_t x= 0; x < max_keys; ++x) + { + char k[251]; + + key_length[x]= (size_t)snprintf(k, sizeof(k), "0200%zu", x); + keys[x]= strdup(k); + test_truth(keys[x] != NULL); + } + + /* + * Run two times.. the first time we should have 100% cache miss, + * and the second time we should have 100% cache hits + */ + for (size_t y= 0; y < 2; y++) + { + rc= memcached_mget(memc, (const char**)keys, key_length, max_keys); + test_truth(rc == MEMCACHED_SUCCESS); + rc= memcached_fetch_execute(memc, callbacks, (void *)&counter, 1); + + if (y == 0) + { + /* The first iteration should give me a 100% cache miss. verify that*/ + char blob[1024]= { 0 }; + + test_truth(counter == 0); + + for (size_t x= 0; x < max_keys; ++x) + { + rc= memcached_add(memc, keys[x], key_length[x], + blob, sizeof(blob), 0, 0); + test_truth(rc == MEMCACHED_SUCCESS || rc == MEMCACHED_BUFFERED); + } + } + else + { + /* Verify that we received all of the key/value pairs */ + test_truth(counter == max_keys); + } + } + + /* Release allocated resources */ + for (size_t x= 0; x < max_keys; ++x) + { + free(keys[x]); + } + free(keys); + free(key_length); + + memc->number_of_hosts= number_of_hosts; + + return TEST_SUCCESS; +} + +static test_return_t regression_bug_434843_buffered(memcached_st *memc) +{ + memcached_return_t rc; + rc= memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_BUFFER_REQUESTS, 1); + test_truth(rc == MEMCACHED_SUCCESS); + + return regression_bug_434843(memc); +} + +static test_return_t regression_bug_421108(memcached_st *memc) +{ + memcached_return_t rc; + memcached_stat_st *memc_stat= memcached_stat(memc, NULL, &rc); + test_truth(rc == MEMCACHED_SUCCESS); + + char *bytes= memcached_stat_get_value(memc, memc_stat, "bytes", &rc); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(bytes != NULL); + char *bytes_read= memcached_stat_get_value(memc, memc_stat, + "bytes_read", &rc); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(bytes_read != NULL); + + char *bytes_written= memcached_stat_get_value(memc, memc_stat, + "bytes_written", &rc); + test_truth(rc == MEMCACHED_SUCCESS); + test_truth(bytes_written != NULL); + + test_truth(strcmp(bytes, bytes_read) != 0); + test_truth(strcmp(bytes, bytes_written) != 0); + + /* Release allocated resources */ + free(bytes); + free(bytes_read); + free(bytes_written); + memcached_stat_free(NULL, memc_stat); + + return TEST_SUCCESS; +} + +/* + * The test case isn't obvious so I should probably document why + * it works the way it does. Bug 442914 was caused by a bug + * in the logic in memcached_purge (it did not handle the case + * where the number of bytes sent was equal to the watermark). + * In this test case, create messages so that we hit that case + * and then disable noreply mode and issue a new command to + * verify that it isn't stuck. If we change the format for the + * delete command or the watermarks, we need to update this + * test.... + */ +static test_return_t regression_bug_442914(memcached_st *memc) +{ + memcached_return_t rc; + rc= memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_NOREPLY, 1); + test_truth(rc == MEMCACHED_SUCCESS); + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_TCP_NODELAY, 1); + + uint32_t number_of_hosts= memcached_server_count(memc); + memc->number_of_hosts= 1; + + char k[250]; + size_t len; + + for (uint32_t x= 0; x < 250; ++x) + { + len= (size_t)snprintf(k, sizeof(k), "%0250u", x); + rc= memcached_delete(memc, k, len, 0); + test_truth(rc == MEMCACHED_SUCCESS || rc == MEMCACHED_BUFFERED); + } + + (void)snprintf(k, sizeof(k), "%037u", 251U); + len= strlen(k); + + rc= memcached_delete(memc, k, len, 0); + test_truth(rc == MEMCACHED_SUCCESS || rc == MEMCACHED_BUFFERED); + + rc= memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_NOREPLY, 0); + test_truth(rc == MEMCACHED_SUCCESS); + rc= memcached_delete(memc, k, len, 0); + test_truth(rc == MEMCACHED_NOTFOUND); + + memc->number_of_hosts= number_of_hosts; + + return TEST_SUCCESS; +} + +static test_return_t regression_bug_447342(memcached_st *memc) +{ + memcached_server_instance_st *instance_one; + memcached_server_instance_st *instance_two; + + if (memcached_server_count(memc) < 3 || pre_replication(memc) != MEMCACHED_SUCCESS) + return TEST_SKIPPED; + + memcached_return_t rc; + + rc= memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_NUMBER_OF_REPLICAS, 2); + test_truth(rc == MEMCACHED_SUCCESS); + + const size_t max_keys= 100; + char **keys= calloc(max_keys, sizeof(char*)); + size_t *key_length= calloc(max_keys, sizeof(size_t)); + + for (size_t x= 0; x < max_keys; ++x) + { + char k[251]; + + key_length[x]= (size_t)snprintf(k, sizeof(k), "0200%zu", x); + keys[x]= strdup(k); + test_truth(keys[x] != NULL); + rc= memcached_set(memc, k, key_length[x], k, key_length[x], 0, 0); + test_truth(rc == MEMCACHED_SUCCESS); + } + + /* + ** We are using the quiet commands to store the replicas, so we need + ** to ensure that all of them are processed before we can continue. + ** In the test we go directly from storing the object to trying to + ** receive the object from all of the different servers, so we + ** could end up in a race condition (the memcached server hasn't yet + ** processed the quiet command from the replication set when it process + ** the request from the other client (created by the clone)). As a + ** workaround for that we call memcached_quit to send the quit command + ** to the server and wait for the response ;-) If you use the test code + ** as an example for your own code, please note that you shouldn't need + ** to do this ;-) + */ + memcached_quit(memc); + + /* Verify that all messages are stored, and we didn't stuff too much + * into the servers + */ + rc= memcached_mget(memc, (const char* const *)keys, key_length, max_keys); + test_truth(rc == MEMCACHED_SUCCESS); + + size_t counter= 0; + memcached_execute_fn callbacks[1]= { [0]= &callback_counter }; + rc= memcached_fetch_execute(memc, callbacks, (void *)&counter, 1); + /* Verify that we received all of the key/value pairs */ + test_truth(counter == max_keys); + + memcached_quit(memc); + /* + * Don't do the following in your code. I am abusing the internal details + * within the library, and this is not a supported interface. + * This is to verify correct behavior in the library. Fake that two servers + * are dead.. + */ + instance_one= memcached_server_instance_fetch(memc, 0); + instance_two= memcached_server_instance_fetch(memc, 2); + in_port_t port0= instance_one->port; + in_port_t port2= instance_two->port; + + instance_one->port= 0; + instance_two->port= 0; + + rc= memcached_mget(memc, (const char* const *)keys, key_length, max_keys); + test_truth(rc == MEMCACHED_SUCCESS); + + counter= 0; + rc= memcached_fetch_execute(memc, callbacks, (void *)&counter, 1); + test_truth(counter == (unsigned int)max_keys); + + /* restore the memc handle */ + instance_one->port= port0; + instance_two->port= port2; + + memcached_quit(memc); + + /* Remove half of the objects */ + for (size_t x= 0; x < max_keys; ++x) + { + if (x & 1) + { + rc= memcached_delete(memc, keys[x], key_length[x], 0); + test_truth(rc == MEMCACHED_SUCCESS); + } + } + + memcached_quit(memc); + instance_one->port= 0; + instance_two->port= 0; + + /* now retry the command, this time we should have cache misses */ + rc= memcached_mget(memc, (const char* const *)keys, key_length, max_keys); + test_truth(rc == MEMCACHED_SUCCESS); + + counter= 0; + rc= memcached_fetch_execute(memc, callbacks, (void *)&counter, 1); + test_truth(counter == (unsigned int)(max_keys >> 1)); + + /* Release allocated resources */ + for (size_t x= 0; x < max_keys; ++x) + { + free(keys[x]); + } + free(keys); + free(key_length); + + /* restore the memc handle */ + instance_one->port= port0; + instance_two->port= port2; + + return TEST_SUCCESS; +} + +static test_return_t regression_bug_463297(memcached_st *memc) +{ + memcached_st *memc_clone= memcached_clone(NULL, memc); + test_truth(memc_clone != NULL); + test_truth(memcached_version(memc_clone) == MEMCACHED_SUCCESS); + + memcached_server_instance_st *instance= + memcached_server_instance_fetch(memc_clone, 0); + + if (instance->major_version > 1 || + (instance->major_version == 1 && + instance->minor_version > 2)) + { + /* Binary protocol doesn't support deferred delete */ + memcached_st *bin_clone= memcached_clone(NULL, memc); + test_truth(bin_clone != NULL); + test_truth(memcached_behavior_set(bin_clone, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, 1) == MEMCACHED_SUCCESS); + test_truth(memcached_delete(bin_clone, "foo", 3, 1) == MEMCACHED_INVALID_ARGUMENTS); + memcached_free(bin_clone); + + memcached_quit(memc_clone); + + /* If we know the server version, deferred delete should fail + * with invalid arguments */ + test_truth(memcached_delete(memc_clone, "foo", 3, 1) == MEMCACHED_INVALID_ARGUMENTS); + + /* If we don't know the server version, we should get a protocol error */ + memcached_return_t rc= memcached_delete(memc, "foo", 3, 1); + + /* but there is a bug in some of the memcached servers (1.4) that treats + * the counter as noreply so it doesn't send the proper error message + */ + test_truth(rc == MEMCACHED_PROTOCOL_ERROR || rc == MEMCACHED_NOTFOUND || rc == MEMCACHED_CLIENT_ERROR); + + /* And buffered mode should be disabled and we should get protocol error */ + test_truth(memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_BUFFER_REQUESTS, 1) == MEMCACHED_SUCCESS); + rc= memcached_delete(memc, "foo", 3, 1); + test_truth(rc == MEMCACHED_PROTOCOL_ERROR || rc == MEMCACHED_NOTFOUND || rc == MEMCACHED_CLIENT_ERROR); + + /* Same goes for noreply... */ + test_truth(memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_NOREPLY, 1) == MEMCACHED_SUCCESS); + rc= memcached_delete(memc, "foo", 3, 1); + test_truth(rc == MEMCACHED_PROTOCOL_ERROR || rc == MEMCACHED_NOTFOUND || rc == MEMCACHED_CLIENT_ERROR); + + /* but a normal request should go through (and be buffered) */ + test_truth((rc= memcached_delete(memc, "foo", 3, 0)) == MEMCACHED_BUFFERED); + test_truth(memcached_flush_buffers(memc) == MEMCACHED_SUCCESS); + + test_truth(memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_BUFFER_REQUESTS, 0) == MEMCACHED_SUCCESS); + /* unbuffered noreply should be success */ + test_truth(memcached_delete(memc, "foo", 3, 0) == MEMCACHED_SUCCESS); + /* unbuffered with reply should be not found... */ + test_truth(memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_NOREPLY, 0) == MEMCACHED_SUCCESS); + test_truth(memcached_delete(memc, "foo", 3, 0) == MEMCACHED_NOTFOUND); + } + + memcached_free(memc_clone); + return TEST_SUCCESS; +} + + +/* Test memcached_server_get_last_disconnect + * For a working server set, shall be NULL + * For a set of non existing server, shall not be NULL + */ +static test_return_t test_get_last_disconnect(memcached_st *memc) +{ + memcached_return_t rc; + memcached_server_st *disconnected_server; + + /* With the working set of server */ + const char *key= "marmotte"; + const char *value= "milka"; + + rc= memcached_set(memc, key, strlen(key), + value, strlen(value), + (time_t)0, (uint32_t)0); + test_truth(rc == MEMCACHED_SUCCESS || rc == MEMCACHED_BUFFERED); + + disconnected_server = memcached_server_get_last_disconnect(memc); + test_truth(disconnected_server == NULL); + + /* With a non existing server */ + memcached_st *mine; + memcached_server_st *servers; + + const char *server_list= "localhost:9"; + + servers= memcached_servers_parse(server_list); + test_truth(servers); + mine= memcached_create(NULL); + rc= memcached_server_push(mine, servers); + test_truth(rc == MEMCACHED_SUCCESS); + memcached_server_list_free(servers); + test_truth(mine); + + rc= memcached_set(mine, key, strlen(key), + value, strlen(value), + (time_t)0, (uint32_t)0); + test_truth(rc != MEMCACHED_SUCCESS); + + disconnected_server = memcached_server_get_last_disconnect(mine); + test_truth(disconnected_server != NULL); + test_truth(disconnected_server->port == 9); + test_truth(strncmp(disconnected_server->hostname,"localhost",9) == 0); + + memcached_quit(mine); + memcached_free(mine); + + return TEST_SUCCESS; +} + +/* + * This test ensures that the failure counter isn't incremented during + * normal termination of the memcached instance. + */ +static test_return_t wrong_failure_counter_test(memcached_st *memc) +{ + memcached_return_t rc; + memcached_server_instance_st *instance; + + /* Set value to force connection to the server */ + const char *key= "marmotte"; + const char *value= "milka"; + + /* + * Please note that I'm abusing the internal structures in libmemcached + * in a non-portable way and you shouldn't be doing this. I'm only + * doing this in order to verify that the library works the way it should + */ + uint32_t number_of_hosts= memcached_server_count(memc); + memc->number_of_hosts= 1; + + /* Ensure that we are connected to the server by setting a value */ + rc= memcached_set(memc, key, strlen(key), + value, strlen(value), + (time_t)0, (uint32_t)0); + test_truth(rc == MEMCACHED_SUCCESS || rc == MEMCACHED_BUFFERED); + + + instance= memcached_server_instance_fetch(memc, 0); + /* The test is to see that the memcached_quit doesn't increase the + * the server failure conter, so let's ensure that it is zero + * before sending quit + */ + instance->server_failure_counter= 0; + + memcached_quit(memc); + + /* Verify that it memcached_quit didn't increment the failure counter + * Please note that this isn't bullet proof, because an error could + * occur... + */ + test_truth(instance->server_failure_counter == 0); + + /* restore the instance */ + memc->number_of_hosts= number_of_hosts; + + return TEST_SUCCESS; +} + + + + +/* + * Test that ensures mget_execute does not end into recursive calls that finally fails + */ +static test_return_t regression_bug_490486(memcached_st *memc) +{ + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, 1); + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_NO_BLOCK, 1); + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_POLL_TIMEOUT, 1000); + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_SERVER_FAILURE_LIMIT, 1); + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_RETRY_TIMEOUT, 3600); + + /* + * I only want to hit _one_ server so I know the number of requests I'm + * sending in the pipeline. + */ + uint32_t number_of_hosts= memc->number_of_hosts; + memc->number_of_hosts= 1; + size_t max_keys= 20480; + + + char **keys= calloc(max_keys, sizeof(char*)); + size_t *key_length=calloc(max_keys, sizeof(size_t)); + + /* First add all of the items.. */ + char blob[1024]= { 0 }; + memcached_return rc; + for (size_t x= 0; x < max_keys; ++x) + { + char k[251]; + key_length[x]= (size_t)snprintf(k, sizeof(k), "0200%zu", x); + keys[x]= strdup(k); + assert(keys[x] != NULL); + rc= memcached_set(memc, keys[x], key_length[x], blob, sizeof(blob), 0, 0); + assert(rc == MEMCACHED_SUCCESS || rc == MEMCACHED_BUFFERED); + } + + /* Try to get all of them with a large multiget */ + size_t counter= 0; + memcached_execute_function callbacks[1]= { [0]= &callback_counter }; + rc= memcached_mget_execute(memc, (const char**)keys, key_length, + (size_t)max_keys, callbacks, &counter, 1); + + assert(rc == MEMCACHED_SUCCESS); + char* the_value= NULL; + char the_key[MEMCACHED_MAX_KEY]; + size_t the_key_length; + size_t the_value_length; + uint32_t the_flags; + + do { + the_value= memcached_fetch(memc, the_key, &the_key_length, &the_value_length, &the_flags, &rc); + + if ((the_value!= NULL) && (rc == MEMCACHED_SUCCESS)) + { + ++counter; + free(the_value); + } + + } while ( (the_value!= NULL) && (rc == MEMCACHED_SUCCESS)); + + + assert(rc == MEMCACHED_END); + + /* Verify that we got all of the items */ + assert(counter == max_keys); + + /* Release all allocated resources */ + for (size_t x= 0; x < max_keys; ++x) + { + free(keys[x]); + } + free(keys); + free(key_length); + + memc->number_of_hosts= number_of_hosts; + + return TEST_SUCCESS; +} + + + + +test_st udp_setup_server_tests[] ={ + {"set_udp_behavior_test", 0, (test_callback_fn)set_udp_behavior_test}, + {"add_tcp_server_udp_client_test", 0, (test_callback_fn)add_tcp_server_udp_client_test}, + {"add_udp_server_tcp_client_test", 0, (test_callback_fn)add_udp_server_tcp_client_test}, + {0, 0, 0} +}; + +test_st upd_io_tests[] ={ + {"udp_set_test", 0, (test_callback_fn)udp_set_test}, + {"udp_buffered_set_test", 0, (test_callback_fn)udp_buffered_set_test}, + {"udp_set_too_big_test", 0, (test_callback_fn)udp_set_too_big_test}, + {"udp_delete_test", 0, (test_callback_fn)udp_delete_test}, + {"udp_buffered_delete_test", 0, (test_callback_fn)udp_buffered_delete_test}, + {"udp_verbosity_test", 0, (test_callback_fn)udp_verbosity_test}, + {"udp_quit_test", 0, (test_callback_fn)udp_quit_test}, + {"udp_flush_test", 0, (test_callback_fn)udp_flush_test}, + {"udp_incr_test", 0, (test_callback_fn)udp_incr_test}, + {"udp_decr_test", 0, (test_callback_fn)udp_decr_test}, + {"udp_stat_test", 0, (test_callback_fn)udp_stat_test}, + {"udp_version_test", 0, (test_callback_fn)udp_version_test}, + {"udp_get_test", 0, (test_callback_fn)udp_get_test}, + {"udp_mixed_io_test", 0, (test_callback_fn)udp_mixed_io_test}, + {0, 0, 0} +}; + +/* Clean the server before beginning testing */ +test_st tests[] ={ + {"flush", 0, (test_callback_fn)flush_test }, + {"init", 0, (test_callback_fn)init_test }, + {"allocation", 0, (test_callback_fn)allocation_test }, + {"server_list_null_test", 0, (test_callback_fn)server_list_null_test}, + {"server_unsort", 0, (test_callback_fn)server_unsort_test}, + {"server_sort", 0, (test_callback_fn)server_sort_test}, + {"server_sort2", 0, (test_callback_fn)server_sort2_test}, + {"clone_test", 0, (test_callback_fn)clone_test }, + {"connection_test", 0, (test_callback_fn)connection_test}, + {"callback_test", 0, (test_callback_fn)callback_test}, + {"userdata_test", 0, (test_callback_fn)userdata_test}, + {"error", 0, (test_callback_fn)error_test }, + {"set", 0, (test_callback_fn)set_test }, + {"set2", 0, (test_callback_fn)set_test2 }, + {"set3", 0, (test_callback_fn)set_test3 }, + {"dump", 1, (test_callback_fn)dump_test}, + {"add", 1, (test_callback_fn)add_test }, + {"replace", 1, (test_callback_fn)replace_test }, + {"delete", 1, (test_callback_fn)delete_test }, + {"get", 1, (test_callback_fn)get_test }, + {"get2", 0, (test_callback_fn)get_test2 }, + {"get3", 0, (test_callback_fn)get_test3 }, + {"get4", 0, (test_callback_fn)get_test4 }, + {"partial mget", 0, (test_callback_fn)get_test5 }, + {"stats_servername", 0, (test_callback_fn)stats_servername_test }, + {"increment", 0, (test_callback_fn)increment_test }, + {"increment_with_initial", 1, (test_callback_fn)increment_with_initial_test }, + {"decrement", 0, (test_callback_fn)decrement_test }, + {"decrement_with_initial", 1, (test_callback_fn)decrement_with_initial_test }, + {"increment_by_key", 0, (test_callback_fn)increment_by_key_test }, + {"increment_with_initial_by_key", 1, (test_callback_fn)increment_with_initial_by_key_test }, + {"decrement_by_key", 0, (test_callback_fn)decrement_by_key_test }, + {"decrement_with_initial_by_key", 1, (test_callback_fn)decrement_with_initial_by_key_test }, + {"quit", 0, (test_callback_fn)quit_test }, + {"mget", 1, (test_callback_fn)mget_test }, + {"mget_result", 1, (test_callback_fn)mget_result_test }, + {"mget_result_alloc", 1, (test_callback_fn)mget_result_alloc_test }, + {"mget_result_function", 1, (test_callback_fn)mget_result_function }, + {"mget_execute", 1, (test_callback_fn)mget_execute }, + {"mget_end", 0, (test_callback_fn)mget_end }, + {"get_stats", 0, (test_callback_fn)get_stats }, + {"add_host_test", 0, (test_callback_fn)add_host_test }, + {"add_host_test_1", 0, (test_callback_fn)add_host_test1 }, + {"get_stats_keys", 0, (test_callback_fn)get_stats_keys }, + {"version_string_test", 0, (test_callback_fn)version_string_test}, + {"bad_key", 1, (test_callback_fn)bad_key_test }, + {"memcached_server_cursor", 1, (test_callback_fn)memcached_server_cursor_test }, + {"read_through", 1, (test_callback_fn)read_through }, + {"delete_through", 1, (test_callback_fn)delete_through }, + {"noreply", 1, (test_callback_fn)noreply_test}, + {"analyzer", 1, (test_callback_fn)analyzer_test}, +#ifdef HAVE_LIBMEMCACHEDUTIL + {"connectionpool", 1, (test_callback_fn)connection_pool_test }, +#endif + {"test_get_last_disconnect", 1, (test_callback_fn)test_get_last_disconnect}, + {0, 0, 0} +}; + +test_st behavior_tests[] ={ + {"behavior_test", 0, (test_callback_fn)behavior_test}, ++ {"MEMCACHED_BEHAVIOR_CORK", 0, (test_callback_fn)MEMCACHED_BEHAVIOR_CORK_test}, + {0, 0, 0} +}; + +test_st async_tests[] ={ + {"add", 1, (test_callback_fn)add_wrapper }, + {0, 0, 0} +}; + +test_st string_tests[] ={ + {"string static with null", 0, (test_callback_fn)string_static_null }, + {"string alloc with null", 0, (test_callback_fn)string_alloc_null }, + {"string alloc with 1K", 0, (test_callback_fn)string_alloc_with_size }, + {"string alloc with malloc failure", 0, (test_callback_fn)string_alloc_with_size_toobig }, + {"string append", 0, (test_callback_fn)string_alloc_append }, + {"string append failure (too big)", 0, (test_callback_fn)string_alloc_append_toobig }, + {0, 0, (test_callback_fn)0} +}; + +test_st result_tests[] ={ + {"result static", 0, (test_callback_fn)result_static}, + {"result alloc", 0, (test_callback_fn)result_alloc}, + {0, 0, (test_callback_fn)0} +}; + +test_st version_1_2_3[] ={ + {"append", 0, (test_callback_fn)append_test }, + {"prepend", 0, (test_callback_fn)prepend_test }, + {"cas", 0, (test_callback_fn)cas_test }, + {"cas2", 0, (test_callback_fn)cas2_test }, + {"append_binary", 0, (test_callback_fn)append_binary_test }, + {0, 0, (test_callback_fn)0} +}; + +test_st user_tests[] ={ + {"user_supplied_bug1", 0, (test_callback_fn)user_supplied_bug1 }, + {"user_supplied_bug2", 0, (test_callback_fn)user_supplied_bug2 }, + {"user_supplied_bug3", 0, (test_callback_fn)user_supplied_bug3 }, + {"user_supplied_bug4", 0, (test_callback_fn)user_supplied_bug4 }, + {"user_supplied_bug5", 1, (test_callback_fn)user_supplied_bug5 }, + {"user_supplied_bug6", 1, (test_callback_fn)user_supplied_bug6 }, + {"user_supplied_bug7", 1, (test_callback_fn)user_supplied_bug7 }, + {"user_supplied_bug8", 1, (test_callback_fn)user_supplied_bug8 }, + {"user_supplied_bug9", 1, (test_callback_fn)user_supplied_bug9 }, + {"user_supplied_bug10", 1, (test_callback_fn)user_supplied_bug10 }, + {"user_supplied_bug11", 1, (test_callback_fn)user_supplied_bug11 }, + {"user_supplied_bug12", 1, (test_callback_fn)user_supplied_bug12 }, + {"user_supplied_bug13", 1, (test_callback_fn)user_supplied_bug13 }, + {"user_supplied_bug14", 1, (test_callback_fn)user_supplied_bug14 }, + {"user_supplied_bug15", 1, (test_callback_fn)user_supplied_bug15 }, + {"user_supplied_bug16", 1, (test_callback_fn)user_supplied_bug16 }, +#ifndef __sun + /* + ** It seems to be something weird with the character sets.. + ** value_fetch is unable to parse the value line (iscntrl "fails"), so I + ** guess I need to find out how this is supposed to work.. Perhaps I need + ** to run the test in a specific locale (I tried zh_CN.UTF-8 without success, + ** so just disable the code for now...). + */ + {"user_supplied_bug17", 1, (test_callback_fn)user_supplied_bug17 }, +#endif + {"user_supplied_bug18", 1, (test_callback_fn)user_supplied_bug18 }, + {"user_supplied_bug19", 1, (test_callback_fn)user_supplied_bug19 }, + {"user_supplied_bug20", 1, (test_callback_fn)user_supplied_bug20 }, + {"user_supplied_bug21", 1, (test_callback_fn)user_supplied_bug21 }, + {"wrong_failure_counter_test", 1, (test_callback_fn)wrong_failure_counter_test}, + {0, 0, (test_callback_fn)0} +}; + +test_st replication_tests[]= { + {"set", 1, (test_callback_fn)replication_set_test }, + {"get", 0, (test_callback_fn)replication_get_test }, + {"mget", 0, (test_callback_fn)replication_mget_test }, + {"delete", 0, (test_callback_fn)replication_delete_test }, + {"rand_mget", 0, (test_callback_fn)replication_randomize_mget_test }, + {0, 0, (test_callback_fn)0} +}; + +/* + * The following test suite is used to verify that we don't introduce + * regression bugs. If you want more information about the bug / test, + * you should look in the bug report at + * http://bugs.launchpad.net/libmemcached + */ +test_st regression_tests[]= { + {"lp:434484", 1, (test_callback_fn)regression_bug_434484 }, + {"lp:434843", 1, (test_callback_fn)regression_bug_434843 }, + {"lp:434843 buffered", 1, (test_callback_fn)regression_bug_434843_buffered }, + {"lp:421108", 1, (test_callback_fn)regression_bug_421108 }, + {"lp:442914", 1, (test_callback_fn)regression_bug_442914 }, + {"lp:447342", 1, (test_callback_fn)regression_bug_447342 }, + {"lp:463297", 1, (test_callback_fn)regression_bug_463297 }, + {"lp:490486", 1, (test_callback_fn)regression_bug_490486 }, + {0, 0, (test_callback_fn)0} +}; + +test_st ketama_compatibility[]= { + {"libmemcached", 1, (test_callback_fn)ketama_compatibility_libmemcached }, + {"spymemcached", 1, (test_callback_fn)ketama_compatibility_spymemcached }, + {0, 0, (test_callback_fn)0} +}; + +test_st generate_tests[] ={ + {"generate_pairs", 1, (test_callback_fn)generate_pairs }, + {"generate_data", 1, (test_callback_fn)generate_data }, + {"get_read", 0, (test_callback_fn)get_read }, + {"delete_generate", 0, (test_callback_fn)delete_generate }, + {"generate_buffer_data", 1, (test_callback_fn)generate_buffer_data }, + {"delete_buffer", 0, (test_callback_fn)delete_buffer_generate}, + {"generate_data", 1, (test_callback_fn)generate_data }, + {"mget_read", 0, (test_callback_fn)mget_read }, + {"mget_read_result", 0, (test_callback_fn)mget_read_result }, + {"mget_read_function", 0, (test_callback_fn)mget_read_function }, + {"cleanup", 1, (test_callback_fn)cleanup_pairs }, + {"generate_large_pairs", 1, (test_callback_fn)generate_large_pairs }, + {"generate_data", 1, (test_callback_fn)generate_data }, + {"generate_buffer_data", 1, (test_callback_fn)generate_buffer_data }, + {"cleanup", 1, (test_callback_fn)cleanup_pairs }, + {0, 0, (test_callback_fn)0} +}; + +test_st consistent_tests[] ={ + {"generate_pairs", 1, (test_callback_fn)generate_pairs }, + {"generate_data", 1, (test_callback_fn)generate_data }, + {"get_read", 0, (test_callback_fn)get_read_count }, + {"cleanup", 1, (test_callback_fn)cleanup_pairs }, + {0, 0, (test_callback_fn)0} +}; + +test_st consistent_weighted_tests[] ={ + {"generate_pairs", 1, (test_callback_fn)generate_pairs }, + {"generate_data", 1, (test_callback_fn)generate_data_with_stats }, + {"get_read", 0, (test_callback_fn)get_read_count }, + {"cleanup", 1, (test_callback_fn)cleanup_pairs }, + {0, 0, (test_callback_fn)0} +}; + +test_st hsieh_availability[] ={ + {"hsieh_avaibility_test", 0, (test_callback_fn)hsieh_avaibility_test}, + {0, 0, (test_callback_fn)0} +}; + +#if 0 +test_st hash_sanity[] ={ + {"hash sanity", 0, (test_callback_fn)hash_sanity_test}, + {0, 0, (test_callback_fn)0} +}; +#endif + +test_st ketama_auto_eject_hosts[] ={ + {"auto_eject_hosts", 1, (test_callback_fn)auto_eject_hosts }, + {"output_ketama_weighted_keys", 1, (test_callback_fn)output_ketama_weighted_keys }, + {0, 0, (test_callback_fn)0} +}; + +test_st hash_tests[] ={ + {"md5", 0, (test_callback_fn)md5_run }, + {"crc", 0, (test_callback_fn)crc_run }, + {"fnv1_64", 0, (test_callback_fn)fnv1_64_run }, + {"fnv1a_64", 0, (test_callback_fn)fnv1a_64_run }, + {"fnv1_32", 0, (test_callback_fn)fnv1_32_run }, + {"fnv1a_32", 0, (test_callback_fn)fnv1a_32_run }, + {"hsieh", 0, (test_callback_fn)hsieh_run }, + {"murmur", 0, (test_callback_fn)murmur_run }, + {"jenkis", 0, (test_callback_fn)jenkins_run }, + {0, 0, (test_callback_fn)0} +}; + +collection_st collection[] ={ +#if 0 + {"hash_sanity", 0, 0, hash_sanity}, +#endif + {"hsieh_availability", 0, 0, hsieh_availability}, + {"udp_setup", (test_callback_fn)init_udp, 0, udp_setup_server_tests}, + {"udp_io", (test_callback_fn)init_udp, 0, upd_io_tests}, + {"udp_binary_io", (test_callback_fn)binary_init_udp, 0, upd_io_tests}, + {"block", 0, 0, tests}, + {"binary", (test_callback_fn)pre_binary, 0, tests}, + {"nonblock", (test_callback_fn)pre_nonblock, 0, tests}, + {"nodelay", (test_callback_fn)pre_nodelay, 0, tests}, + {"settimer", (test_callback_fn)pre_settimer, 0, tests}, + {"md5", (test_callback_fn)pre_md5, 0, tests}, + {"crc", (test_callback_fn)pre_crc, 0, tests}, + {"hsieh", (test_callback_fn)pre_hsieh, 0, tests}, + {"jenkins", (test_callback_fn)pre_jenkins, 0, tests}, + {"fnv1_64", (test_callback_fn)pre_hash_fnv1_64, 0, tests}, + {"fnv1a_64", (test_callback_fn)pre_hash_fnv1a_64, 0, tests}, + {"fnv1_32", (test_callback_fn)pre_hash_fnv1_32, 0, tests}, + {"fnv1a_32", (test_callback_fn)pre_hash_fnv1a_32, 0, tests}, + {"ketama", (test_callback_fn)pre_behavior_ketama, 0, tests}, + {"ketama_auto_eject_hosts", (test_callback_fn)pre_behavior_ketama, 0, ketama_auto_eject_hosts}, + {"unix_socket", (test_callback_fn)pre_unix_socket, 0, tests}, + {"unix_socket_nodelay", (test_callback_fn)pre_nodelay, 0, tests}, + {"poll_timeout", (test_callback_fn)poll_timeout, 0, tests}, + {"gets", (test_callback_fn)enable_cas, 0, tests}, + {"consistent_crc", (test_callback_fn)enable_consistent_crc, 0, tests}, + {"consistent_hsieh", (test_callback_fn)enable_consistent_hsieh, 0, tests}, +#ifdef MEMCACHED_ENABLE_DEPRECATED + {"deprecated_memory_allocators", (test_callback_fn)deprecated_set_memory_alloc, 0, tests}, +#endif + {"memory_allocators", (test_callback_fn)set_memory_alloc, 0, tests}, + {"prefix", (test_callback_fn)set_prefix, 0, tests}, + {"version_1_2_3", (test_callback_fn)check_for_1_2_3, 0, version_1_2_3}, + {"string", 0, 0, string_tests}, + {"result", 0, 0, result_tests}, + {"async", (test_callback_fn)pre_nonblock, 0, async_tests}, + {"async_binary", (test_callback_fn)pre_nonblock_binary, 0, async_tests}, + {"user", 0, 0, user_tests}, + {"generate", 0, 0, generate_tests}, + {"generate_hsieh", (test_callback_fn)pre_hsieh, 0, generate_tests}, + {"generate_ketama", (test_callback_fn)pre_behavior_ketama, 0, generate_tests}, + {"generate_hsieh_consistent", (test_callback_fn)enable_consistent_hsieh, 0, generate_tests}, + {"generate_md5", (test_callback_fn)pre_md5, 0, generate_tests}, + {"generate_murmur", (test_callback_fn)pre_murmur, 0, generate_tests}, + {"generate_jenkins", (test_callback_fn)pre_jenkins, 0, generate_tests}, + {"generate_nonblock", (test_callback_fn)pre_nonblock, 0, generate_tests}, ++ {"generate_corked", (test_callback_fn)pre_cork, 0, generate_tests}, ++ {"generate_corked_and_nonblock", (test_callback_fn)pre_cork_and_nonblock, 0, generate_tests}, + {"consistent_not", 0, 0, consistent_tests}, + {"consistent_ketama", (test_callback_fn)pre_behavior_ketama, 0, consistent_tests}, + {"consistent_ketama_weighted", (test_callback_fn)pre_behavior_ketama_weighted, 0, consistent_weighted_tests}, + {"ketama_compat", 0, 0, ketama_compatibility}, + {"test_hashes", 0, 0, hash_tests}, + {"replication", (test_callback_fn)pre_replication, 0, replication_tests}, + {"replication_noblock", (test_callback_fn)pre_replication_noblock, 0, replication_tests}, + {"regression", 0, 0, regression_tests}, + {"behaviors", 0, 0, behavior_tests}, + {0, 0, 0, 0} +}; + +#define SERVERS_TO_CREATE 5 + +#include "libmemcached_world.h" + +void get_world(world_st *world) +{ + world->collections= collection; + + world->create= (test_callback_create_fn)world_create; + world->destroy= (test_callback_fn)world_destroy; + + world->test.startup= (test_callback_fn)world_test_startup; + world->test.flush= (test_callback_fn)world_flush; + world->test.pre_run= (test_callback_fn)world_pre_run; + world->test.post_run= (test_callback_fn)world_post_run; + world->test.on_error= (test_callback_error_fn)world_on_error; + + world->collection.startup= (test_callback_fn)world_container_startup; + world->collection.shutdown= (test_callback_fn)world_container_shutdown; + + world->runner= &defualt_libmemcached_runner; +}