test.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <strings.h>
  5. #include <sys/time.h>
  6. #include <assert.h>
  7. #include <unistd.h>
  8. #include <signal.h>
  9. #include "hiredis.h"
  10. /* The following lines make up our testing "framework" :) */
  11. static int tests = 0, fails = 0;
  12. #define test(_s) { printf("#%02d ", ++tests); printf(_s); }
  13. #define test_cond(_c) if(_c) printf("PASSED\n"); else {printf("FAILED\n"); fails++;}
  14. static long long usec(void) {
  15. struct timeval tv;
  16. gettimeofday(&tv,NULL);
  17. return (((long long)tv.tv_sec)*1000000)+tv.tv_usec;
  18. }
  19. static int use_unix = 0;
  20. static redisContext *blocking_context = NULL;
  21. static void __connect(redisContext **target) {
  22. *target = blocking_context = (use_unix ?
  23. redisConnectUnix("/tmp/redis.sock") : redisConnect((char*)"127.0.0.1", 6379));
  24. if (blocking_context->err) {
  25. printf("Connection error: %s\n", blocking_context->errstr);
  26. exit(1);
  27. }
  28. }
  29. static void test_format_commands() {
  30. char *cmd;
  31. int len;
  32. test("Format command without interpolation: ");
  33. len = redisFormatCommand(&cmd,"SET foo bar");
  34. test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
  35. len == 4+4+(3+2)+4+(3+2)+4+(3+2));
  36. free(cmd);
  37. test("Format command with %%s string interpolation: ");
  38. len = redisFormatCommand(&cmd,"SET %s %s","foo","bar");
  39. test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
  40. len == 4+4+(3+2)+4+(3+2)+4+(3+2));
  41. free(cmd);
  42. test("Format command with %%s and an empty string: ");
  43. len = redisFormatCommand(&cmd,"SET %s %s","foo","");
  44. test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 &&
  45. len == 4+4+(3+2)+4+(3+2)+4+(0+2));
  46. free(cmd);
  47. test("Format command with %%b string interpolation: ");
  48. len = redisFormatCommand(&cmd,"SET %b %b","foo",3,"b\0r",3);
  49. test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nb\0r\r\n",len) == 0 &&
  50. len == 4+4+(3+2)+4+(3+2)+4+(3+2));
  51. free(cmd);
  52. test("Format command with %%b and an empty string: ");
  53. len = redisFormatCommand(&cmd,"SET %b %b","foo",3,"",0);
  54. test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 &&
  55. len == 4+4+(3+2)+4+(3+2)+4+(0+2));
  56. free(cmd);
  57. test("Format command with literal %%: ");
  58. len = redisFormatCommand(&cmd,"SET %% %%");
  59. test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$1\r\n%\r\n$1\r\n%\r\n",len) == 0 &&
  60. len == 4+4+(3+2)+4+(1+2)+4+(1+2));
  61. free(cmd);
  62. test("Format command with printf-delegation (long long): ");
  63. len = redisFormatCommand(&cmd,"key:%08lld",1234ll);
  64. test_cond(strncmp(cmd,"*1\r\n$12\r\nkey:00001234\r\n",len) == 0 &&
  65. len == 4+5+(12+2));
  66. free(cmd);
  67. test("Format command with printf-delegation (float): ");
  68. len = redisFormatCommand(&cmd,"v:%06.1f",12.34f);
  69. test_cond(strncmp(cmd,"*1\r\n$8\r\nv:0012.3\r\n",len) == 0 &&
  70. len == 4+4+(8+2));
  71. free(cmd);
  72. test("Format command with printf-delegation and extra interpolation: ");
  73. len = redisFormatCommand(&cmd,"key:%d %b",1234,"foo",3);
  74. test_cond(strncmp(cmd,"*2\r\n$8\r\nkey:1234\r\n$3\r\nfoo\r\n",len) == 0 &&
  75. len == 4+4+(8+2)+4+(3+2));
  76. free(cmd);
  77. test("Format command with wrong printf format and extra interpolation: ");
  78. len = redisFormatCommand(&cmd,"key:%08p %b",1234,"foo",3);
  79. test_cond(strncmp(cmd,"*2\r\n$6\r\nkey:8p\r\n$3\r\nfoo\r\n",len) == 0 &&
  80. len == 4+4+(6+2)+4+(3+2));
  81. free(cmd);
  82. const char *argv[3];
  83. argv[0] = "SET";
  84. argv[1] = "foo\0xxx";
  85. argv[2] = "bar";
  86. size_t lens[3] = { 3, 7, 3 };
  87. int argc = 3;
  88. test("Format command by passing argc/argv without lengths: ");
  89. len = redisFormatCommandArgv(&cmd,argc,argv,NULL);
  90. test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
  91. len == 4+4+(3+2)+4+(3+2)+4+(3+2));
  92. free(cmd);
  93. test("Format command by passing argc/argv with lengths: ");
  94. len = redisFormatCommandArgv(&cmd,argc,argv,lens);
  95. test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 &&
  96. len == 4+4+(3+2)+4+(7+2)+4+(3+2));
  97. free(cmd);
  98. }
  99. static void test_blocking_connection() {
  100. redisContext *c;
  101. redisReply *reply;
  102. int major, minor;
  103. test("Returns error when host cannot be resolved: ");
  104. c = redisConnect((char*)"idontexist.local", 6379);
  105. test_cond(c->err == REDIS_ERR_OTHER &&
  106. strcmp(c->errstr,"Can't resolve: idontexist.local") == 0);
  107. redisFree(c);
  108. test("Returns error when the port is not open: ");
  109. c = redisConnect((char*)"localhost", 56380);
  110. test_cond(c->err == REDIS_ERR_IO &&
  111. strcmp(c->errstr,"Connection refused") == 0);
  112. redisFree(c);
  113. __connect(&c);
  114. test("Is able to deliver commands: ");
  115. reply = redisCommand(c,"PING");
  116. test_cond(reply->type == REDIS_REPLY_STATUS &&
  117. strcasecmp(reply->str,"pong") == 0)
  118. freeReplyObject(reply);
  119. /* Switch to DB 9 for testing, now that we know we can chat. */
  120. reply = redisCommand(c,"SELECT 9");
  121. freeReplyObject(reply);
  122. /* Make sure the DB is emtpy */
  123. reply = redisCommand(c,"DBSIZE");
  124. if (reply->type != REDIS_REPLY_INTEGER || reply->integer != 0) {
  125. printf("Database #9 is not empty, test can not continue\n");
  126. exit(1);
  127. }
  128. freeReplyObject(reply);
  129. test("Is a able to send commands verbatim: ");
  130. reply = redisCommand(c,"SET foo bar");
  131. test_cond (reply->type == REDIS_REPLY_STATUS &&
  132. strcasecmp(reply->str,"ok") == 0)
  133. freeReplyObject(reply);
  134. test("%%s String interpolation works: ");
  135. reply = redisCommand(c,"SET %s %s","foo","hello world");
  136. freeReplyObject(reply);
  137. reply = redisCommand(c,"GET foo");
  138. test_cond(reply->type == REDIS_REPLY_STRING &&
  139. strcmp(reply->str,"hello world") == 0);
  140. freeReplyObject(reply);
  141. test("%%b String interpolation works: ");
  142. reply = redisCommand(c,"SET %b %b","foo",3,"hello\x00world",11);
  143. freeReplyObject(reply);
  144. reply = redisCommand(c,"GET foo");
  145. test_cond(reply->type == REDIS_REPLY_STRING &&
  146. memcmp(reply->str,"hello\x00world",11) == 0)
  147. test("Binary reply length is correct: ");
  148. test_cond(reply->len == 11)
  149. freeReplyObject(reply);
  150. test("Can parse nil replies: ");
  151. reply = redisCommand(c,"GET nokey");
  152. test_cond(reply->type == REDIS_REPLY_NIL)
  153. freeReplyObject(reply);
  154. /* test 7 */
  155. test("Can parse integer replies: ");
  156. reply = redisCommand(c,"INCR mycounter");
  157. test_cond(reply->type == REDIS_REPLY_INTEGER && reply->integer == 1)
  158. freeReplyObject(reply);
  159. test("Can parse multi bulk replies: ");
  160. freeReplyObject(redisCommand(c,"LPUSH mylist foo"));
  161. freeReplyObject(redisCommand(c,"LPUSH mylist bar"));
  162. reply = redisCommand(c,"LRANGE mylist 0 -1");
  163. test_cond(reply->type == REDIS_REPLY_ARRAY &&
  164. reply->elements == 2 &&
  165. !memcmp(reply->element[0]->str,"bar",3) &&
  166. !memcmp(reply->element[1]->str,"foo",3))
  167. freeReplyObject(reply);
  168. /* m/e with multi bulk reply *before* other reply.
  169. * specifically test ordering of reply items to parse. */
  170. test("Can handle nested multi bulk replies: ");
  171. freeReplyObject(redisCommand(c,"MULTI"));
  172. freeReplyObject(redisCommand(c,"LRANGE mylist 0 -1"));
  173. freeReplyObject(redisCommand(c,"PING"));
  174. reply = (redisCommand(c,"EXEC"));
  175. test_cond(reply->type == REDIS_REPLY_ARRAY &&
  176. reply->elements == 2 &&
  177. reply->element[0]->type == REDIS_REPLY_ARRAY &&
  178. reply->element[0]->elements == 2 &&
  179. !memcmp(reply->element[0]->element[0]->str,"bar",3) &&
  180. !memcmp(reply->element[0]->element[1]->str,"foo",3) &&
  181. reply->element[1]->type == REDIS_REPLY_STATUS &&
  182. strcasecmp(reply->element[1]->str,"pong") == 0);
  183. freeReplyObject(reply);
  184. {
  185. /* Find out Redis version to determine the path for the next test */
  186. const char *field = "redis_version:";
  187. char *p, *eptr;
  188. reply = redisCommand(c,"INFO");
  189. p = strstr(reply->str,field);
  190. major = strtol(p+strlen(field),&eptr,10);
  191. p = eptr+1; /* char next to the first "." */
  192. minor = strtol(p,&eptr,10);
  193. freeReplyObject(reply);
  194. }
  195. test("Returns I/O error when the connection is lost: ");
  196. reply = redisCommand(c,"QUIT");
  197. if (major >= 2 && minor > 0) {
  198. /* > 2.0 returns OK on QUIT and read() should be issued once more
  199. * to know the descriptor is at EOF. */
  200. test_cond(strcasecmp(reply->str,"OK") == 0 &&
  201. redisGetReply(c,(void**)&reply) == REDIS_ERR);
  202. freeReplyObject(reply);
  203. } else {
  204. test_cond(reply == NULL);
  205. }
  206. /* On 2.0, QUIT will cause the connection to be closed immediately and
  207. * the read(2) for the reply on QUIT will set the error to EOF.
  208. * On >2.0, QUIT will return with OK and another read(2) needed to be
  209. * issued to find out the socket was closed by the server. In both
  210. * conditions, the error will be set to EOF. */
  211. assert(c->err == REDIS_ERR_EOF &&
  212. strcmp(c->errstr,"Server closed the connection") == 0);
  213. /* Clean up context and reconnect again */
  214. redisFree(c);
  215. __connect(&c);
  216. }
  217. static void test_reply_reader() {
  218. void *reader;
  219. void *reply;
  220. char *err;
  221. int ret;
  222. test("Error handling in reply parser: ");
  223. reader = redisReplyReaderCreate();
  224. redisReplyReaderFeed(reader,(char*)"@foo\r\n",6);
  225. ret = redisReplyReaderGetReply(reader,NULL);
  226. err = redisReplyReaderGetError(reader);
  227. test_cond(ret == REDIS_ERR &&
  228. strcasecmp(err,"Protocol error, got \"@\" as reply type byte") == 0);
  229. redisReplyReaderFree(reader);
  230. /* when the reply already contains multiple items, they must be free'd
  231. * on an error. valgrind will bark when this doesn't happen. */
  232. test("Memory cleanup in reply parser: ");
  233. reader = redisReplyReaderCreate();
  234. redisReplyReaderFeed(reader,(char*)"*2\r\n",4);
  235. redisReplyReaderFeed(reader,(char*)"$5\r\nhello\r\n",11);
  236. redisReplyReaderFeed(reader,(char*)"@foo\r\n",6);
  237. ret = redisReplyReaderGetReply(reader,NULL);
  238. err = redisReplyReaderGetError(reader);
  239. test_cond(ret == REDIS_ERR &&
  240. strcasecmp(err,"Protocol error, got \"@\" as reply type byte") == 0);
  241. redisReplyReaderFree(reader);
  242. test("Set error on nested multi bulks with depth > 1: ");
  243. reader = redisReplyReaderCreate();
  244. redisReplyReaderFeed(reader,(char*)"*1\r\n",4);
  245. redisReplyReaderFeed(reader,(char*)"*1\r\n",4);
  246. redisReplyReaderFeed(reader,(char*)"*1\r\n",4);
  247. ret = redisReplyReaderGetReply(reader,NULL);
  248. err = redisReplyReaderGetError(reader);
  249. test_cond(ret == REDIS_ERR &&
  250. strncasecmp(err,"No support for",14) == 0);
  251. redisReplyReaderFree(reader);
  252. test("Works with NULL functions for reply: ");
  253. reader = redisReplyReaderCreate();
  254. redisReplyReaderSetReplyObjectFunctions(reader,NULL);
  255. redisReplyReaderFeed(reader,(char*)"+OK\r\n",5);
  256. ret = redisReplyReaderGetReply(reader,&reply);
  257. test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS);
  258. redisReplyReaderFree(reader);
  259. test("Works when a single newline (\\r\\n) covers two calls to feed: ");
  260. reader = redisReplyReaderCreate();
  261. redisReplyReaderSetReplyObjectFunctions(reader,NULL);
  262. redisReplyReaderFeed(reader,(char*)"+OK\r",4);
  263. ret = redisReplyReaderGetReply(reader,&reply);
  264. assert(ret == REDIS_OK && reply == NULL);
  265. redisReplyReaderFeed(reader,(char*)"\n",1);
  266. ret = redisReplyReaderGetReply(reader,&reply);
  267. test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS);
  268. redisReplyReaderFree(reader);
  269. }
  270. static void test_throughput() {
  271. int i;
  272. long long t1, t2;
  273. redisContext *c = blocking_context;
  274. redisReply **replies;
  275. test("Throughput:\n");
  276. for (i = 0; i < 500; i++)
  277. freeReplyObject(redisCommand(c,"LPUSH mylist foo"));
  278. replies = malloc(sizeof(redisReply*)*1000);
  279. t1 = usec();
  280. for (i = 0; i < 1000; i++) {
  281. replies[i] = redisCommand(c,"PING");
  282. assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS);
  283. }
  284. t2 = usec();
  285. for (i = 0; i < 1000; i++) freeReplyObject(replies[i]);
  286. free(replies);
  287. printf("\t(1000x PING: %.2fs)\n", (t2-t1)/1000000.0);
  288. replies = malloc(sizeof(redisReply*)*1000);
  289. t1 = usec();
  290. for (i = 0; i < 1000; i++) {
  291. replies[i] = redisCommand(c,"LRANGE mylist 0 499");
  292. assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY);
  293. assert(replies[i] != NULL && replies[i]->elements == 500);
  294. }
  295. t2 = usec();
  296. for (i = 0; i < 1000; i++) freeReplyObject(replies[i]);
  297. free(replies);
  298. printf("\t(1000x LRANGE with 500 elements: %.2fs)\n", (t2-t1)/1000000.0);
  299. }
  300. static void cleanup() {
  301. redisContext *c = blocking_context;
  302. redisReply *reply;
  303. /* Make sure we're on DB 9 */
  304. reply = redisCommand(c,"SELECT 9");
  305. assert(reply != NULL); freeReplyObject(reply);
  306. reply = redisCommand(c,"FLUSHDB");
  307. assert(reply != NULL); freeReplyObject(reply);
  308. redisFree(c);
  309. }
  310. // static long __test_callback_flags = 0;
  311. // static void __test_callback(redisContext *c, void *privdata) {
  312. // ((void)c);
  313. // /* Shift to detect execution order */
  314. // __test_callback_flags <<= 8;
  315. // __test_callback_flags |= (long)privdata;
  316. // }
  317. //
  318. // static void __test_reply_callback(redisContext *c, redisReply *reply, void *privdata) {
  319. // ((void)c);
  320. // /* Shift to detect execution order */
  321. // __test_callback_flags <<= 8;
  322. // __test_callback_flags |= (long)privdata;
  323. // if (reply) freeReplyObject(reply);
  324. // }
  325. //
  326. // static redisContext *__connect_nonblock() {
  327. // /* Reset callback flags */
  328. // __test_callback_flags = 0;
  329. // return redisConnectNonBlock("127.0.0.1", 6379, NULL);
  330. // }
  331. //
  332. // static void test_nonblocking_connection() {
  333. // redisContext *c;
  334. // int wdone = 0;
  335. //
  336. // test("Calls command callback when command is issued: ");
  337. // c = __connect_nonblock();
  338. // redisSetCommandCallback(c,__test_callback,(void*)1);
  339. // redisCommand(c,"PING");
  340. // test_cond(__test_callback_flags == 1);
  341. // redisFree(c);
  342. //
  343. // test("Calls disconnect callback on redisDisconnect: ");
  344. // c = __connect_nonblock();
  345. // redisSetDisconnectCallback(c,__test_callback,(void*)2);
  346. // redisDisconnect(c);
  347. // test_cond(__test_callback_flags == 2);
  348. // redisFree(c);
  349. //
  350. // test("Calls disconnect callback and free callback on redisFree: ");
  351. // c = __connect_nonblock();
  352. // redisSetDisconnectCallback(c,__test_callback,(void*)2);
  353. // redisSetFreeCallback(c,__test_callback,(void*)4);
  354. // redisFree(c);
  355. // test_cond(__test_callback_flags == ((2 << 8) | 4));
  356. //
  357. // test("redisBufferWrite against empty write buffer: ");
  358. // c = __connect_nonblock();
  359. // test_cond(redisBufferWrite(c,&wdone) == REDIS_OK && wdone == 1);
  360. // redisFree(c);
  361. //
  362. // test("redisBufferWrite against not yet connected fd: ");
  363. // c = __connect_nonblock();
  364. // redisCommand(c,"PING");
  365. // test_cond(redisBufferWrite(c,NULL) == REDIS_ERR &&
  366. // strncmp(c->error,"write:",6) == 0);
  367. // redisFree(c);
  368. //
  369. // test("redisBufferWrite against closed fd: ");
  370. // c = __connect_nonblock();
  371. // redisCommand(c,"PING");
  372. // redisDisconnect(c);
  373. // test_cond(redisBufferWrite(c,NULL) == REDIS_ERR &&
  374. // strncmp(c->error,"write:",6) == 0);
  375. // redisFree(c);
  376. //
  377. // test("Process callbacks in the right sequence: ");
  378. // c = __connect_nonblock();
  379. // redisCommandWithCallback(c,__test_reply_callback,(void*)1,"PING");
  380. // redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING");
  381. // redisCommandWithCallback(c,__test_reply_callback,(void*)3,"PING");
  382. //
  383. // /* Write output buffer */
  384. // wdone = 0;
  385. // while(!wdone) {
  386. // usleep(500);
  387. // redisBufferWrite(c,&wdone);
  388. // }
  389. //
  390. // /* Read until at least one callback is executed (the 3 replies will
  391. // * arrive in a single packet, causing all callbacks to be executed in
  392. // * a single pass). */
  393. // while(__test_callback_flags == 0) {
  394. // assert(redisBufferRead(c) == REDIS_OK);
  395. // redisProcessCallbacks(c);
  396. // }
  397. // test_cond(__test_callback_flags == 0x010203);
  398. // redisFree(c);
  399. //
  400. // test("redisDisconnect executes pending callbacks with NULL reply: ");
  401. // c = __connect_nonblock();
  402. // redisSetDisconnectCallback(c,__test_callback,(void*)1);
  403. // redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING");
  404. // redisDisconnect(c);
  405. // test_cond(__test_callback_flags == 0x0201);
  406. // redisFree(c);
  407. // }
  408. int main(int argc, char **argv) {
  409. if (argc > 1) {
  410. if (strcmp(argv[1],"-s") == 0)
  411. use_unix = 1;
  412. }
  413. signal(SIGPIPE, SIG_IGN);
  414. test_format_commands();
  415. test_blocking_connection();
  416. test_reply_reader();
  417. // test_nonblocking_connection();
  418. test_throughput();
  419. cleanup();
  420. if (fails == 0) {
  421. printf("ALL TESTS PASSED\n");
  422. } else {
  423. printf("*** %d TESTS FAILED ***\n", fails);
  424. }
  425. return 0;
  426. }