#include #include #include #include #include #include "hiredis.h" /* The following lines make up our testing "framework" :) */ #define test(_s) { printf("#%02d ", ++tests); printf(_s); } #define test_cond(_c) if(_c) printf("PASSED\n"); else {printf("FAILED\n"); fails++;} static long long usec(void) { struct timeval tv; gettimeofday(&tv,NULL); return (((long long)tv.tv_sec)*1000000)+tv.tv_usec; } static void __connect(redisContext **target) { redisContext *c; c = *target = redisConnect((char*)"127.0.0.1", 6379, NULL); if (c->error != NULL) { printf("Connection error: %s\n", c->error); exit(1); } } int main(void) { int i, ret, tests = 0, fails = 0; long long t1, t2; redisContext *c; redisReply *reply, **replies; void *reader; char *err; __connect(&c); test("Returns I/O error when the connection is lost: "); test_cond(redisCommand(c,"QUIT") == NULL && strcmp(c->error,"Server closed the connection") == 0); redisFree(c); __connect(&c); /* reconnect */ test("Is able to deliver commands: "); reply = redisCommand(c,"PING"); test_cond(reply->type == REDIS_REPLY_STRING && strcasecmp(reply->reply,"pong") == 0) freeReplyObject(reply); /* Switch to DB 9 for testing, now that we know we can chat. */ reply = redisCommand(c,"SELECT 9"); freeReplyObject(reply); /* Make sure the DB is emtpy */ reply = redisCommand(c,"DBSIZE"); if (reply->type != REDIS_REPLY_INTEGER || reply->integer != 0) { printf("Sorry DB 9 is not empty, test can not continue\n"); exit(1); } else { printf("DB 9 is empty... test can continue\n"); } freeReplyObject(reply); test("Is a able to send commands verbatim: "); reply = redisCommand(c,"SET foo bar"); test_cond (reply->type == REDIS_REPLY_STRING && strcasecmp(reply->reply,"ok") == 0) freeReplyObject(reply); test("%%s String interpolation works: "); reply = redisCommand(c,"SET %s %s","foo","hello world"); freeReplyObject(reply); reply = redisCommand(c,"GET foo"); test_cond(reply->type == REDIS_REPLY_STRING && strcmp(reply->reply,"hello world") == 0); freeReplyObject(reply); test("%%b String interpolation works: "); reply = redisCommand(c,"SET %b %b","foo",3,"hello\x00world",11); freeReplyObject(reply); reply = redisCommand(c,"GET foo"); test_cond(reply->type == REDIS_REPLY_STRING && memcmp(reply->reply,"hello\x00world",11) == 0) test("Binary reply length is correct: "); test_cond(sdslen(reply->reply) == 11) freeReplyObject(reply); test("Can parse nil replies: "); reply = redisCommand(c,"GET nokey"); test_cond(reply->type == REDIS_REPLY_NIL) freeReplyObject(reply); /* test 7 */ test("Can parse integer replies: "); reply = redisCommand(c,"INCR mycounter"); test_cond(reply->type == REDIS_REPLY_INTEGER && reply->integer == 1) freeReplyObject(reply); test("Can parse multi bulk replies: "); freeReplyObject(redisCommand(c,"LPUSH mylist foo")); freeReplyObject(redisCommand(c,"LPUSH mylist bar")); reply = redisCommand(c,"LRANGE mylist 0 -1"); test_cond(reply->type == REDIS_REPLY_ARRAY && reply->elements == 2 && !memcmp(reply->element[0]->reply,"bar",3) && !memcmp(reply->element[1]->reply,"foo",3)) freeReplyObject(reply); /* m/e with multi bulk reply *before* other reply. * specifically test ordering of reply items to parse. */ test("Can handle nested multi bulk replies: "); freeReplyObject(redisCommand(c,"MULTI")); freeReplyObject(redisCommand(c,"LRANGE mylist 0 -1")); freeReplyObject(redisCommand(c,"PING")); reply = (redisCommand(c,"EXEC")); test_cond(reply->type == REDIS_REPLY_ARRAY && reply->elements == 2 && reply->element[0]->type == REDIS_REPLY_ARRAY && reply->element[0]->elements == 2 && !memcmp(reply->element[0]->element[0]->reply,"bar",3) && !memcmp(reply->element[0]->element[1]->reply,"foo",3) && reply->element[1]->type == REDIS_REPLY_STRING && strcasecmp(reply->element[1]->reply,"pong") == 0); freeReplyObject(reply); test("Error handling in reply parser: "); reader = redisReplyReaderCreate(NULL); redisReplyReaderFeed(reader,(char*)"@foo\r\n",6); ret = redisReplyReaderGetReply(reader,(void*)&reply); err = redisReplyReaderGetError(reader); test_cond(ret == REDIS_ERR && strcasecmp(err,"protocol error, got \"@\" as reply type byte") == 0); redisReplyReaderFree(reader); /* when the reply already contains multiple items, they must be free'd * on an error. valgrind will bark when this doesn't happen. */ test("Memory cleanup in reply parser: "); reader = redisReplyReaderCreate(NULL); redisReplyReaderFeed(reader,(char*)"*2\r\n",4); redisReplyReaderFeed(reader,(char*)"$5\r\nhello\r\n",11); redisReplyReaderFeed(reader,(char*)"@foo\r\n",6); ret = redisReplyReaderGetReply(reader,(void*)&reply); err = redisReplyReaderGetError(reader); test_cond(ret == REDIS_ERR && strcasecmp(err,"protocol error, got \"@\" as reply type byte") == 0); redisReplyReaderFree(reader); test("Throughput:\n"); for (i = 0; i < 500; i++) freeReplyObject(redisCommand(c,"LPUSH mylist foo")); replies = malloc(sizeof(redisReply*)*1000); t1 = usec(); for (i = 0; i < 1000; i++) replies[i] = redisCommand(c,"PING"); t2 = usec(); for (i = 0; i < 1000; i++) freeReplyObject(replies[i]); free(replies); printf("\t(1000x PING: %.2fs)\n", (t2-t1)/1000000.0); replies = malloc(sizeof(redisReply*)*1000); t1 = usec(); for (i = 0; i < 1000; i++) replies[i] = redisCommand(c,"LRANGE mylist 0 499"); t2 = usec(); for (i = 0; i < 1000; i++) freeReplyObject(replies[i]); free(replies); printf("\t(1000x LRANGE with 500 elements: %.2fs)\n", (t2-t1)/1000000.0); /* Clean DB 9 */ reply = redisCommand(c,"FLUSHDB"); freeReplyObject(reply); if (fails == 0) { printf("ALL TESTS PASSED\n"); } else { printf("*** %d TESTS FAILED ***\n", fails); } redisFree(c); return 0; }