Unverified Commit e7a3d3d1 authored by Yossi Gottlieb's avatar Yossi Gottlieb Committed by GitHub
Browse files

Merge pull request #12254 from yossigo/hiredis-refresh

Refresh deps/hiredis to latest (unreleased) version.
parents 0bd1a3a4 032bb2a2
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "hiredis.h" #include "hiredis.h"
#include "async.h" #include "async.h"
#include "adapters/poll.h"
#ifdef HIREDIS_TEST_SSL #ifdef HIREDIS_TEST_SSL
#include "hiredis_ssl.h" #include "hiredis_ssl.h"
#endif #endif
...@@ -34,11 +35,11 @@ enum connection_type { ...@@ -34,11 +35,11 @@ enum connection_type {
struct config { struct config {
enum connection_type type; enum connection_type type;
struct timeval connect_timeout;
struct { struct {
const char *host; const char *host;
int port; int port;
struct timeval timeout;
} tcp; } tcp;
struct { struct {
...@@ -75,6 +76,15 @@ static int tests = 0, fails = 0, skips = 0; ...@@ -75,6 +76,15 @@ static int tests = 0, fails = 0, skips = 0;
#define test_cond(_c) if(_c) printf("\033[0;32mPASSED\033[0;0m\n"); else {printf("\033[0;31mFAILED\033[0;0m\n"); fails++;} #define test_cond(_c) if(_c) printf("\033[0;32mPASSED\033[0;0m\n"); else {printf("\033[0;31mFAILED\033[0;0m\n"); fails++;}
#define test_skipped() { printf("\033[01;33mSKIPPED\033[0;0m\n"); skips++; } #define test_skipped() { printf("\033[01;33mSKIPPED\033[0;0m\n"); skips++; }
static void millisleep(int ms)
{
#if _MSC_VER
Sleep(ms);
#else
usleep(ms*1000);
#endif
}
static long long usec(void) { static long long usec(void) {
#ifndef _MSC_VER #ifndef _MSC_VER
struct timeval tv; struct timeval tv;
...@@ -329,10 +339,14 @@ static void test_format_commands(void) { ...@@ -329,10 +339,14 @@ static void test_format_commands(void) {
FLOAT_WIDTH_TEST(float); FLOAT_WIDTH_TEST(float);
FLOAT_WIDTH_TEST(double); FLOAT_WIDTH_TEST(double);
test("Format command with invalid printf format: "); test("Format command with unhandled printf format (specifier 'p' not supported): ");
len = redisFormatCommand(&cmd,"key:%08p %b",(void*)1234,"foo",(size_t)3); len = redisFormatCommand(&cmd,"key:%08p %b",(void*)1234,"foo",(size_t)3);
test_cond(len == -1); test_cond(len == -1);
test("Format command with invalid printf format (specifier missing): ");
len = redisFormatCommand(&cmd,"%-");
test_cond(len == -1);
const char *argv[3]; const char *argv[3];
argv[0] = "SET"; argv[0] = "SET";
argv[1] = "foo\0xxx"; argv[1] = "foo\0xxx";
...@@ -391,6 +405,16 @@ static void test_append_formatted_commands(struct config config) { ...@@ -391,6 +405,16 @@ static void test_append_formatted_commands(struct config config) {
disconnect(c, 0); disconnect(c, 0);
} }
static void test_tcp_options(struct config cfg) {
redisContext *c;
c = do_connect(cfg);
test("We can enable TCP_KEEPALIVE: ");
test_cond(redisEnableKeepAlive(c) == REDIS_OK);
disconnect(c, 0);
}
static void test_reply_reader(void) { static void test_reply_reader(void) {
redisReader *reader; redisReader *reader;
void *reply, *root; void *reply, *root;
...@@ -568,6 +592,19 @@ static void test_reply_reader(void) { ...@@ -568,6 +592,19 @@ static void test_reply_reader(void) {
test_cond(ret == REDIS_ERR && reply == NULL); test_cond(ret == REDIS_ERR && reply == NULL);
redisReaderFree(reader); redisReaderFree(reader);
test("Don't reset state after protocol error(not segfault): ");
reader = redisReaderCreate();
redisReaderFeed(reader,(char*)"*3\r\n$3\r\nSET\r\n$5\r\nhello\r\n$", 25);
ret = redisReaderGetReply(reader,&reply);
assert(ret == REDIS_OK);
redisReaderFeed(reader,(char*)"3\r\nval\r\n", 8);
ret = redisReaderGetReply(reader,&reply);
test_cond(ret == REDIS_OK &&
((redisReply*)reply)->type == REDIS_REPLY_ARRAY &&
((redisReply*)reply)->elements == 3);
freeReplyObject(reply);
redisReaderFree(reader);
/* Regression test for issue #45 on GitHub. */ /* Regression test for issue #45 on GitHub. */
test("Don't do empty allocation for empty multi bulk: "); test("Don't do empty allocation for empty multi bulk: ");
reader = redisReaderCreate(); reader = redisReaderCreate();
...@@ -637,12 +674,23 @@ static void test_reply_reader(void) { ...@@ -637,12 +674,23 @@ static void test_reply_reader(void) {
freeReplyObject(reply); freeReplyObject(reply);
redisReaderFree(reader); redisReaderFree(reader);
test("Set error when RESP3 double is NaN: "); test("Correctly parses RESP3 double NaN: ");
reader = redisReaderCreate(); reader = redisReaderCreate();
redisReaderFeed(reader, ",nan\r\n",6); redisReaderFeed(reader, ",nan\r\n",6);
ret = redisReaderGetReply(reader,&reply); ret = redisReaderGetReply(reader,&reply);
test_cond(ret == REDIS_ERR && test_cond(ret == REDIS_OK &&
strcasecmp(reader->errstr,"Bad double value") == 0); ((redisReply*)reply)->type == REDIS_REPLY_DOUBLE &&
isnan(((redisReply*)reply)->dval));
freeReplyObject(reply);
redisReaderFree(reader);
test("Correctly parses RESP3 double -Nan: ");
reader = redisReaderCreate();
redisReaderFeed(reader, ",-nan\r\n", 7);
ret = redisReaderGetReply(reader, &reply);
test_cond(ret == REDIS_OK &&
((redisReply*)reply)->type == REDIS_REPLY_DOUBLE &&
isnan(((redisReply*)reply)->dval));
freeReplyObject(reply); freeReplyObject(reply);
redisReaderFree(reader); redisReaderFree(reader);
...@@ -745,6 +793,20 @@ static void test_reply_reader(void) { ...@@ -745,6 +793,20 @@ static void test_reply_reader(void) {
!strcmp(((redisReply*)reply)->str,"3492890328409238509324850943850943825024385")); !strcmp(((redisReply*)reply)->str,"3492890328409238509324850943850943825024385"));
freeReplyObject(reply); freeReplyObject(reply);
redisReaderFree(reader); redisReaderFree(reader);
test("Can parse RESP3 doubles in an array: ");
reader = redisReaderCreate();
redisReaderFeed(reader, "*1\r\n,3.14159265358979323846\r\n",31);
ret = redisReaderGetReply(reader,&reply);
test_cond(ret == REDIS_OK &&
((redisReply*)reply)->type == REDIS_REPLY_ARRAY &&
((redisReply*)reply)->elements == 1 &&
((redisReply*)reply)->element[0]->type == REDIS_REPLY_DOUBLE &&
fabs(((redisReply*)reply)->element[0]->dval - 3.14159265358979323846) < 0.00000001 &&
((redisReply*)reply)->element[0]->len == 22 &&
strcmp(((redisReply*)reply)->element[0]->str, "3.14159265358979323846") == 0);
freeReplyObject(reply);
redisReaderFree(reader);
} }
static void test_free_null(void) { static void test_free_null(void) {
...@@ -819,9 +881,9 @@ static void test_allocator_injection(void) { ...@@ -819,9 +881,9 @@ static void test_allocator_injection(void) {
#define HIREDIS_BAD_DOMAIN "idontexist-noreally.com" #define HIREDIS_BAD_DOMAIN "idontexist-noreally.com"
static void test_blocking_connection_errors(void) { static void test_blocking_connection_errors(void) {
redisContext *c;
struct addrinfo hints = {.ai_family = AF_INET}; struct addrinfo hints = {.ai_family = AF_INET};
struct addrinfo *ai_tmp = NULL; struct addrinfo *ai_tmp = NULL;
redisContext *c;
int rv = getaddrinfo(HIREDIS_BAD_DOMAIN, "6379", &hints, &ai_tmp); int rv = getaddrinfo(HIREDIS_BAD_DOMAIN, "6379", &hints, &ai_tmp);
if (rv != 0) { if (rv != 0) {
...@@ -835,6 +897,7 @@ static void test_blocking_connection_errors(void) { ...@@ -835,6 +897,7 @@ static void test_blocking_connection_errors(void) {
strcmp(c->errstr, "Can't resolve: " HIREDIS_BAD_DOMAIN) == 0 || strcmp(c->errstr, "Can't resolve: " HIREDIS_BAD_DOMAIN) == 0 ||
strcmp(c->errstr, "Name does not resolve") == 0 || strcmp(c->errstr, "Name does not resolve") == 0 ||
strcmp(c->errstr, "nodename nor servname provided, or not known") == 0 || strcmp(c->errstr, "nodename nor servname provided, or not known") == 0 ||
strcmp(c->errstr, "node name or service name not known") == 0 ||
strcmp(c->errstr, "No address associated with hostname") == 0 || strcmp(c->errstr, "No address associated with hostname") == 0 ||
strcmp(c->errstr, "Temporary failure in name resolution") == 0 || strcmp(c->errstr, "Temporary failure in name resolution") == 0 ||
strcmp(c->errstr, "hostname nor servname provided, or not known") == 0 || strcmp(c->errstr, "hostname nor servname provided, or not known") == 0 ||
...@@ -847,12 +910,26 @@ static void test_blocking_connection_errors(void) { ...@@ -847,12 +910,26 @@ static void test_blocking_connection_errors(void) {
} }
#ifndef _WIN32 #ifndef _WIN32
redisOptions opt = {0};
struct timeval tv;
test("Returns error when the port is not open: "); test("Returns error when the port is not open: ");
c = redisConnect((char*)"localhost", 1); c = redisConnect((char*)"localhost", 1);
test_cond(c->err == REDIS_ERR_IO && test_cond(c->err == REDIS_ERR_IO &&
strcmp(c->errstr,"Connection refused") == 0); strcmp(c->errstr,"Connection refused") == 0);
redisFree(c); redisFree(c);
/* Verify we don't regress from the fix in PR #1180 */
test("We don't clobber connection exception with setsockopt error: ");
tv = (struct timeval){.tv_sec = 0, .tv_usec = 500000};
opt.command_timeout = opt.connect_timeout = &tv;
REDIS_OPTIONS_SET_TCP(&opt, "localhost", 10337);
c = redisConnectWithOptions(&opt);
test_cond(c->err == REDIS_ERR_IO &&
strcmp(c->errstr, "Connection refused") == 0);
redisFree(c);
test("Returns error when the unix_sock socket path doesn't accept connections: "); test("Returns error when the unix_sock socket path doesn't accept connections: ");
c = redisConnectUnix((char*)"/tmp/idontexist.sock"); c = redisConnectUnix((char*)"/tmp/idontexist.sock");
test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */ test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */
...@@ -914,11 +991,19 @@ static void test_resp3_push_handler(redisContext *c) { ...@@ -914,11 +991,19 @@ static void test_resp3_push_handler(redisContext *c) {
old = redisSetPushCallback(c, push_handler); old = redisSetPushCallback(c, push_handler);
test("We can set a custom RESP3 PUSH handler: "); test("We can set a custom RESP3 PUSH handler: ");
reply = redisCommand(c, "SET key:0 val:0"); reply = redisCommand(c, "SET key:0 val:0");
/* We need another command because depending on the version of Redis, the
* notification may be delivered after the command's reply. */
assert(reply != NULL);
freeReplyObject(reply);
reply = redisCommand(c, "PING");
test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && pc.str == 1); test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && pc.str == 1);
freeReplyObject(reply); freeReplyObject(reply);
test("We properly handle a NIL invalidation payload: "); test("We properly handle a NIL invalidation payload: ");
reply = redisCommand(c, "FLUSHDB"); reply = redisCommand(c, "FLUSHDB");
assert(reply != NULL);
freeReplyObject(reply);
reply = redisCommand(c, "PING");
test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && pc.nil == 1); test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && pc.nil == 1);
freeReplyObject(reply); freeReplyObject(reply);
...@@ -929,6 +1014,12 @@ static void test_resp3_push_handler(redisContext *c) { ...@@ -929,6 +1014,12 @@ static void test_resp3_push_handler(redisContext *c) {
assert((reply = redisCommand(c, "GET key:0")) != NULL); assert((reply = redisCommand(c, "GET key:0")) != NULL);
freeReplyObject(reply); freeReplyObject(reply);
assert((reply = redisCommand(c, "SET key:0 invalid")) != NULL); assert((reply = redisCommand(c, "SET key:0 invalid")) != NULL);
/* Depending on Redis version, we may receive either push notification or
* status reply. Both cases are valid. */
if (reply->type == REDIS_REPLY_STATUS) {
freeReplyObject(reply);
reply = redisCommand(c, "PING");
}
test_cond(reply->type == REDIS_REPLY_PUSH); test_cond(reply->type == REDIS_REPLY_PUSH);
freeReplyObject(reply); freeReplyObject(reply);
...@@ -1089,6 +1180,13 @@ static void test_blocking_connection(struct config config) { ...@@ -1089,6 +1180,13 @@ static void test_blocking_connection(struct config config) {
strcasecmp(reply->element[1]->str,"pong") == 0); strcasecmp(reply->element[1]->str,"pong") == 0);
freeReplyObject(reply); freeReplyObject(reply);
test("Send command by passing argc/argv: ");
const char *argv[3] = {"SET", "foo", "bar"};
size_t argvlen[3] = {3, 3, 3};
reply = redisCommandArgv(c,3,argv,argvlen);
test_cond(reply->type == REDIS_REPLY_STATUS);
freeReplyObject(reply);
/* Make sure passing NULL to redisGetReply is safe */ /* Make sure passing NULL to redisGetReply is safe */
test("Can pass NULL to redisGetReply: "); test("Can pass NULL to redisGetReply: ");
assert(redisAppendCommand(c, "PING") == REDIS_OK); assert(redisAppendCommand(c, "PING") == REDIS_OK);
...@@ -1143,7 +1241,13 @@ static void test_blocking_connection_timeouts(struct config config) { ...@@ -1143,7 +1241,13 @@ static void test_blocking_connection_timeouts(struct config config) {
test("Does not return a reply when the command times out: "); test("Does not return a reply when the command times out: ");
if (detect_debug_sleep(c)) { if (detect_debug_sleep(c)) {
redisAppendFormattedCommand(c, sleep_cmd, strlen(sleep_cmd)); redisAppendFormattedCommand(c, sleep_cmd, strlen(sleep_cmd));
// flush connection buffer without waiting for the reply
s = c->funcs->write(c); s = c->funcs->write(c);
assert(s == (ssize_t)hi_sdslen(c->obuf));
hi_sdsfree(c->obuf);
c->obuf = hi_sdsempty();
tv.tv_sec = 0; tv.tv_sec = 0;
tv.tv_usec = 10000; tv.tv_usec = 10000;
redisSetTimeout(c, tv); redisSetTimeout(c, tv);
...@@ -1156,6 +1260,9 @@ static void test_blocking_connection_timeouts(struct config config) { ...@@ -1156,6 +1260,9 @@ static void test_blocking_connection_timeouts(struct config config) {
strcmp(c->errstr, "recv timeout") == 0); strcmp(c->errstr, "recv timeout") == 0);
#endif #endif
freeReplyObject(reply); freeReplyObject(reply);
// wait for the DEBUG SLEEP to complete so that Redis server is unblocked for the following tests
millisleep(3000);
} else { } else {
test_skipped(); test_skipped();
} }
...@@ -1226,22 +1333,34 @@ static void test_blocking_io_errors(struct config config) { ...@@ -1226,22 +1333,34 @@ static void test_blocking_io_errors(struct config config) {
static void test_invalid_timeout_errors(struct config config) { static void test_invalid_timeout_errors(struct config config) {
redisContext *c; redisContext *c;
test("Set error when an invalid timeout usec value is given to redisConnectWithTimeout: "); test("Set error when an invalid timeout usec value is used during connect: ");
config.tcp.timeout.tv_sec = 0; config.connect_timeout.tv_sec = 0;
config.tcp.timeout.tv_usec = 10000001; config.connect_timeout.tv_usec = 10000001;
c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout); if (config.type == CONN_TCP || config.type == CONN_SSL) {
c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.connect_timeout);
} else if(config.type == CONN_UNIX) {
c = redisConnectUnixWithTimeout(config.unix_sock.path, config.connect_timeout);
} else {
assert(NULL);
}
test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0); test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0);
redisFree(c); redisFree(c);
test("Set error when an invalid timeout sec value is given to redisConnectWithTimeout: "); test("Set error when an invalid timeout sec value is used during connect: ");
config.tcp.timeout.tv_sec = (((LONG_MAX) - 999) / 1000) + 1; config.connect_timeout.tv_sec = (((LONG_MAX) - 999) / 1000) + 1;
config.tcp.timeout.tv_usec = 0; config.connect_timeout.tv_usec = 0;
c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout); if (config.type == CONN_TCP || config.type == CONN_SSL) {
c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.connect_timeout);
} else if(config.type == CONN_UNIX) {
c = redisConnectUnixWithTimeout(config.unix_sock.path, config.connect_timeout);
} else {
assert(NULL);
}
test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0); test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0);
redisFree(c); redisFree(c);
...@@ -1729,10 +1848,14 @@ void subscribe_channel_a_cb(redisAsyncContext *ac, void *r, void *privdata) { ...@@ -1729,10 +1848,14 @@ void subscribe_channel_a_cb(redisAsyncContext *ac, void *r, void *privdata) {
strcmp(reply->element[2]->str,"Hello!") == 0); strcmp(reply->element[2]->str,"Hello!") == 0);
state->checkpoint++; state->checkpoint++;
/* Unsubscribe to channels, including a channel X which we don't subscribe to */ /* Unsubscribe to channels, including channel X & Z which we don't subscribe to */
redisAsyncCommand(ac,unexpected_cb, redisAsyncCommand(ac,unexpected_cb,
(void*)"unsubscribe should not call unexpected_cb()", (void*)"unsubscribe should not call unexpected_cb()",
"unsubscribe B X A"); "unsubscribe B X A A Z");
/* Unsubscribe to patterns, none which we subscribe to */
redisAsyncCommand(ac,unexpected_cb,
(void*)"punsubscribe should not call unexpected_cb()",
"punsubscribe");
/* Send a regular command after unsubscribing, then disconnect */ /* Send a regular command after unsubscribing, then disconnect */
state->disconnect = 1; state->disconnect = 1;
redisAsyncCommand(ac,integer_cb,state,"LPUSH mylist foo"); redisAsyncCommand(ac,integer_cb,state,"LPUSH mylist foo");
...@@ -1749,6 +1872,7 @@ void subscribe_channel_a_cb(redisAsyncContext *ac, void *r, void *privdata) { ...@@ -1749,6 +1872,7 @@ void subscribe_channel_a_cb(redisAsyncContext *ac, void *r, void *privdata) {
void subscribe_channel_b_cb(redisAsyncContext *ac, void *r, void *privdata) { void subscribe_channel_b_cb(redisAsyncContext *ac, void *r, void *privdata) {
redisReply *reply = r; redisReply *reply = r;
TestState *state = privdata; TestState *state = privdata;
(void)ac;
assert(reply != NULL && reply->type == REDIS_REPLY_ARRAY && assert(reply != NULL && reply->type == REDIS_REPLY_ARRAY &&
reply->elements == 3); reply->elements == 3);
...@@ -1767,8 +1891,10 @@ void subscribe_channel_b_cb(redisAsyncContext *ac, void *r, void *privdata) { ...@@ -1767,8 +1891,10 @@ void subscribe_channel_b_cb(redisAsyncContext *ac, void *r, void *privdata) {
/* Test handling of multiple channels /* Test handling of multiple channels
* - subscribe to channel A and B * - subscribe to channel A and B
* - a published message on A triggers an unsubscribe of channel B, X and A * - a published message on A triggers an unsubscribe of channel B, X, A and Z
* where channel X is not subscribed to. * where channel X and Z are not subscribed to.
* - the published message also triggers an unsubscribe to patterns. Since no
* pattern is subscribed to the responded pattern element type is NIL.
* - a command sent after unsubscribe triggers a disconnect */ * - a command sent after unsubscribe triggers a disconnect */
static void test_pubsub_multiple_channels(struct config config) { static void test_pubsub_multiple_channels(struct config config) {
test("Subscribe to multiple channels: "); test("Subscribe to multiple channels: ");
...@@ -1881,6 +2007,250 @@ static void test_monitor(struct config config) { ...@@ -1881,6 +2007,250 @@ static void test_monitor(struct config config) {
} }
#endif /* HIREDIS_TEST_ASYNC */ #endif /* HIREDIS_TEST_ASYNC */
/* tests for async api using polling adapter, requires no extra libraries*/
/* enum for the test cases, the callbacks have different logic based on them */
typedef enum astest_no
{
ASTEST_CONNECT=0,
ASTEST_CONN_TIMEOUT,
ASTEST_PINGPONG,
ASTEST_PINGPONG_TIMEOUT,
ASTEST_ISSUE_931,
ASTEST_ISSUE_931_PING
}astest_no;
/* a static context for the async tests */
struct _astest {
redisAsyncContext *ac;
astest_no testno;
int counter;
int connects;
int connect_status;
int disconnects;
int pongs;
int disconnect_status;
int connected;
int err;
char errstr[256];
};
static struct _astest astest;
/* async callbacks */
static void asCleanup(void* data)
{
struct _astest *t = (struct _astest *)data;
t->ac = NULL;
}
static void commandCallback(struct redisAsyncContext *ac, void* _reply, void* _privdata);
static void connectCallback(redisAsyncContext *c, int status) {
struct _astest *t = (struct _astest *)c->data;
assert(t == &astest);
assert(t->connects == 0);
t->err = c->err;
strcpy(t->errstr, c->errstr);
t->connects++;
t->connect_status = status;
t->connected = status == REDIS_OK ? 1 : -1;
if (t->testno == ASTEST_ISSUE_931) {
/* disconnect again */
redisAsyncDisconnect(c);
}
else if (t->testno == ASTEST_ISSUE_931_PING)
{
redisAsyncCommand(c, commandCallback, NULL, "PING");
}
}
static void disconnectCallback(const redisAsyncContext *c, int status) {
assert(c->data == (void*)&astest);
assert(astest.disconnects == 0);
astest.err = c->err;
strcpy(astest.errstr, c->errstr);
astest.disconnects++;
astest.disconnect_status = status;
astest.connected = 0;
}
static void commandCallback(struct redisAsyncContext *ac, void* _reply, void* _privdata)
{
redisReply *reply = (redisReply*)_reply;
struct _astest *t = (struct _astest *)ac->data;
assert(t == &astest);
(void)_privdata;
t->err = ac->err;
strcpy(t->errstr, ac->errstr);
t->counter++;
if (t->testno == ASTEST_PINGPONG ||t->testno == ASTEST_ISSUE_931_PING)
{
assert(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
t->pongs++;
redisAsyncFree(ac);
}
if (t->testno == ASTEST_PINGPONG_TIMEOUT)
{
/* two ping pongs */
assert(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
t->pongs++;
if (t->counter == 1) {
int status = redisAsyncCommand(ac, commandCallback, NULL, "PING");
assert(status == REDIS_OK);
} else {
redisAsyncFree(ac);
}
}
}
static redisAsyncContext *do_aconnect(struct config config, astest_no testno)
{
redisOptions options = {0};
memset(&astest, 0, sizeof(astest));
astest.testno = testno;
astest.connect_status = astest.disconnect_status = -2;
if (config.type == CONN_TCP) {
options.type = REDIS_CONN_TCP;
options.connect_timeout = &config.connect_timeout;
REDIS_OPTIONS_SET_TCP(&options, config.tcp.host, config.tcp.port);
} else if (config.type == CONN_SSL) {
options.type = REDIS_CONN_TCP;
options.connect_timeout = &config.connect_timeout;
REDIS_OPTIONS_SET_TCP(&options, config.ssl.host, config.ssl.port);
} else if (config.type == CONN_UNIX) {
options.type = REDIS_CONN_UNIX;
options.endpoint.unix_socket = config.unix_sock.path;
} else if (config.type == CONN_FD) {
options.type = REDIS_CONN_USERFD;
/* Create a dummy connection just to get an fd to inherit */
redisContext *dummy_ctx = redisConnectUnix(config.unix_sock.path);
if (dummy_ctx) {
redisFD fd = disconnect(dummy_ctx, 1);
printf("Connecting to inherited fd %d\n", (int)fd);
options.endpoint.fd = fd;
}
}
redisAsyncContext *c = redisAsyncConnectWithOptions(&options);
assert(c);
astest.ac = c;
c->data = &astest;
c->dataCleanup = asCleanup;
redisPollAttach(c);
redisAsyncSetConnectCallbackNC(c, connectCallback);
redisAsyncSetDisconnectCallback(c, disconnectCallback);
return c;
}
static void as_printerr(void) {
printf("Async err %d : %s\n", astest.err, astest.errstr);
}
#define ASASSERT(e) do { \
if (!(e)) \
as_printerr(); \
assert(e); \
} while (0);
static void test_async_polling(struct config config) {
int status;
redisAsyncContext *c;
struct config defaultconfig = config;
test("Async connect: ");
c = do_aconnect(config, ASTEST_CONNECT);
assert(c);
while(astest.connected == 0)
redisPollTick(c, 0.1);
assert(astest.connects == 1);
ASASSERT(astest.connect_status == REDIS_OK);
assert(astest.disconnects == 0);
test_cond(astest.connected == 1);
test("Async free after connect: ");
assert(astest.ac != NULL);
redisAsyncFree(c);
assert(astest.disconnects == 1);
assert(astest.ac == NULL);
test_cond(astest.disconnect_status == REDIS_OK);
if (config.type == CONN_TCP || config.type == CONN_SSL) {
/* timeout can only be simulated with network */
test("Async connect timeout: ");
config.tcp.host = "192.168.254.254"; /* blackhole ip */
config.connect_timeout.tv_usec = 100000;
c = do_aconnect(config, ASTEST_CONN_TIMEOUT);
assert(c);
assert(c->err == 0);
while(astest.connected == 0)
redisPollTick(c, 0.1);
assert(astest.connected == -1);
/*
* freeing should not be done, clearing should have happened.
*redisAsyncFree(c);
*/
assert(astest.ac == NULL);
test_cond(astest.connect_status == REDIS_ERR);
config = defaultconfig;
}
/* Test a ping/pong after connection */
test("Async PING/PONG: ");
c = do_aconnect(config, ASTEST_PINGPONG);
while(astest.connected == 0)
redisPollTick(c, 0.1);
status = redisAsyncCommand(c, commandCallback, NULL, "PING");
assert(status == REDIS_OK);
while(astest.ac)
redisPollTick(c, 0.1);
test_cond(astest.pongs == 1);
/* Test a ping/pong after connection that didn't time out.
* see https://github.com/redis/hiredis/issues/945
*/
if (config.type == CONN_TCP || config.type == CONN_SSL) {
test("Async PING/PONG after connect timeout: ");
config.connect_timeout.tv_usec = 10000; /* 10ms */
c = do_aconnect(config, ASTEST_PINGPONG_TIMEOUT);
while(astest.connected == 0)
redisPollTick(c, 0.1);
/* sleep 0.1 s, allowing old timeout to arrive */
millisleep(10);
status = redisAsyncCommand(c, commandCallback, NULL, "PING");
assert(status == REDIS_OK);
while(astest.ac)
redisPollTick(c, 0.1);
test_cond(astest.pongs == 2);
config = defaultconfig;
}
/* Test disconnect from an on_connect callback
* see https://github.com/redis/hiredis/issues/931
*/
test("Disconnect from onConnected callback (Issue #931): ");
c = do_aconnect(config, ASTEST_ISSUE_931);
while(astest.disconnects == 0)
redisPollTick(c, 0.1);
assert(astest.connected == 0);
assert(astest.connects == 1);
test_cond(astest.disconnects == 1);
/* Test ping/pong from an on_connect callback
* see https://github.com/redis/hiredis/issues/931
*/
test("Ping/Pong from onConnected callback (Issue #931): ");
c = do_aconnect(config, ASTEST_ISSUE_931_PING);
/* connect callback issues ping, reponse callback destroys context */
while(astest.ac)
redisPollTick(c, 0.1);
assert(astest.connected == 0);
assert(astest.connects == 1);
assert(astest.disconnects == 1);
test_cond(astest.pongs == 1);
}
/* End of Async polling_adapter driven tests */
int main(int argc, char **argv) { int main(int argc, char **argv) {
struct config cfg = { struct config cfg = {
.tcp = { .tcp = {
...@@ -1963,6 +2333,7 @@ int main(int argc, char **argv) { ...@@ -1963,6 +2333,7 @@ int main(int argc, char **argv) {
test_blocking_io_errors(cfg); test_blocking_io_errors(cfg);
test_invalid_timeout_errors(cfg); test_invalid_timeout_errors(cfg);
test_append_formatted_commands(cfg); test_append_formatted_commands(cfg);
test_tcp_options(cfg);
if (throughput) test_throughput(cfg); if (throughput) test_throughput(cfg);
printf("\nTesting against Unix socket connection (%s): ", cfg.unix_sock.path); printf("\nTesting against Unix socket connection (%s): ", cfg.unix_sock.path);
...@@ -1972,6 +2343,7 @@ int main(int argc, char **argv) { ...@@ -1972,6 +2343,7 @@ int main(int argc, char **argv) {
test_blocking_connection(cfg); test_blocking_connection(cfg);
test_blocking_connection_timeouts(cfg); test_blocking_connection_timeouts(cfg);
test_blocking_io_errors(cfg); test_blocking_io_errors(cfg);
test_invalid_timeout_errors(cfg);
if (throughput) test_throughput(cfg); if (throughput) test_throughput(cfg);
} else { } else {
test_skipped(); test_skipped();
...@@ -2000,6 +2372,7 @@ int main(int argc, char **argv) { ...@@ -2000,6 +2372,7 @@ int main(int argc, char **argv) {
#endif #endif
#ifdef HIREDIS_TEST_ASYNC #ifdef HIREDIS_TEST_ASYNC
cfg.type = CONN_TCP;
printf("\nTesting asynchronous API against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port); printf("\nTesting asynchronous API against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port);
cfg.type = CONN_TCP; cfg.type = CONN_TCP;
...@@ -2017,6 +2390,15 @@ int main(int argc, char **argv) { ...@@ -2017,6 +2390,15 @@ int main(int argc, char **argv) {
} }
#endif /* HIREDIS_TEST_ASYNC */ #endif /* HIREDIS_TEST_ASYNC */
cfg.type = CONN_TCP;
printf("\nTesting asynchronous API using polling_adapter TCP (%s:%d):\n", cfg.tcp.host, cfg.tcp.port);
test_async_polling(cfg);
if (test_unix_socket) {
cfg.type = CONN_UNIX;
printf("\nTesting asynchronous API using polling_adapter UNIX (%s):\n", cfg.unix_sock.path);
test_async_polling(cfg);
}
if (test_inherit_fd) { if (test_inherit_fd) {
printf("\nTesting against inherited fd (%s): ", cfg.unix_sock.path); printf("\nTesting against inherited fd (%s): ", cfg.unix_sock.path);
if (test_unix_socket) { if (test_unix_socket) {
......
...@@ -4,9 +4,17 @@ REDIS_SERVER=${REDIS_SERVER:-redis-server} ...@@ -4,9 +4,17 @@ REDIS_SERVER=${REDIS_SERVER:-redis-server}
REDIS_PORT=${REDIS_PORT:-56379} REDIS_PORT=${REDIS_PORT:-56379}
REDIS_SSL_PORT=${REDIS_SSL_PORT:-56443} REDIS_SSL_PORT=${REDIS_SSL_PORT:-56443}
TEST_SSL=${TEST_SSL:-0} TEST_SSL=${TEST_SSL:-0}
SKIPS_AS_FAILS=${SKIPS_AS_FAILS-:0} SKIPS_AS_FAILS=${SKIPS_AS_FAILS:-0}
ENABLE_DEBUG_CMD=
SSL_TEST_ARGS= SSL_TEST_ARGS=
SKIPS_ARG= SKIPS_ARG=${SKIPS_ARG:-}
REDIS_DOCKER=${REDIS_DOCKER:-}
# We need to enable the DEBUG command for redis-server >= 7.0.0
REDIS_MAJOR_VERSION="$(redis-server --version|awk -F'[^0-9]+' '{ print $2 }')"
if [ "$REDIS_MAJOR_VERSION" -gt "6" ]; then
ENABLE_DEBUG_CMD="enable-debug-command local"
fi
tmpdir=$(mktemp -d) tmpdir=$(mktemp -d)
PID_FILE=${tmpdir}/hiredis-test-redis.pid PID_FILE=${tmpdir}/hiredis-test-redis.pid
...@@ -43,20 +51,34 @@ if [ "$TEST_SSL" = "1" ]; then ...@@ -43,20 +51,34 @@ if [ "$TEST_SSL" = "1" ]; then
fi fi
cleanup() { cleanup() {
set +e if [ -n "${REDIS_DOCKER}" ] ; then
kill $(cat ${PID_FILE}) docker kill redis-test-server
else
set +e
kill $(cat ${PID_FILE})
fi
rm -rf ${tmpdir} rm -rf ${tmpdir}
} }
trap cleanup INT TERM EXIT trap cleanup INT TERM EXIT
# base config
cat > ${tmpdir}/redis.conf <<EOF cat > ${tmpdir}/redis.conf <<EOF
daemonize yes
pidfile ${PID_FILE} pidfile ${PID_FILE}
port ${REDIS_PORT} port ${REDIS_PORT}
bind 127.0.0.1
unixsocket ${SOCK_FILE} unixsocket ${SOCK_FILE}
unixsocketperm 777
EOF EOF
# if not running in docker add these:
if [ ! -n "${REDIS_DOCKER}" ]; then
cat >> ${tmpdir}/redis.conf <<EOF
daemonize yes
${ENABLE_DEBUG_CMD}
bind 127.0.0.1
EOF
fi
# if doing ssl, add these
if [ "$TEST_SSL" = "1" ]; then if [ "$TEST_SSL" = "1" ]; then
cat >> ${tmpdir}/redis.conf <<EOF cat >> ${tmpdir}/redis.conf <<EOF
tls-port ${REDIS_SSL_PORT} tls-port ${REDIS_SSL_PORT}
...@@ -66,13 +88,25 @@ tls-key-file ${SSL_KEY} ...@@ -66,13 +88,25 @@ tls-key-file ${SSL_KEY}
EOF EOF
fi fi
echo ${tmpdir}
cat ${tmpdir}/redis.conf cat ${tmpdir}/redis.conf
${REDIS_SERVER} ${tmpdir}/redis.conf if [ -n "${REDIS_DOCKER}" ] ; then
chmod a+wx ${tmpdir}
chmod a+r ${tmpdir}/*
docker run -d --rm --name redis-test-server \
-p ${REDIS_PORT}:${REDIS_PORT} \
-p ${REDIS_SSL_PORT}:${REDIS_SSL_PORT} \
-v ${tmpdir}:${tmpdir} \
${REDIS_DOCKER} \
redis-server ${tmpdir}/redis.conf
else
${REDIS_SERVER} ${tmpdir}/redis.conf
fi
# Wait until we detect the unix socket # Wait until we detect the unix socket
echo waiting for server
while [ ! -S "${SOCK_FILE}" ]; do sleep 1; done while [ ! -S "${SOCK_FILE}" ]; do sleep 1; done
# Treat skips as failures if directed # Treat skips as failures if directed
[ "$SKIPS_AS_FAILS" = 1 ] && SKIPS_ARG="--skips-as-fails" [ "$SKIPS_AS_FAILS" = 1 ] && SKIPS_ARG="${SKIPS_ARG} --skips-as-fails"
${TEST_PREFIX:-} ./hiredis-test -h 127.0.0.1 -p ${REDIS_PORT} -s ${SOCK_FILE} ${SSL_TEST_ARGS} ${SKIPS_ARG} ${TEST_PREFIX:-} ./hiredis-test -h 127.0.0.1 -p ${REDIS_PORT} -s ${SOCK_FILE} ${SSL_TEST_ARGS} ${SKIPS_ARG}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment