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