diff options
Diffstat (limited to 'test.c')
-rw-r--r-- | test.c | 247 |
1 files changed, 243 insertions, 4 deletions
@@ -15,6 +15,7 @@ #include "hiredis.h" #include "async.h" +#include "adapters/poll.h" #ifdef HIREDIS_TEST_SSL #include "hiredis_ssl.h" #endif @@ -914,6 +915,11 @@ static void test_resp3_push_handler(redisContext *c) { old = redisSetPushCallback(c, push_handler); test("We can set a custom RESP3 PUSH handler: "); 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. */ + test_cond(reply != NULL); + freeReplyObject(reply); + reply = redisCommand(c, "PING"); test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && pc.str == 1); freeReplyObject(reply); @@ -929,6 +935,12 @@ static void test_resp3_push_handler(redisContext *c) { assert((reply = redisCommand(c, "GET key:0")) != NULL); freeReplyObject(reply); 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); freeReplyObject(reply); @@ -1729,10 +1741,14 @@ void subscribe_channel_a_cb(redisAsyncContext *ac, void *r, void *privdata) { strcmp(reply->element[2]->str,"Hello!") == 0); 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, (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 */ state->disconnect = 1; redisAsyncCommand(ac,integer_cb,state,"LPUSH mylist foo"); @@ -1767,8 +1783,10 @@ void subscribe_channel_b_cb(redisAsyncContext *ac, void *r, void *privdata) { /* Test handling of multiple channels * - subscribe to channel A and B - * - a published message on A triggers an unsubscribe of channel B, X and A - * where channel X is not subscribed to. + * - a published message on A triggers an unsubscribe of channel B, X, A and Z + * 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 */ static void test_pubsub_multiple_channels(struct config config) { test("Subscribe to multiple channels: "); @@ -1881,6 +1899,217 @@ static void test_monitor(struct config config) { } #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_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 disconnect_status; + int connected; + int err; + char errstr[256]; +}; +static struct _astest astest; + +static void asSleep(int ms) +{ +#if _MSC_VER + Sleep(ms); +#else + usleep(ms*1000); +#endif +} + +/* async callbacks */ +static void asCleanup(void* data) +{ + struct _astest *t = (struct _astest *)data; + t->ac = NULL; +} + +static void connectCallback(const 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; +} +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); + if (t->testno == ASTEST_PINGPONG) + { + test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0); + redisAsyncFree(ac); + } + if (t->testno == ASTEST_PINGPONG_TIMEOUT) + { + /* two ping pongs */ + assert(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0); + if (++t->counter == 1) { + int status = redisAsyncCommand(ac, commandCallback, NULL, "PING"); + assert(status == REDIS_OK); + } else { + test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0); + 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.tcp.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.tcp.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); + redisAsyncSetConnectCallback(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.tcp.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 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.tcp.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 */ + asSleep(10); + status = redisAsyncCommand(c, commandCallback, NULL, "PING"); + assert(status == REDIS_OK); + while(astest.ac) + redisPollTick(c, 0.1); + config = defaultconfig; + } +} +/* End of Async polling_adapter driven tests */ + int main(int argc, char **argv) { struct config cfg = { .tcp = { @@ -2000,6 +2229,7 @@ int main(int argc, char **argv) { #endif #ifdef HIREDIS_TEST_ASYNC + cfg.type = CONN_TCP; printf("\nTesting asynchronous API against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port); cfg.type = CONN_TCP; @@ -2017,6 +2247,15 @@ int main(int argc, char **argv) { } #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) { printf("\nTesting against inherited fd (%s): ", cfg.unix_sock.path); if (test_unix_socket) { |