2
0

test.c 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733
  1. #include "fmacros.h"
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <string.h>
  5. #include <strings.h>
  6. #include <sys/time.h>
  7. #include <assert.h>
  8. #include <unistd.h>
  9. #include <signal.h>
  10. #include <errno.h>
  11. #include <limits.h>
  12. #include "hiredis.h"
  13. enum connection_type {
  14. CONN_TCP,
  15. CONN_UNIX,
  16. CONN_FD
  17. };
  18. struct config {
  19. enum connection_type type;
  20. struct {
  21. const char *host;
  22. int port;
  23. struct timeval timeout;
  24. } tcp;
  25. struct {
  26. const char *path;
  27. } unix;
  28. };
  29. /* The following lines make up our testing "framework" :) */
  30. static int tests = 0, fails = 0;
  31. #define test(_s) { printf("#%02d ", ++tests); printf(_s); }
  32. #define test_cond(_c) if(_c) printf("\033[0;32mPASSED\033[0;0m\n"); else {printf("\033[0;31mFAILED\033[0;0m\n"); fails++;}
  33. static long long usec(void) {
  34. struct timeval tv;
  35. gettimeofday(&tv,NULL);
  36. return (((long long)tv.tv_sec)*1000000)+tv.tv_usec;
  37. }
  38. static redisContext *select_database(redisContext *c) {
  39. redisReply *reply;
  40. /* Switch to DB 9 for testing, now that we know we can chat. */
  41. reply = redisCommand(c,"SELECT 9");
  42. assert(reply != NULL);
  43. freeReplyObject(reply);
  44. /* Make sure the DB is empty */
  45. reply = redisCommand(c,"DBSIZE");
  46. assert(reply != NULL);
  47. if (reply->type == REDIS_REPLY_INTEGER && reply->integer == 0) {
  48. /* Awesome, DB 9 is empty and we can continue. */
  49. freeReplyObject(reply);
  50. } else {
  51. printf("Database #9 is not empty, test can not continue\n");
  52. exit(1);
  53. }
  54. return c;
  55. }
  56. static int disconnect(redisContext *c, int keep_fd) {
  57. redisReply *reply;
  58. /* Make sure we're on DB 9. */
  59. reply = redisCommand(c,"SELECT 9");
  60. assert(reply != NULL);
  61. freeReplyObject(reply);
  62. reply = redisCommand(c,"FLUSHDB");
  63. assert(reply != NULL);
  64. freeReplyObject(reply);
  65. /* Free the context as well, but keep the fd if requested. */
  66. if (keep_fd)
  67. return redisFreeKeepFd(c);
  68. redisFree(c);
  69. return -1;
  70. }
  71. static redisContext *connect(struct config config) {
  72. redisContext *c = NULL;
  73. if (config.type == CONN_TCP) {
  74. c = redisConnect(config.tcp.host, config.tcp.port);
  75. } else if (config.type == CONN_UNIX) {
  76. c = redisConnectUnix(config.unix.path);
  77. } else if (config.type == CONN_FD) {
  78. /* Create a dummy connection just to get an fd to inherit */
  79. redisContext *dummy_ctx = redisConnectUnix(config.unix.path);
  80. if (dummy_ctx) {
  81. int fd = disconnect(dummy_ctx, 1);
  82. printf("Connecting to inherited fd %d\n", fd);
  83. c = redisConnectFd(fd);
  84. }
  85. } else {
  86. assert(NULL);
  87. }
  88. if (c == NULL) {
  89. printf("Connection error: can't allocate redis context\n");
  90. exit(1);
  91. } else if (c->err) {
  92. printf("Connection error: %s\n", c->errstr);
  93. exit(1);
  94. }
  95. return select_database(c);
  96. }
  97. static void test_format_commands(void) {
  98. char *cmd;
  99. int len;
  100. test("Format command without interpolation: ");
  101. len = redisFormatCommand(&cmd,"SET foo bar");
  102. test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
  103. len == 4+4+(3+2)+4+(3+2)+4+(3+2));
  104. free(cmd);
  105. test("Format command with %%s string interpolation: ");
  106. len = redisFormatCommand(&cmd,"SET %s %s","foo","bar");
  107. test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
  108. len == 4+4+(3+2)+4+(3+2)+4+(3+2));
  109. free(cmd);
  110. test("Format command with %%s and an empty string: ");
  111. len = redisFormatCommand(&cmd,"SET %s %s","foo","");
  112. test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 &&
  113. len == 4+4+(3+2)+4+(3+2)+4+(0+2));
  114. free(cmd);
  115. test("Format command with an empty string in between proper interpolations: ");
  116. len = redisFormatCommand(&cmd,"SET %s %s","","foo");
  117. test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$0\r\n\r\n$3\r\nfoo\r\n",len) == 0 &&
  118. len == 4+4+(3+2)+4+(0+2)+4+(3+2));
  119. free(cmd);
  120. test("Format command with %%b string interpolation: ");
  121. len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"b\0r",(size_t)3);
  122. 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 &&
  123. len == 4+4+(3+2)+4+(3+2)+4+(3+2));
  124. free(cmd);
  125. test("Format command with %%b and an empty string: ");
  126. len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"",(size_t)0);
  127. test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 &&
  128. len == 4+4+(3+2)+4+(3+2)+4+(0+2));
  129. free(cmd);
  130. test("Format command with literal %%: ");
  131. len = redisFormatCommand(&cmd,"SET %% %%");
  132. test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$1\r\n%\r\n$1\r\n%\r\n",len) == 0 &&
  133. len == 4+4+(3+2)+4+(1+2)+4+(1+2));
  134. free(cmd);
  135. /* Vararg width depends on the type. These tests make sure that the
  136. * width is correctly determined using the format and subsequent varargs
  137. * can correctly be interpolated. */
  138. #define INTEGER_WIDTH_TEST(fmt, type) do { \
  139. type value = 123; \
  140. test("Format command with printf-delegation (" #type "): "); \
  141. len = redisFormatCommand(&cmd,"key:%08" fmt " str:%s", value, "hello"); \
  142. test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:00000123\r\n$9\r\nstr:hello\r\n",len) == 0 && \
  143. len == 4+5+(12+2)+4+(9+2)); \
  144. free(cmd); \
  145. } while(0)
  146. #define FLOAT_WIDTH_TEST(type) do { \
  147. type value = 123.0; \
  148. test("Format command with printf-delegation (" #type "): "); \
  149. len = redisFormatCommand(&cmd,"key:%08.3f str:%s", value, "hello"); \
  150. test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:0123.000\r\n$9\r\nstr:hello\r\n",len) == 0 && \
  151. len == 4+5+(12+2)+4+(9+2)); \
  152. free(cmd); \
  153. } while(0)
  154. INTEGER_WIDTH_TEST("d", int);
  155. INTEGER_WIDTH_TEST("hhd", char);
  156. INTEGER_WIDTH_TEST("hd", short);
  157. INTEGER_WIDTH_TEST("ld", long);
  158. INTEGER_WIDTH_TEST("lld", long long);
  159. INTEGER_WIDTH_TEST("u", unsigned int);
  160. INTEGER_WIDTH_TEST("hhu", unsigned char);
  161. INTEGER_WIDTH_TEST("hu", unsigned short);
  162. INTEGER_WIDTH_TEST("lu", unsigned long);
  163. INTEGER_WIDTH_TEST("llu", unsigned long long);
  164. FLOAT_WIDTH_TEST(float);
  165. FLOAT_WIDTH_TEST(double);
  166. test("Format command with invalid printf format: ");
  167. len = redisFormatCommand(&cmd,"key:%08p %b",(void*)1234,"foo",(size_t)3);
  168. test_cond(len == -1);
  169. const char *argv[3];
  170. argv[0] = "SET";
  171. argv[1] = "foo\0xxx";
  172. argv[2] = "bar";
  173. size_t lens[3] = { 3, 7, 3 };
  174. int argc = 3;
  175. test("Format command by passing argc/argv without lengths: ");
  176. len = redisFormatCommandArgv(&cmd,argc,argv,NULL);
  177. test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
  178. len == 4+4+(3+2)+4+(3+2)+4+(3+2));
  179. free(cmd);
  180. test("Format command by passing argc/argv with lengths: ");
  181. len = redisFormatCommandArgv(&cmd,argc,argv,lens);
  182. 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 &&
  183. len == 4+4+(3+2)+4+(7+2)+4+(3+2));
  184. free(cmd);
  185. }
  186. static void test_append_formatted_commands(struct config config) {
  187. redisContext *c;
  188. redisReply *reply;
  189. char *cmd;
  190. int len;
  191. c = connect(config);
  192. test("Append format command: ");
  193. len = redisFormatCommand(&cmd, "SET foo bar");
  194. test_cond(redisAppendFormattedCommand(c, cmd, len) == REDIS_OK);
  195. assert(redisGetReply(c, (void*)&reply) == REDIS_OK);
  196. free(cmd);
  197. freeReplyObject(reply);
  198. disconnect(c, 0);
  199. }
  200. static void test_reply_reader(void) {
  201. redisReader *reader;
  202. void *reply;
  203. int ret;
  204. int i;
  205. test("Error handling in reply parser: ");
  206. reader = redisReaderCreate();
  207. redisReaderFeed(reader,(char*)"@foo\r\n",6);
  208. ret = redisReaderGetReply(reader,NULL);
  209. test_cond(ret == REDIS_ERR &&
  210. strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0);
  211. redisReaderFree(reader);
  212. /* when the reply already contains multiple items, they must be free'd
  213. * on an error. valgrind will bark when this doesn't happen. */
  214. test("Memory cleanup in reply parser: ");
  215. reader = redisReaderCreate();
  216. redisReaderFeed(reader,(char*)"*2\r\n",4);
  217. redisReaderFeed(reader,(char*)"$5\r\nhello\r\n",11);
  218. redisReaderFeed(reader,(char*)"@foo\r\n",6);
  219. ret = redisReaderGetReply(reader,NULL);
  220. test_cond(ret == REDIS_ERR &&
  221. strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0);
  222. redisReaderFree(reader);
  223. test("Set error on nested multi bulks with depth > 7: ");
  224. reader = redisReaderCreate();
  225. for (i = 0; i < 9; i++) {
  226. redisReaderFeed(reader,(char*)"*1\r\n",4);
  227. }
  228. ret = redisReaderGetReply(reader,NULL);
  229. test_cond(ret == REDIS_ERR &&
  230. strncasecmp(reader->errstr,"No support for",14) == 0);
  231. redisReaderFree(reader);
  232. test("Works with NULL functions for reply: ");
  233. reader = redisReaderCreate();
  234. reader->fn = NULL;
  235. redisReaderFeed(reader,(char*)"+OK\r\n",5);
  236. ret = redisReaderGetReply(reader,&reply);
  237. test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS);
  238. redisReaderFree(reader);
  239. test("Works when a single newline (\\r\\n) covers two calls to feed: ");
  240. reader = redisReaderCreate();
  241. reader->fn = NULL;
  242. redisReaderFeed(reader,(char*)"+OK\r",4);
  243. ret = redisReaderGetReply(reader,&reply);
  244. assert(ret == REDIS_OK && reply == NULL);
  245. redisReaderFeed(reader,(char*)"\n",1);
  246. ret = redisReaderGetReply(reader,&reply);
  247. test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS);
  248. redisReaderFree(reader);
  249. test("Don't reset state after protocol error: ");
  250. reader = redisReaderCreate();
  251. reader->fn = NULL;
  252. redisReaderFeed(reader,(char*)"x",1);
  253. ret = redisReaderGetReply(reader,&reply);
  254. assert(ret == REDIS_ERR);
  255. ret = redisReaderGetReply(reader,&reply);
  256. test_cond(ret == REDIS_ERR && reply == NULL);
  257. redisReaderFree(reader);
  258. /* Regression test for issue #45 on GitHub. */
  259. test("Don't do empty allocation for empty multi bulk: ");
  260. reader = redisReaderCreate();
  261. redisReaderFeed(reader,(char*)"*0\r\n",4);
  262. ret = redisReaderGetReply(reader,&reply);
  263. test_cond(ret == REDIS_OK &&
  264. ((redisReply*)reply)->type == REDIS_REPLY_ARRAY &&
  265. ((redisReply*)reply)->elements == 0);
  266. freeReplyObject(reply);
  267. redisReaderFree(reader);
  268. }
  269. static void test_blocking_connection_errors(void) {
  270. redisContext *c;
  271. test("Returns error when host cannot be resolved: ");
  272. c = redisConnect((char*)"idontexist.local", 6379);
  273. test_cond(c->err == REDIS_ERR_OTHER &&
  274. (strcmp(c->errstr,"Name or service not known") == 0 ||
  275. strcmp(c->errstr,"Can't resolve: idontexist.local") == 0 ||
  276. strcmp(c->errstr,"nodename nor servname provided, or not known") == 0 ||
  277. strcmp(c->errstr,"No address associated with hostname") == 0 ||
  278. strcmp(c->errstr,"no address associated with name") == 0));
  279. redisFree(c);
  280. test("Returns error when the port is not open: ");
  281. c = redisConnect((char*)"localhost", 1);
  282. test_cond(c->err == REDIS_ERR_IO &&
  283. strcmp(c->errstr,"Connection refused") == 0);
  284. redisFree(c);
  285. test("Returns error when the unix socket path doesn't accept connections: ");
  286. c = redisConnectUnix((char*)"/tmp/idontexist.sock");
  287. test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */
  288. redisFree(c);
  289. }
  290. static void test_blocking_connection(struct config config) {
  291. redisContext *c;
  292. redisReply *reply;
  293. c = connect(config);
  294. test("Is able to deliver commands: ");
  295. reply = redisCommand(c,"PING");
  296. test_cond(reply->type == REDIS_REPLY_STATUS &&
  297. strcasecmp(reply->str,"pong") == 0)
  298. freeReplyObject(reply);
  299. test("Is a able to send commands verbatim: ");
  300. reply = redisCommand(c,"SET foo bar");
  301. test_cond (reply->type == REDIS_REPLY_STATUS &&
  302. strcasecmp(reply->str,"ok") == 0)
  303. freeReplyObject(reply);
  304. test("%%s String interpolation works: ");
  305. reply = redisCommand(c,"SET %s %s","foo","hello world");
  306. freeReplyObject(reply);
  307. reply = redisCommand(c,"GET foo");
  308. test_cond(reply->type == REDIS_REPLY_STRING &&
  309. strcmp(reply->str,"hello world") == 0);
  310. freeReplyObject(reply);
  311. test("%%b String interpolation works: ");
  312. reply = redisCommand(c,"SET %b %b","foo",(size_t)3,"hello\x00world",(size_t)11);
  313. freeReplyObject(reply);
  314. reply = redisCommand(c,"GET foo");
  315. test_cond(reply->type == REDIS_REPLY_STRING &&
  316. memcmp(reply->str,"hello\x00world",11) == 0)
  317. test("Binary reply length is correct: ");
  318. test_cond(reply->len == 11)
  319. freeReplyObject(reply);
  320. test("Can parse nil replies: ");
  321. reply = redisCommand(c,"GET nokey");
  322. test_cond(reply->type == REDIS_REPLY_NIL)
  323. freeReplyObject(reply);
  324. /* test 7 */
  325. test("Can parse integer replies: ");
  326. reply = redisCommand(c,"INCR mycounter");
  327. test_cond(reply->type == REDIS_REPLY_INTEGER && reply->integer == 1)
  328. freeReplyObject(reply);
  329. test("Can parse multi bulk replies: ");
  330. freeReplyObject(redisCommand(c,"LPUSH mylist foo"));
  331. freeReplyObject(redisCommand(c,"LPUSH mylist bar"));
  332. reply = redisCommand(c,"LRANGE mylist 0 -1");
  333. test_cond(reply->type == REDIS_REPLY_ARRAY &&
  334. reply->elements == 2 &&
  335. !memcmp(reply->element[0]->str,"bar",3) &&
  336. !memcmp(reply->element[1]->str,"foo",3))
  337. freeReplyObject(reply);
  338. /* m/e with multi bulk reply *before* other reply.
  339. * specifically test ordering of reply items to parse. */
  340. test("Can handle nested multi bulk replies: ");
  341. freeReplyObject(redisCommand(c,"MULTI"));
  342. freeReplyObject(redisCommand(c,"LRANGE mylist 0 -1"));
  343. freeReplyObject(redisCommand(c,"PING"));
  344. reply = (redisCommand(c,"EXEC"));
  345. test_cond(reply->type == REDIS_REPLY_ARRAY &&
  346. reply->elements == 2 &&
  347. reply->element[0]->type == REDIS_REPLY_ARRAY &&
  348. reply->element[0]->elements == 2 &&
  349. !memcmp(reply->element[0]->element[0]->str,"bar",3) &&
  350. !memcmp(reply->element[0]->element[1]->str,"foo",3) &&
  351. reply->element[1]->type == REDIS_REPLY_STATUS &&
  352. strcasecmp(reply->element[1]->str,"pong") == 0);
  353. freeReplyObject(reply);
  354. disconnect(c, 0);
  355. }
  356. static void test_blocking_io_errors(struct config config) {
  357. redisContext *c;
  358. redisReply *reply;
  359. void *_reply;
  360. int major, minor;
  361. /* Connect to target given by config. */
  362. c = connect(config);
  363. {
  364. /* Find out Redis version to determine the path for the next test */
  365. const char *field = "redis_version:";
  366. char *p, *eptr;
  367. reply = redisCommand(c,"INFO");
  368. p = strstr(reply->str,field);
  369. major = strtol(p+strlen(field),&eptr,10);
  370. p = eptr+1; /* char next to the first "." */
  371. minor = strtol(p,&eptr,10);
  372. freeReplyObject(reply);
  373. }
  374. test("Returns I/O error when the connection is lost: ");
  375. reply = redisCommand(c,"QUIT");
  376. if (major >= 2 && minor > 0) {
  377. /* > 2.0 returns OK on QUIT and read() should be issued once more
  378. * to know the descriptor is at EOF. */
  379. test_cond(strcasecmp(reply->str,"OK") == 0 &&
  380. redisGetReply(c,&_reply) == REDIS_ERR);
  381. freeReplyObject(reply);
  382. } else {
  383. test_cond(reply == NULL);
  384. }
  385. /* On 2.0, QUIT will cause the connection to be closed immediately and
  386. * the read(2) for the reply on QUIT will set the error to EOF.
  387. * On >2.0, QUIT will return with OK and another read(2) needed to be
  388. * issued to find out the socket was closed by the server. In both
  389. * conditions, the error will be set to EOF. */
  390. assert(c->err == REDIS_ERR_EOF &&
  391. strcmp(c->errstr,"Server closed the connection") == 0);
  392. redisFree(c);
  393. c = connect(config);
  394. test("Returns I/O error on socket timeout: ");
  395. struct timeval tv = { 0, 1000 };
  396. assert(redisSetTimeout(c,tv) == REDIS_OK);
  397. test_cond(redisGetReply(c,&_reply) == REDIS_ERR &&
  398. c->err == REDIS_ERR_IO && errno == EAGAIN);
  399. redisFree(c);
  400. }
  401. static void test_invalid_timeout_errors(struct config config) {
  402. redisContext *c;
  403. test("Set error when an invalid timeout usec value is given to redisConnectWithTimeout: ");
  404. config.tcp.timeout.tv_sec = 0;
  405. config.tcp.timeout.tv_usec = 10000001;
  406. c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout);
  407. test_cond(c->err == REDIS_ERR_IO);
  408. test("Set error when an invalid timeout sec value is given to redisConnectWithTimeout: ");
  409. config.tcp.timeout.tv_sec = (((LONG_MAX) - 999) / 1000) + 1;
  410. config.tcp.timeout.tv_usec = 0;
  411. c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout);
  412. test_cond(c->err == REDIS_ERR_IO);
  413. redisFree(c);
  414. }
  415. static void test_throughput(struct config config) {
  416. redisContext *c = connect(config);
  417. redisReply **replies;
  418. int i, num;
  419. long long t1, t2;
  420. test("Throughput:\n");
  421. for (i = 0; i < 500; i++)
  422. freeReplyObject(redisCommand(c,"LPUSH mylist foo"));
  423. num = 1000;
  424. replies = malloc(sizeof(redisReply*)*num);
  425. t1 = usec();
  426. for (i = 0; i < num; i++) {
  427. replies[i] = redisCommand(c,"PING");
  428. assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS);
  429. }
  430. t2 = usec();
  431. for (i = 0; i < num; i++) freeReplyObject(replies[i]);
  432. free(replies);
  433. printf("\t(%dx PING: %.3fs)\n", num, (t2-t1)/1000000.0);
  434. replies = malloc(sizeof(redisReply*)*num);
  435. t1 = usec();
  436. for (i = 0; i < num; i++) {
  437. replies[i] = redisCommand(c,"LRANGE mylist 0 499");
  438. assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY);
  439. assert(replies[i] != NULL && replies[i]->elements == 500);
  440. }
  441. t2 = usec();
  442. for (i = 0; i < num; i++) freeReplyObject(replies[i]);
  443. free(replies);
  444. printf("\t(%dx LRANGE with 500 elements: %.3fs)\n", num, (t2-t1)/1000000.0);
  445. num = 10000;
  446. replies = malloc(sizeof(redisReply*)*num);
  447. for (i = 0; i < num; i++)
  448. redisAppendCommand(c,"PING");
  449. t1 = usec();
  450. for (i = 0; i < num; i++) {
  451. assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);
  452. assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS);
  453. }
  454. t2 = usec();
  455. for (i = 0; i < num; i++) freeReplyObject(replies[i]);
  456. free(replies);
  457. printf("\t(%dx PING (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
  458. replies = malloc(sizeof(redisReply*)*num);
  459. for (i = 0; i < num; i++)
  460. redisAppendCommand(c,"LRANGE mylist 0 499");
  461. t1 = usec();
  462. for (i = 0; i < num; i++) {
  463. assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);
  464. assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY);
  465. assert(replies[i] != NULL && replies[i]->elements == 500);
  466. }
  467. t2 = usec();
  468. for (i = 0; i < num; i++) freeReplyObject(replies[i]);
  469. free(replies);
  470. printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
  471. disconnect(c, 0);
  472. }
  473. // static long __test_callback_flags = 0;
  474. // static void __test_callback(redisContext *c, void *privdata) {
  475. // ((void)c);
  476. // /* Shift to detect execution order */
  477. // __test_callback_flags <<= 8;
  478. // __test_callback_flags |= (long)privdata;
  479. // }
  480. //
  481. // static void __test_reply_callback(redisContext *c, redisReply *reply, void *privdata) {
  482. // ((void)c);
  483. // /* Shift to detect execution order */
  484. // __test_callback_flags <<= 8;
  485. // __test_callback_flags |= (long)privdata;
  486. // if (reply) freeReplyObject(reply);
  487. // }
  488. //
  489. // static redisContext *__connect_nonblock() {
  490. // /* Reset callback flags */
  491. // __test_callback_flags = 0;
  492. // return redisConnectNonBlock("127.0.0.1", port, NULL);
  493. // }
  494. //
  495. // static void test_nonblocking_connection() {
  496. // redisContext *c;
  497. // int wdone = 0;
  498. //
  499. // test("Calls command callback when command is issued: ");
  500. // c = __connect_nonblock();
  501. // redisSetCommandCallback(c,__test_callback,(void*)1);
  502. // redisCommand(c,"PING");
  503. // test_cond(__test_callback_flags == 1);
  504. // redisFree(c);
  505. //
  506. // test("Calls disconnect callback on redisDisconnect: ");
  507. // c = __connect_nonblock();
  508. // redisSetDisconnectCallback(c,__test_callback,(void*)2);
  509. // redisDisconnect(c);
  510. // test_cond(__test_callback_flags == 2);
  511. // redisFree(c);
  512. //
  513. // test("Calls disconnect callback and free callback on redisFree: ");
  514. // c = __connect_nonblock();
  515. // redisSetDisconnectCallback(c,__test_callback,(void*)2);
  516. // redisSetFreeCallback(c,__test_callback,(void*)4);
  517. // redisFree(c);
  518. // test_cond(__test_callback_flags == ((2 << 8) | 4));
  519. //
  520. // test("redisBufferWrite against empty write buffer: ");
  521. // c = __connect_nonblock();
  522. // test_cond(redisBufferWrite(c,&wdone) == REDIS_OK && wdone == 1);
  523. // redisFree(c);
  524. //
  525. // test("redisBufferWrite against not yet connected fd: ");
  526. // c = __connect_nonblock();
  527. // redisCommand(c,"PING");
  528. // test_cond(redisBufferWrite(c,NULL) == REDIS_ERR &&
  529. // strncmp(c->error,"write:",6) == 0);
  530. // redisFree(c);
  531. //
  532. // test("redisBufferWrite against closed fd: ");
  533. // c = __connect_nonblock();
  534. // redisCommand(c,"PING");
  535. // redisDisconnect(c);
  536. // test_cond(redisBufferWrite(c,NULL) == REDIS_ERR &&
  537. // strncmp(c->error,"write:",6) == 0);
  538. // redisFree(c);
  539. //
  540. // test("Process callbacks in the right sequence: ");
  541. // c = __connect_nonblock();
  542. // redisCommandWithCallback(c,__test_reply_callback,(void*)1,"PING");
  543. // redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING");
  544. // redisCommandWithCallback(c,__test_reply_callback,(void*)3,"PING");
  545. //
  546. // /* Write output buffer */
  547. // wdone = 0;
  548. // while(!wdone) {
  549. // usleep(500);
  550. // redisBufferWrite(c,&wdone);
  551. // }
  552. //
  553. // /* Read until at least one callback is executed (the 3 replies will
  554. // * arrive in a single packet, causing all callbacks to be executed in
  555. // * a single pass). */
  556. // while(__test_callback_flags == 0) {
  557. // assert(redisBufferRead(c) == REDIS_OK);
  558. // redisProcessCallbacks(c);
  559. // }
  560. // test_cond(__test_callback_flags == 0x010203);
  561. // redisFree(c);
  562. //
  563. // test("redisDisconnect executes pending callbacks with NULL reply: ");
  564. // c = __connect_nonblock();
  565. // redisSetDisconnectCallback(c,__test_callback,(void*)1);
  566. // redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING");
  567. // redisDisconnect(c);
  568. // test_cond(__test_callback_flags == 0x0201);
  569. // redisFree(c);
  570. // }
  571. int main(int argc, char **argv) {
  572. struct config cfg = {
  573. .tcp = {
  574. .host = "127.0.0.1",
  575. .port = 6379
  576. },
  577. .unix = {
  578. .path = "/tmp/redis.sock"
  579. }
  580. };
  581. int throughput = 1;
  582. int test_inherit_fd = 1;
  583. /* Ignore broken pipe signal (for I/O error tests). */
  584. signal(SIGPIPE, SIG_IGN);
  585. /* Parse command line options. */
  586. argv++; argc--;
  587. while (argc) {
  588. if (argc >= 2 && !strcmp(argv[0],"-h")) {
  589. argv++; argc--;
  590. cfg.tcp.host = argv[0];
  591. } else if (argc >= 2 && !strcmp(argv[0],"-p")) {
  592. argv++; argc--;
  593. cfg.tcp.port = atoi(argv[0]);
  594. } else if (argc >= 2 && !strcmp(argv[0],"-s")) {
  595. argv++; argc--;
  596. cfg.unix.path = argv[0];
  597. } else if (argc >= 1 && !strcmp(argv[0],"--skip-throughput")) {
  598. throughput = 0;
  599. } else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) {
  600. test_inherit_fd = 0;
  601. } else {
  602. fprintf(stderr, "Invalid argument: %s\n", argv[0]);
  603. exit(1);
  604. }
  605. argv++; argc--;
  606. }
  607. test_format_commands();
  608. test_reply_reader();
  609. test_blocking_connection_errors();
  610. printf("\nTesting against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port);
  611. cfg.type = CONN_TCP;
  612. test_blocking_connection(cfg);
  613. test_blocking_io_errors(cfg);
  614. test_invalid_timeout_errors(cfg);
  615. test_append_formatted_commands(cfg);
  616. if (throughput) test_throughput(cfg);
  617. printf("\nTesting against Unix socket connection (%s):\n", cfg.unix.path);
  618. cfg.type = CONN_UNIX;
  619. test_blocking_connection(cfg);
  620. test_blocking_io_errors(cfg);
  621. if (throughput) test_throughput(cfg);
  622. if (test_inherit_fd) {
  623. printf("\nTesting against inherited fd (%s):\n", cfg.unix.path);
  624. cfg.type = CONN_FD;
  625. test_blocking_connection(cfg);
  626. }
  627. if (fails) {
  628. printf("*** %d TESTS FAILED ***\n", fails);
  629. return 1;
  630. }
  631. printf("ALL TESTS PASSED\n");
  632. return 0;
  633. }