1 #include "fmacros.h"
2 #include "sockcompat.h"
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #ifndef _WIN32
7 #include <strings.h>
8 #include <sys/time.h>
9 #endif
10 #include <assert.h>
11 #include <signal.h>
12 #include <errno.h>
13 #include <limits.h>
14 
15 #include "hiredis.h"
16 #include "async.h"
17 #ifdef HIREDIS_TEST_SSL
18 #include "hiredis_ssl.h"
19 #endif
20 #include "net.h"
21 #include "win32.h"
22 
23 enum connection_type {
24     CONN_TCP,
25     CONN_UNIX,
26     CONN_FD,
27     CONN_SSL
28 };
29 
30 struct config {
31     enum connection_type type;
32 
33     struct {
34         const char *host;
35         int port;
36         struct timeval timeout;
37     } tcp;
38 
39     struct {
40         const char *path;
41     } unix_sock;
42 
43     struct {
44         const char *host;
45         int port;
46         const char *ca_cert;
47         const char *cert;
48         const char *key;
49     } ssl;
50 };
51 
52 struct privdata {
53     int dtor_counter;
54 };
55 
56 #ifdef HIREDIS_TEST_SSL
57 redisSSLContext *_ssl_ctx = NULL;
58 #endif
59 
60 /* The following lines make up our testing "framework" :) */
61 static int tests = 0, fails = 0, skips = 0;
62 #define test(_s) { printf("#%02d ", ++tests); printf(_s); }
63 #define test_cond(_c) if(_c) printf("\033[0;32mPASSED\033[0;0m\n"); else {printf("\033[0;31mFAILED\033[0;0m\n"); fails++;}
64 #define test_skipped() { printf("\033[01;33mSKIPPED\033[0;0m\n"); skips++; }
65 
usec(void)66 static long long usec(void) {
67 #ifndef _MSC_VER
68     struct timeval tv;
69     gettimeofday(&tv,NULL);
70     return (((long long)tv.tv_sec)*1000000)+tv.tv_usec;
71 #else
72     FILETIME ft;
73     GetSystemTimeAsFileTime(&ft);
74     return (((long long)ft.dwHighDateTime << 32) | ft.dwLowDateTime) / 10;
75 #endif
76 }
77 
78 /* The assert() calls below have side effects, so we need assert()
79  * even if we are compiling without asserts (-DNDEBUG). */
80 #ifdef NDEBUG
81 #undef assert
82 #define assert(e) (void)(e)
83 #endif
84 
85 /* Helper to extract Redis version information.  Aborts on any failure. */
86 #define REDIS_VERSION_FIELD "redis_version:"
get_redis_version(redisContext * c,int * majorptr,int * minorptr)87 void get_redis_version(redisContext *c, int *majorptr, int *minorptr) {
88     redisReply *reply;
89     char *eptr, *s, *e;
90     int major, minor;
91 
92     reply = redisCommand(c, "INFO");
93     if (reply == NULL || c->err || reply->type != REDIS_REPLY_STRING)
94         goto abort;
95     if ((s = strstr(reply->str, REDIS_VERSION_FIELD)) == NULL)
96         goto abort;
97 
98     s += strlen(REDIS_VERSION_FIELD);
99 
100     /* We need a field terminator and at least 'x.y.z' (5) bytes of data */
101     if ((e = strstr(s, "\r\n")) == NULL || (e - s) < 5)
102         goto abort;
103 
104     /* Extract version info */
105     major = strtol(s, &eptr, 10);
106     if (*eptr != '.') goto abort;
107     minor = strtol(eptr+1, NULL, 10);
108 
109     /* Push info the caller wants */
110     if (majorptr) *majorptr = major;
111     if (minorptr) *minorptr = minor;
112 
113     freeReplyObject(reply);
114     return;
115 
116 abort:
117     freeReplyObject(reply);
118     fprintf(stderr, "Error:  Cannot determine Redis version, aborting\n");
119     exit(1);
120 }
121 
select_database(redisContext * c)122 static redisContext *select_database(redisContext *c) {
123     redisReply *reply;
124 
125     /* Switch to DB 9 for testing, now that we know we can chat. */
126     reply = redisCommand(c,"SELECT 9");
127     assert(reply != NULL);
128     freeReplyObject(reply);
129 
130     /* Make sure the DB is emtpy */
131     reply = redisCommand(c,"DBSIZE");
132     assert(reply != NULL);
133     if (reply->type == REDIS_REPLY_INTEGER && reply->integer == 0) {
134         /* Awesome, DB 9 is empty and we can continue. */
135         freeReplyObject(reply);
136     } else {
137         printf("Database #9 is not empty, test can not continue\n");
138         exit(1);
139     }
140 
141     return c;
142 }
143 
144 /* Switch protocol */
send_hello(redisContext * c,int version)145 static void send_hello(redisContext *c, int version) {
146     redisReply *reply;
147     int expected;
148 
149     reply = redisCommand(c, "HELLO %d", version);
150     expected = version == 3 ? REDIS_REPLY_MAP : REDIS_REPLY_ARRAY;
151     assert(reply != NULL && reply->type == expected);
152     freeReplyObject(reply);
153 }
154 
155 /* Togggle client tracking */
send_client_tracking(redisContext * c,const char * str)156 static void send_client_tracking(redisContext *c, const char *str) {
157     redisReply *reply;
158 
159     reply = redisCommand(c, "CLIENT TRACKING %s", str);
160     assert(reply != NULL && reply->type == REDIS_REPLY_STATUS);
161     freeReplyObject(reply);
162 }
163 
disconnect(redisContext * c,int keep_fd)164 static int disconnect(redisContext *c, int keep_fd) {
165     redisReply *reply;
166 
167     /* Make sure we're on DB 9. */
168     reply = redisCommand(c,"SELECT 9");
169     assert(reply != NULL);
170     freeReplyObject(reply);
171     reply = redisCommand(c,"FLUSHDB");
172     assert(reply != NULL);
173     freeReplyObject(reply);
174 
175     /* Free the context as well, but keep the fd if requested. */
176     if (keep_fd)
177         return redisFreeKeepFd(c);
178     redisFree(c);
179     return -1;
180 }
181 
do_ssl_handshake(redisContext * c)182 static void do_ssl_handshake(redisContext *c) {
183 #ifdef HIREDIS_TEST_SSL
184     redisInitiateSSLWithContext(c, _ssl_ctx);
185     if (c->err) {
186         printf("SSL error: %s\n", c->errstr);
187         redisFree(c);
188         exit(1);
189     }
190 #else
191     (void) c;
192 #endif
193 }
194 
do_connect(struct config config)195 static redisContext *do_connect(struct config config) {
196     redisContext *c = NULL;
197 
198     if (config.type == CONN_TCP) {
199         c = redisConnect(config.tcp.host, config.tcp.port);
200     } else if (config.type == CONN_SSL) {
201         c = redisConnect(config.ssl.host, config.ssl.port);
202     } else if (config.type == CONN_UNIX) {
203         c = redisConnectUnix(config.unix_sock.path);
204     } else if (config.type == CONN_FD) {
205         /* Create a dummy connection just to get an fd to inherit */
206         redisContext *dummy_ctx = redisConnectUnix(config.unix_sock.path);
207         if (dummy_ctx) {
208             int fd = disconnect(dummy_ctx, 1);
209             printf("Connecting to inherited fd %d\n", fd);
210             c = redisConnectFd(fd);
211         }
212     } else {
213         assert(NULL);
214     }
215 
216     if (c == NULL) {
217         printf("Connection error: can't allocate redis context\n");
218         exit(1);
219     } else if (c->err) {
220         printf("Connection error: %s\n", c->errstr);
221         redisFree(c);
222         exit(1);
223     }
224 
225     if (config.type == CONN_SSL) {
226         do_ssl_handshake(c);
227     }
228 
229     return select_database(c);
230 }
231 
do_reconnect(redisContext * c,struct config config)232 static void do_reconnect(redisContext *c, struct config config) {
233     redisReconnect(c);
234 
235     if (config.type == CONN_SSL) {
236         do_ssl_handshake(c);
237     }
238 }
239 
test_format_commands(void)240 static void test_format_commands(void) {
241     char *cmd;
242     int len;
243 
244     test("Format command without interpolation: ");
245     len = redisFormatCommand(&cmd,"SET foo bar");
246     test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
247         len == 4+4+(3+2)+4+(3+2)+4+(3+2));
248     hi_free(cmd);
249 
250     test("Format command with %%s string interpolation: ");
251     len = redisFormatCommand(&cmd,"SET %s %s","foo","bar");
252     test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
253         len == 4+4+(3+2)+4+(3+2)+4+(3+2));
254     hi_free(cmd);
255 
256     test("Format command with %%s and an empty string: ");
257     len = redisFormatCommand(&cmd,"SET %s %s","foo","");
258     test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 &&
259         len == 4+4+(3+2)+4+(3+2)+4+(0+2));
260     hi_free(cmd);
261 
262     test("Format command with an empty string in between proper interpolations: ");
263     len = redisFormatCommand(&cmd,"SET %s %s","","foo");
264     test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$0\r\n\r\n$3\r\nfoo\r\n",len) == 0 &&
265         len == 4+4+(3+2)+4+(0+2)+4+(3+2));
266     hi_free(cmd);
267 
268     test("Format command with %%b string interpolation: ");
269     len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"b\0r",(size_t)3);
270     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 &&
271         len == 4+4+(3+2)+4+(3+2)+4+(3+2));
272     hi_free(cmd);
273 
274     test("Format command with %%b and an empty string: ");
275     len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"",(size_t)0);
276     test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 &&
277         len == 4+4+(3+2)+4+(3+2)+4+(0+2));
278     hi_free(cmd);
279 
280     test("Format command with literal %%: ");
281     len = redisFormatCommand(&cmd,"SET %% %%");
282     test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$1\r\n%\r\n$1\r\n%\r\n",len) == 0 &&
283         len == 4+4+(3+2)+4+(1+2)+4+(1+2));
284     hi_free(cmd);
285 
286     /* Vararg width depends on the type. These tests make sure that the
287      * width is correctly determined using the format and subsequent varargs
288      * can correctly be interpolated. */
289 #define INTEGER_WIDTH_TEST(fmt, type) do {                                                \
290     type value = 123;                                                                     \
291     test("Format command with printf-delegation (" #type "): ");                          \
292     len = redisFormatCommand(&cmd,"key:%08" fmt " str:%s", value, "hello");               \
293     test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:00000123\r\n$9\r\nstr:hello\r\n",len) == 0 && \
294         len == 4+5+(12+2)+4+(9+2));                                                       \
295     hi_free(cmd);                                                                         \
296 } while(0)
297 
298 #define FLOAT_WIDTH_TEST(type) do {                                                       \
299     type value = 123.0;                                                                   \
300     test("Format command with printf-delegation (" #type "): ");                          \
301     len = redisFormatCommand(&cmd,"key:%08.3f str:%s", value, "hello");                   \
302     test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:0123.000\r\n$9\r\nstr:hello\r\n",len) == 0 && \
303         len == 4+5+(12+2)+4+(9+2));                                                       \
304     hi_free(cmd);                                                                         \
305 } while(0)
306 
307     INTEGER_WIDTH_TEST("d", int);
308     INTEGER_WIDTH_TEST("hhd", char);
309     INTEGER_WIDTH_TEST("hd", short);
310     INTEGER_WIDTH_TEST("ld", long);
311     INTEGER_WIDTH_TEST("lld", long long);
312     INTEGER_WIDTH_TEST("u", unsigned int);
313     INTEGER_WIDTH_TEST("hhu", unsigned char);
314     INTEGER_WIDTH_TEST("hu", unsigned short);
315     INTEGER_WIDTH_TEST("lu", unsigned long);
316     INTEGER_WIDTH_TEST("llu", unsigned long long);
317     FLOAT_WIDTH_TEST(float);
318     FLOAT_WIDTH_TEST(double);
319 
320     test("Format command with invalid printf format: ");
321     len = redisFormatCommand(&cmd,"key:%08p %b",(void*)1234,"foo",(size_t)3);
322     test_cond(len == -1);
323 
324     const char *argv[3];
325     argv[0] = "SET";
326     argv[1] = "foo\0xxx";
327     argv[2] = "bar";
328     size_t lens[3] = { 3, 7, 3 };
329     int argc = 3;
330 
331     test("Format command by passing argc/argv without lengths: ");
332     len = redisFormatCommandArgv(&cmd,argc,argv,NULL);
333     test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
334         len == 4+4+(3+2)+4+(3+2)+4+(3+2));
335     hi_free(cmd);
336 
337     test("Format command by passing argc/argv with lengths: ");
338     len = redisFormatCommandArgv(&cmd,argc,argv,lens);
339     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 &&
340         len == 4+4+(3+2)+4+(7+2)+4+(3+2));
341     hi_free(cmd);
342 
343     sds sds_cmd;
344 
345     sds_cmd = NULL;
346     test("Format command into sds by passing argc/argv without lengths: ");
347     len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,NULL);
348     test_cond(strncmp(sds_cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
349         len == 4+4+(3+2)+4+(3+2)+4+(3+2));
350     sdsfree(sds_cmd);
351 
352     sds_cmd = NULL;
353     test("Format command into sds by passing argc/argv with lengths: ");
354     len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,lens);
355     test_cond(strncmp(sds_cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 &&
356         len == 4+4+(3+2)+4+(7+2)+4+(3+2));
357     sdsfree(sds_cmd);
358 }
359 
test_append_formatted_commands(struct config config)360 static void test_append_formatted_commands(struct config config) {
361     redisContext *c;
362     redisReply *reply;
363     char *cmd;
364     int len;
365 
366     c = do_connect(config);
367 
368     test("Append format command: ");
369 
370     len = redisFormatCommand(&cmd, "SET foo bar");
371 
372     test_cond(redisAppendFormattedCommand(c, cmd, len) == REDIS_OK);
373 
374     assert(redisGetReply(c, (void*)&reply) == REDIS_OK);
375 
376     hi_free(cmd);
377     freeReplyObject(reply);
378 
379     disconnect(c, 0);
380 }
381 
test_reply_reader(void)382 static void test_reply_reader(void) {
383     redisReader *reader;
384     void *reply, *root;
385     int ret;
386     int i;
387 
388     test("Error handling in reply parser: ");
389     reader = redisReaderCreate();
390     redisReaderFeed(reader,(char*)"@foo\r\n",6);
391     ret = redisReaderGetReply(reader,NULL);
392     test_cond(ret == REDIS_ERR &&
393               strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0);
394     redisReaderFree(reader);
395 
396     /* when the reply already contains multiple items, they must be free'd
397      * on an error. valgrind will bark when this doesn't happen. */
398     test("Memory cleanup in reply parser: ");
399     reader = redisReaderCreate();
400     redisReaderFeed(reader,(char*)"*2\r\n",4);
401     redisReaderFeed(reader,(char*)"$5\r\nhello\r\n",11);
402     redisReaderFeed(reader,(char*)"@foo\r\n",6);
403     ret = redisReaderGetReply(reader,NULL);
404     test_cond(ret == REDIS_ERR &&
405               strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0);
406     redisReaderFree(reader);
407 
408     reader = redisReaderCreate();
409     test("Can handle arbitrarily nested multi-bulks: ");
410     for (i = 0; i < 128; i++) {
411         redisReaderFeed(reader,(char*)"*1\r\n", 4);
412     }
413     redisReaderFeed(reader,(char*)"$6\r\nLOLWUT\r\n",12);
414     ret = redisReaderGetReply(reader,&reply);
415     root = reply; /* Keep track of the root reply */
416     test_cond(ret == REDIS_OK &&
417         ((redisReply*)reply)->type == REDIS_REPLY_ARRAY &&
418         ((redisReply*)reply)->elements == 1);
419 
420     test("Can parse arbitrarily nested multi-bulks correctly: ");
421     while(i--) {
422         assert(reply != NULL && ((redisReply*)reply)->type == REDIS_REPLY_ARRAY);
423         reply = ((redisReply*)reply)->element[0];
424     }
425     test_cond(((redisReply*)reply)->type == REDIS_REPLY_STRING &&
426         !memcmp(((redisReply*)reply)->str, "LOLWUT", 6));
427     freeReplyObject(root);
428     redisReaderFree(reader);
429 
430     test("Correctly parses LLONG_MAX: ");
431     reader = redisReaderCreate();
432     redisReaderFeed(reader, ":9223372036854775807\r\n",22);
433     ret = redisReaderGetReply(reader,&reply);
434     test_cond(ret == REDIS_OK &&
435             ((redisReply*)reply)->type == REDIS_REPLY_INTEGER &&
436             ((redisReply*)reply)->integer == LLONG_MAX);
437     freeReplyObject(reply);
438     redisReaderFree(reader);
439 
440     test("Set error when > LLONG_MAX: ");
441     reader = redisReaderCreate();
442     redisReaderFeed(reader, ":9223372036854775808\r\n",22);
443     ret = redisReaderGetReply(reader,&reply);
444     test_cond(ret == REDIS_ERR &&
445               strcasecmp(reader->errstr,"Bad integer value") == 0);
446     freeReplyObject(reply);
447     redisReaderFree(reader);
448 
449     test("Correctly parses LLONG_MIN: ");
450     reader = redisReaderCreate();
451     redisReaderFeed(reader, ":-9223372036854775808\r\n",23);
452     ret = redisReaderGetReply(reader,&reply);
453     test_cond(ret == REDIS_OK &&
454             ((redisReply*)reply)->type == REDIS_REPLY_INTEGER &&
455             ((redisReply*)reply)->integer == LLONG_MIN);
456     freeReplyObject(reply);
457     redisReaderFree(reader);
458 
459     test("Set error when < LLONG_MIN: ");
460     reader = redisReaderCreate();
461     redisReaderFeed(reader, ":-9223372036854775809\r\n",23);
462     ret = redisReaderGetReply(reader,&reply);
463     test_cond(ret == REDIS_ERR &&
464               strcasecmp(reader->errstr,"Bad integer value") == 0);
465     freeReplyObject(reply);
466     redisReaderFree(reader);
467 
468     test("Set error when array < -1: ");
469     reader = redisReaderCreate();
470     redisReaderFeed(reader, "*-2\r\n+asdf\r\n",12);
471     ret = redisReaderGetReply(reader,&reply);
472     test_cond(ret == REDIS_ERR &&
473               strcasecmp(reader->errstr,"Multi-bulk length out of range") == 0);
474     freeReplyObject(reply);
475     redisReaderFree(reader);
476 
477     test("Set error when bulk < -1: ");
478     reader = redisReaderCreate();
479     redisReaderFeed(reader, "$-2\r\nasdf\r\n",11);
480     ret = redisReaderGetReply(reader,&reply);
481     test_cond(ret == REDIS_ERR &&
482               strcasecmp(reader->errstr,"Bulk string length out of range") == 0);
483     freeReplyObject(reply);
484     redisReaderFree(reader);
485 
486     test("Can configure maximum multi-bulk elements: ");
487     reader = redisReaderCreate();
488     reader->maxelements = 1024;
489     redisReaderFeed(reader, "*1025\r\n", 7);
490     ret = redisReaderGetReply(reader,&reply);
491     test_cond(ret == REDIS_ERR &&
492               strcasecmp(reader->errstr, "Multi-bulk length out of range") == 0);
493     freeReplyObject(reply);
494     redisReaderFree(reader);
495 
496 #if LLONG_MAX > SIZE_MAX
497     test("Set error when array > SIZE_MAX: ");
498     reader = redisReaderCreate();
499     redisReaderFeed(reader, "*9223372036854775807\r\n+asdf\r\n",29);
500     ret = redisReaderGetReply(reader,&reply);
501     test_cond(ret == REDIS_ERR &&
502             strcasecmp(reader->errstr,"Multi-bulk length out of range") == 0);
503     freeReplyObject(reply);
504     redisReaderFree(reader);
505 
506     test("Set error when bulk > SIZE_MAX: ");
507     reader = redisReaderCreate();
508     redisReaderFeed(reader, "$9223372036854775807\r\nasdf\r\n",28);
509     ret = redisReaderGetReply(reader,&reply);
510     test_cond(ret == REDIS_ERR &&
511             strcasecmp(reader->errstr,"Bulk string length out of range") == 0);
512     freeReplyObject(reply);
513     redisReaderFree(reader);
514 #endif
515 
516     test("Works with NULL functions for reply: ");
517     reader = redisReaderCreate();
518     reader->fn = NULL;
519     redisReaderFeed(reader,(char*)"+OK\r\n",5);
520     ret = redisReaderGetReply(reader,&reply);
521     test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS);
522     redisReaderFree(reader);
523 
524     test("Works when a single newline (\\r\\n) covers two calls to feed: ");
525     reader = redisReaderCreate();
526     reader->fn = NULL;
527     redisReaderFeed(reader,(char*)"+OK\r",4);
528     ret = redisReaderGetReply(reader,&reply);
529     assert(ret == REDIS_OK && reply == NULL);
530     redisReaderFeed(reader,(char*)"\n",1);
531     ret = redisReaderGetReply(reader,&reply);
532     test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS);
533     redisReaderFree(reader);
534 
535     test("Don't reset state after protocol error: ");
536     reader = redisReaderCreate();
537     reader->fn = NULL;
538     redisReaderFeed(reader,(char*)"x",1);
539     ret = redisReaderGetReply(reader,&reply);
540     assert(ret == REDIS_ERR);
541     ret = redisReaderGetReply(reader,&reply);
542     test_cond(ret == REDIS_ERR && reply == NULL);
543     redisReaderFree(reader);
544 
545     /* Regression test for issue #45 on GitHub. */
546     test("Don't do empty allocation for empty multi bulk: ");
547     reader = redisReaderCreate();
548     redisReaderFeed(reader,(char*)"*0\r\n",4);
549     ret = redisReaderGetReply(reader,&reply);
550     test_cond(ret == REDIS_OK &&
551         ((redisReply*)reply)->type == REDIS_REPLY_ARRAY &&
552         ((redisReply*)reply)->elements == 0);
553     freeReplyObject(reply);
554     redisReaderFree(reader);
555 
556     /* RESP3 verbatim strings (GitHub issue #802) */
557     test("Can parse RESP3 verbatim strings: ");
558     reader = redisReaderCreate();
559     redisReaderFeed(reader,(char*)"=10\r\ntxt:LOLWUT\r\n",17);
560     ret = redisReaderGetReply(reader,&reply);
561     test_cond(ret == REDIS_OK &&
562         ((redisReply*)reply)->type == REDIS_REPLY_VERB &&
563          !memcmp(((redisReply*)reply)->str,"LOLWUT", 6));
564     freeReplyObject(reply);
565     redisReaderFree(reader);
566 
567     /* RESP3 push messages (Github issue #815) */
568     test("Can parse RESP3 push messages: ");
569     reader = redisReaderCreate();
570     redisReaderFeed(reader,(char*)">2\r\n$6\r\nLOLWUT\r\n:42\r\n",21);
571     ret = redisReaderGetReply(reader,&reply);
572     test_cond(ret == REDIS_OK &&
573         ((redisReply*)reply)->type == REDIS_REPLY_PUSH &&
574         ((redisReply*)reply)->elements == 2 &&
575         ((redisReply*)reply)->element[0]->type == REDIS_REPLY_STRING &&
576         !memcmp(((redisReply*)reply)->element[0]->str,"LOLWUT",6) &&
577         ((redisReply*)reply)->element[1]->type == REDIS_REPLY_INTEGER &&
578         ((redisReply*)reply)->element[1]->integer == 42);
579     freeReplyObject(reply);
580     redisReaderFree(reader);
581 }
582 
test_free_null(void)583 static void test_free_null(void) {
584     void *redisCtx = NULL;
585     void *reply = NULL;
586 
587     test("Don't fail when redisFree is passed a NULL value: ");
588     redisFree(redisCtx);
589     test_cond(redisCtx == NULL);
590 
591     test("Don't fail when freeReplyObject is passed a NULL value: ");
592     freeReplyObject(reply);
593     test_cond(reply == NULL);
594 }
595 
hi_malloc_fail(size_t size)596 static void *hi_malloc_fail(size_t size) {
597     (void)size;
598     return NULL;
599 }
600 
hi_calloc_fail(size_t nmemb,size_t size)601 static void *hi_calloc_fail(size_t nmemb, size_t size) {
602     (void)nmemb;
603     (void)size;
604     return NULL;
605 }
606 
hi_realloc_fail(void * ptr,size_t size)607 static void *hi_realloc_fail(void *ptr, size_t size) {
608     (void)ptr;
609     (void)size;
610     return NULL;
611 }
612 
test_allocator_injection(void)613 static void test_allocator_injection(void) {
614     hiredisAllocFuncs ha = {
615         .mallocFn = hi_malloc_fail,
616         .callocFn = hi_calloc_fail,
617         .reallocFn = hi_realloc_fail,
618         .strdupFn = strdup,
619         .freeFn = free,
620     };
621 
622     // Override hiredis allocators
623     hiredisSetAllocators(&ha);
624 
625     test("redisContext uses injected allocators: ");
626     redisContext *c = redisConnect("localhost", 6379);
627     test_cond(c == NULL);
628 
629     test("redisReader uses injected allocators: ");
630     redisReader *reader = redisReaderCreate();
631     test_cond(reader == NULL);
632 
633     // Return allocators to default
634     hiredisResetAllocators();
635 }
636 
637 #define HIREDIS_BAD_DOMAIN "idontexist-noreally.com"
test_blocking_connection_errors(void)638 static void test_blocking_connection_errors(void) {
639     redisContext *c;
640     struct addrinfo hints = {.ai_family = AF_INET};
641     struct addrinfo *ai_tmp = NULL;
642 
643     int rv = getaddrinfo(HIREDIS_BAD_DOMAIN, "6379", &hints, &ai_tmp);
644     if (rv != 0) {
645         // Address does *not* exist
646         test("Returns error when host cannot be resolved: ");
647         // First see if this domain name *actually* resolves to NXDOMAIN
648         c = redisConnect(HIREDIS_BAD_DOMAIN, 6379);
649         test_cond(
650             c->err == REDIS_ERR_OTHER &&
651             (strcmp(c->errstr, "Name or service not known") == 0 ||
652              strcmp(c->errstr, "Can't resolve: " HIREDIS_BAD_DOMAIN) == 0 ||
653              strcmp(c->errstr, "Name does not resolve") == 0 ||
654              strcmp(c->errstr, "nodename nor servname provided, or not known") == 0 ||
655              strcmp(c->errstr, "No address associated with hostname") == 0 ||
656              strcmp(c->errstr, "Temporary failure in name resolution") == 0 ||
657              strcmp(c->errstr, "hostname nor servname provided, or not known") == 0 ||
658              strcmp(c->errstr, "no address associated with name") == 0 ||
659              strcmp(c->errstr, "No such host is known. ") == 0));
660         redisFree(c);
661     } else {
662         printf("Skipping NXDOMAIN test. Found evil ISP!\n");
663         freeaddrinfo(ai_tmp);
664     }
665 
666 #ifndef _WIN32
667     test("Returns error when the port is not open: ");
668     c = redisConnect((char*)"localhost", 1);
669     test_cond(c->err == REDIS_ERR_IO &&
670         strcmp(c->errstr,"Connection refused") == 0);
671     redisFree(c);
672 
673     test("Returns error when the unix_sock socket path doesn't accept connections: ");
674     c = redisConnectUnix((char*)"/tmp/idontexist.sock");
675     test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */
676     redisFree(c);
677 #endif
678 }
679 
680 /* Dummy push handler */
push_handler(void * privdata,void * reply)681 void push_handler(void *privdata, void *reply) {
682     int *counter = privdata;
683     freeReplyObject(reply);
684     *counter += 1;
685 }
686 
687 /* Dummy function just to test setting a callback with redisOptions */
push_handler_async(redisAsyncContext * ac,void * reply)688 void push_handler_async(redisAsyncContext *ac, void *reply) {
689     (void)ac;
690     (void)reply;
691 }
692 
test_resp3_push_handler(redisContext * c)693 static void test_resp3_push_handler(redisContext *c) {
694     redisPushFn *old = NULL;
695     redisReply *reply;
696     void *privdata;
697     int n = 0;
698 
699     /* Switch to RESP3 and turn on client tracking */
700     send_hello(c, 3);
701     send_client_tracking(c, "ON");
702     privdata = c->privdata;
703     c->privdata = &n;
704 
705     reply = redisCommand(c, "GET key:0");
706     assert(reply != NULL);
707     freeReplyObject(reply);
708 
709     test("RESP3 PUSH messages are handled out of band by default: ");
710     reply = redisCommand(c, "SET key:0 val:0");
711     test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS);
712     freeReplyObject(reply);
713 
714     assert((reply = redisCommand(c, "GET key:0")) != NULL);
715     freeReplyObject(reply);
716 
717     old = redisSetPushCallback(c, push_handler);
718     test("We can set a custom RESP3 PUSH handler: ");
719     reply = redisCommand(c, "SET key:0 val:0");
720     test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && n == 1);
721     freeReplyObject(reply);
722 
723     /* Unset the push callback and generate an invalidate message making
724      * sure it is not handled out of band. */
725     test("With no handler, PUSH replies come in-band: ");
726     redisSetPushCallback(c, NULL);
727     assert((reply = redisCommand(c, "GET key:0")) != NULL);
728     freeReplyObject(reply);
729     assert((reply = redisCommand(c, "SET key:0 invalid")) != NULL);
730     test_cond(reply->type == REDIS_REPLY_PUSH);
731     freeReplyObject(reply);
732 
733     test("With no PUSH handler, no replies are lost: ");
734     assert(redisGetReply(c, (void**)&reply) == REDIS_OK);
735     test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS);
736     freeReplyObject(reply);
737 
738     /* Return to the originally set PUSH handler */
739     assert(old != NULL);
740     redisSetPushCallback(c, old);
741 
742     /* Switch back to RESP2 and disable tracking */
743     c->privdata = privdata;
744     send_client_tracking(c, "OFF");
745     send_hello(c, 2);
746 }
747 
get_redis_tcp_options(struct config config)748 redisOptions get_redis_tcp_options(struct config config) {
749     redisOptions options = {0};
750     REDIS_OPTIONS_SET_TCP(&options, config.tcp.host, config.tcp.port);
751     return options;
752 }
753 
test_resp3_push_options(struct config config)754 static void test_resp3_push_options(struct config config) {
755     redisAsyncContext *ac;
756     redisContext *c;
757     redisOptions options;
758 
759     test("We set a default RESP3 handler for redisContext: ");
760     options = get_redis_tcp_options(config);
761     assert((c = redisConnectWithOptions(&options)) != NULL);
762     test_cond(c->push_cb != NULL);
763     redisFree(c);
764 
765     test("We don't set a default RESP3 push handler for redisAsyncContext: ");
766     options = get_redis_tcp_options(config);
767     assert((ac = redisAsyncConnectWithOptions(&options)) != NULL);
768     test_cond(ac->c.push_cb == NULL);
769     redisAsyncFree(ac);
770 
771     test("Our REDIS_OPT_NO_PUSH_AUTOFREE flag works: ");
772     options = get_redis_tcp_options(config);
773     options.options |= REDIS_OPT_NO_PUSH_AUTOFREE;
774     assert((c = redisConnectWithOptions(&options)) != NULL);
775     test_cond(c->push_cb == NULL);
776     redisFree(c);
777 
778     test("We can use redisOptions to set a custom PUSH handler for redisContext: ");
779     options = get_redis_tcp_options(config);
780     options.push_cb = push_handler;
781     assert((c = redisConnectWithOptions(&options)) != NULL);
782     test_cond(c->push_cb == push_handler);
783     redisFree(c);
784 
785     test("We can use redisOptions to set a custom PUSH handler for redisAsyncContext: ");
786     options = get_redis_tcp_options(config);
787     options.async_push_cb = push_handler_async;
788     assert((ac = redisAsyncConnectWithOptions(&options)) != NULL);
789     test_cond(ac->push_cb == push_handler_async);
790     redisAsyncFree(ac);
791 }
792 
free_privdata(void * privdata)793 void free_privdata(void *privdata) {
794     struct privdata *data = privdata;
795     data->dtor_counter++;
796 }
797 
test_privdata_hooks(struct config config)798 static void test_privdata_hooks(struct config config) {
799     struct privdata data = {0};
800     redisOptions options;
801     redisContext *c;
802 
803     test("We can use redisOptions to set privdata: ");
804     options = get_redis_tcp_options(config);
805     REDIS_OPTIONS_SET_PRIVDATA(&options, &data, free_privdata);
806     assert((c = redisConnectWithOptions(&options)) != NULL);
807     test_cond(c->privdata == &data);
808 
809     test("Our privdata destructor fires when we free the context: ");
810     redisFree(c);
811     test_cond(data.dtor_counter == 1);
812 }
813 
test_blocking_connection(struct config config)814 static void test_blocking_connection(struct config config) {
815     redisContext *c;
816     redisReply *reply;
817     int major;
818 
819     c = do_connect(config);
820 
821     test("Is able to deliver commands: ");
822     reply = redisCommand(c,"PING");
823     test_cond(reply->type == REDIS_REPLY_STATUS &&
824         strcasecmp(reply->str,"pong") == 0)
825     freeReplyObject(reply);
826 
827     test("Is a able to send commands verbatim: ");
828     reply = redisCommand(c,"SET foo bar");
829     test_cond (reply->type == REDIS_REPLY_STATUS &&
830         strcasecmp(reply->str,"ok") == 0)
831     freeReplyObject(reply);
832 
833     test("%%s String interpolation works: ");
834     reply = redisCommand(c,"SET %s %s","foo","hello world");
835     freeReplyObject(reply);
836     reply = redisCommand(c,"GET foo");
837     test_cond(reply->type == REDIS_REPLY_STRING &&
838         strcmp(reply->str,"hello world") == 0);
839     freeReplyObject(reply);
840 
841     test("%%b String interpolation works: ");
842     reply = redisCommand(c,"SET %b %b","foo",(size_t)3,"hello\x00world",(size_t)11);
843     freeReplyObject(reply);
844     reply = redisCommand(c,"GET foo");
845     test_cond(reply->type == REDIS_REPLY_STRING &&
846         memcmp(reply->str,"hello\x00world",11) == 0)
847 
848     test("Binary reply length is correct: ");
849     test_cond(reply->len == 11)
850     freeReplyObject(reply);
851 
852     test("Can parse nil replies: ");
853     reply = redisCommand(c,"GET nokey");
854     test_cond(reply->type == REDIS_REPLY_NIL)
855     freeReplyObject(reply);
856 
857     /* test 7 */
858     test("Can parse integer replies: ");
859     reply = redisCommand(c,"INCR mycounter");
860     test_cond(reply->type == REDIS_REPLY_INTEGER && reply->integer == 1)
861     freeReplyObject(reply);
862 
863     test("Can parse multi bulk replies: ");
864     freeReplyObject(redisCommand(c,"LPUSH mylist foo"));
865     freeReplyObject(redisCommand(c,"LPUSH mylist bar"));
866     reply = redisCommand(c,"LRANGE mylist 0 -1");
867     test_cond(reply->type == REDIS_REPLY_ARRAY &&
868               reply->elements == 2 &&
869               !memcmp(reply->element[0]->str,"bar",3) &&
870               !memcmp(reply->element[1]->str,"foo",3))
871     freeReplyObject(reply);
872 
873     /* m/e with multi bulk reply *before* other reply.
874      * specifically test ordering of reply items to parse. */
875     test("Can handle nested multi bulk replies: ");
876     freeReplyObject(redisCommand(c,"MULTI"));
877     freeReplyObject(redisCommand(c,"LRANGE mylist 0 -1"));
878     freeReplyObject(redisCommand(c,"PING"));
879     reply = (redisCommand(c,"EXEC"));
880     test_cond(reply->type == REDIS_REPLY_ARRAY &&
881               reply->elements == 2 &&
882               reply->element[0]->type == REDIS_REPLY_ARRAY &&
883               reply->element[0]->elements == 2 &&
884               !memcmp(reply->element[0]->element[0]->str,"bar",3) &&
885               !memcmp(reply->element[0]->element[1]->str,"foo",3) &&
886               reply->element[1]->type == REDIS_REPLY_STATUS &&
887               strcasecmp(reply->element[1]->str,"pong") == 0);
888     freeReplyObject(reply);
889 
890     /* Make sure passing NULL to redisGetReply is safe */
891     test("Can pass NULL to redisGetReply: ");
892     assert(redisAppendCommand(c, "PING") == REDIS_OK);
893     test_cond(redisGetReply(c, NULL) == REDIS_OK);
894 
895     get_redis_version(c, &major, NULL);
896     if (major >= 6) test_resp3_push_handler(c);
897     test_resp3_push_options(config);
898 
899     test_privdata_hooks(config);
900 
901     disconnect(c, 0);
902 }
903 
904 /* Send DEBUG SLEEP 0 to detect if we have this command */
detect_debug_sleep(redisContext * c)905 static int detect_debug_sleep(redisContext *c) {
906     int detected;
907     redisReply *reply = redisCommand(c, "DEBUG SLEEP 0\r\n");
908 
909     if (reply == NULL || c->err) {
910         const char *cause = c->err ? c->errstr : "(none)";
911         fprintf(stderr, "Error testing for DEBUG SLEEP (Redis error: %s), exiting\n", cause);
912         exit(-1);
913     }
914 
915     detected = reply->type == REDIS_REPLY_STATUS;
916     freeReplyObject(reply);
917 
918     return detected;
919 }
920 
test_blocking_connection_timeouts(struct config config)921 static void test_blocking_connection_timeouts(struct config config) {
922     redisContext *c;
923     redisReply *reply;
924     ssize_t s;
925     const char *sleep_cmd = "DEBUG SLEEP 3\r\n";
926     struct timeval tv;
927 
928     c = do_connect(config);
929     test("Successfully completes a command when the timeout is not exceeded: ");
930     reply = redisCommand(c,"SET foo fast");
931     freeReplyObject(reply);
932     tv.tv_sec = 0;
933     tv.tv_usec = 10000;
934     redisSetTimeout(c, tv);
935     reply = redisCommand(c, "GET foo");
936     test_cond(reply != NULL && reply->type == REDIS_REPLY_STRING && memcmp(reply->str, "fast", 4) == 0);
937     freeReplyObject(reply);
938     disconnect(c, 0);
939 
940     c = do_connect(config);
941     test("Does not return a reply when the command times out: ");
942     if (detect_debug_sleep(c)) {
943         redisAppendFormattedCommand(c, sleep_cmd, strlen(sleep_cmd));
944         s = c->funcs->write(c);
945         tv.tv_sec = 0;
946         tv.tv_usec = 10000;
947         redisSetTimeout(c, tv);
948         reply = redisCommand(c, "GET foo");
949 #ifndef _WIN32
950         test_cond(s > 0 && reply == NULL && c->err == REDIS_ERR_IO &&
951                   strcmp(c->errstr, "Resource temporarily unavailable") == 0);
952 #else
953         test_cond(s > 0 && reply == NULL && c->err == REDIS_ERR_TIMEOUT &&
954                   strcmp(c->errstr, "recv timeout") == 0);
955 #endif
956         freeReplyObject(reply);
957     } else {
958         test_skipped();
959     }
960 
961     test("Reconnect properly reconnects after a timeout: ");
962     do_reconnect(c, config);
963     reply = redisCommand(c, "PING");
964     test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
965     freeReplyObject(reply);
966 
967     test("Reconnect properly uses owned parameters: ");
968     config.tcp.host = "foo";
969     config.unix_sock.path = "foo";
970     do_reconnect(c, config);
971     reply = redisCommand(c, "PING");
972     test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
973     freeReplyObject(reply);
974 
975     disconnect(c, 0);
976 }
977 
test_blocking_io_errors(struct config config)978 static void test_blocking_io_errors(struct config config) {
979     redisContext *c;
980     redisReply *reply;
981     void *_reply;
982     int major, minor;
983 
984     /* Connect to target given by config. */
985     c = do_connect(config);
986     get_redis_version(c, &major, &minor);
987 
988     test("Returns I/O error when the connection is lost: ");
989     reply = redisCommand(c,"QUIT");
990     if (major > 2 || (major == 2 && minor > 0)) {
991         /* > 2.0 returns OK on QUIT and read() should be issued once more
992          * to know the descriptor is at EOF. */
993         test_cond(strcasecmp(reply->str,"OK") == 0 &&
994             redisGetReply(c,&_reply) == REDIS_ERR);
995         freeReplyObject(reply);
996     } else {
997         test_cond(reply == NULL);
998     }
999 
1000 #ifndef _WIN32
1001     /* On 2.0, QUIT will cause the connection to be closed immediately and
1002      * the read(2) for the reply on QUIT will set the error to EOF.
1003      * On >2.0, QUIT will return with OK and another read(2) needed to be
1004      * issued to find out the socket was closed by the server. In both
1005      * conditions, the error will be set to EOF. */
1006     assert(c->err == REDIS_ERR_EOF &&
1007         strcmp(c->errstr,"Server closed the connection") == 0);
1008 #endif
1009     redisFree(c);
1010 
1011     c = do_connect(config);
1012     test("Returns I/O error on socket timeout: ");
1013     struct timeval tv = { 0, 1000 };
1014     assert(redisSetTimeout(c,tv) == REDIS_OK);
1015     int respcode = redisGetReply(c,&_reply);
1016 #ifndef _WIN32
1017     test_cond(respcode == REDIS_ERR && c->err == REDIS_ERR_IO && errno == EAGAIN);
1018 #else
1019     test_cond(respcode == REDIS_ERR && c->err == REDIS_ERR_TIMEOUT);
1020 #endif
1021     redisFree(c);
1022 }
1023 
test_invalid_timeout_errors(struct config config)1024 static void test_invalid_timeout_errors(struct config config) {
1025     redisContext *c;
1026 
1027     test("Set error when an invalid timeout usec value is given to redisConnectWithTimeout: ");
1028 
1029     config.tcp.timeout.tv_sec = 0;
1030     config.tcp.timeout.tv_usec = 10000001;
1031 
1032     c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout);
1033 
1034     test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0);
1035     redisFree(c);
1036 
1037     test("Set error when an invalid timeout sec value is given to redisConnectWithTimeout: ");
1038 
1039     config.tcp.timeout.tv_sec = (((LONG_MAX) - 999) / 1000) + 1;
1040     config.tcp.timeout.tv_usec = 0;
1041 
1042     c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout);
1043 
1044     test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0);
1045     redisFree(c);
1046 }
1047 
1048 /* Wrap malloc to abort on failure so OOM checks don't make the test logic
1049  * harder to follow. */
hi_malloc_safe(size_t size)1050 void *hi_malloc_safe(size_t size) {
1051     void *ptr = hi_malloc(size);
1052     if (ptr == NULL) {
1053         fprintf(stderr, "Error:  Out of memory\n");
1054         exit(-1);
1055     }
1056 
1057     return ptr;
1058 }
1059 
test_throughput(struct config config)1060 static void test_throughput(struct config config) {
1061     redisContext *c = do_connect(config);
1062     redisReply **replies;
1063     int i, num;
1064     long long t1, t2;
1065 
1066     test("Throughput:\n");
1067     for (i = 0; i < 500; i++)
1068         freeReplyObject(redisCommand(c,"LPUSH mylist foo"));
1069 
1070     num = 1000;
1071     replies = hi_malloc_safe(sizeof(redisReply*)*num);
1072     t1 = usec();
1073     for (i = 0; i < num; i++) {
1074         replies[i] = redisCommand(c,"PING");
1075         assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS);
1076     }
1077     t2 = usec();
1078     for (i = 0; i < num; i++) freeReplyObject(replies[i]);
1079     hi_free(replies);
1080     printf("\t(%dx PING: %.3fs)\n", num, (t2-t1)/1000000.0);
1081 
1082     replies = hi_malloc_safe(sizeof(redisReply*)*num);
1083     t1 = usec();
1084     for (i = 0; i < num; i++) {
1085         replies[i] = redisCommand(c,"LRANGE mylist 0 499");
1086         assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY);
1087         assert(replies[i] != NULL && replies[i]->elements == 500);
1088     }
1089     t2 = usec();
1090     for (i = 0; i < num; i++) freeReplyObject(replies[i]);
1091     hi_free(replies);
1092     printf("\t(%dx LRANGE with 500 elements: %.3fs)\n", num, (t2-t1)/1000000.0);
1093 
1094     replies = hi_malloc_safe(sizeof(redisReply*)*num);
1095     t1 = usec();
1096     for (i = 0; i < num; i++) {
1097         replies[i] = redisCommand(c, "INCRBY incrkey %d", 1000000);
1098         assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_INTEGER);
1099     }
1100     t2 = usec();
1101     for (i = 0; i < num; i++) freeReplyObject(replies[i]);
1102     hi_free(replies);
1103     printf("\t(%dx INCRBY: %.3fs)\n", num, (t2-t1)/1000000.0);
1104 
1105     num = 10000;
1106     replies = hi_malloc_safe(sizeof(redisReply*)*num);
1107     for (i = 0; i < num; i++)
1108         redisAppendCommand(c,"PING");
1109     t1 = usec();
1110     for (i = 0; i < num; i++) {
1111         assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);
1112         assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS);
1113     }
1114     t2 = usec();
1115     for (i = 0; i < num; i++) freeReplyObject(replies[i]);
1116     hi_free(replies);
1117     printf("\t(%dx PING (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
1118 
1119     replies = hi_malloc_safe(sizeof(redisReply*)*num);
1120     for (i = 0; i < num; i++)
1121         redisAppendCommand(c,"LRANGE mylist 0 499");
1122     t1 = usec();
1123     for (i = 0; i < num; i++) {
1124         assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);
1125         assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY);
1126         assert(replies[i] != NULL && replies[i]->elements == 500);
1127     }
1128     t2 = usec();
1129     for (i = 0; i < num; i++) freeReplyObject(replies[i]);
1130     hi_free(replies);
1131     printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
1132 
1133     replies = hi_malloc_safe(sizeof(redisReply*)*num);
1134     for (i = 0; i < num; i++)
1135         redisAppendCommand(c,"INCRBY incrkey %d", 1000000);
1136     t1 = usec();
1137     for (i = 0; i < num; i++) {
1138         assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);
1139         assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_INTEGER);
1140     }
1141     t2 = usec();
1142     for (i = 0; i < num; i++) freeReplyObject(replies[i]);
1143     hi_free(replies);
1144     printf("\t(%dx INCRBY (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
1145 
1146     disconnect(c, 0);
1147 }
1148 
1149 // static long __test_callback_flags = 0;
1150 // static void __test_callback(redisContext *c, void *privdata) {
1151 //     ((void)c);
1152 //     /* Shift to detect execution order */
1153 //     __test_callback_flags <<= 8;
1154 //     __test_callback_flags |= (long)privdata;
1155 // }
1156 //
1157 // static void __test_reply_callback(redisContext *c, redisReply *reply, void *privdata) {
1158 //     ((void)c);
1159 //     /* Shift to detect execution order */
1160 //     __test_callback_flags <<= 8;
1161 //     __test_callback_flags |= (long)privdata;
1162 //     if (reply) freeReplyObject(reply);
1163 // }
1164 //
1165 // static redisContext *__connect_nonblock() {
1166 //     /* Reset callback flags */
1167 //     __test_callback_flags = 0;
1168 //     return redisConnectNonBlock("127.0.0.1", port, NULL);
1169 // }
1170 //
1171 // static void test_nonblocking_connection() {
1172 //     redisContext *c;
1173 //     int wdone = 0;
1174 //
1175 //     test("Calls command callback when command is issued: ");
1176 //     c = __connect_nonblock();
1177 //     redisSetCommandCallback(c,__test_callback,(void*)1);
1178 //     redisCommand(c,"PING");
1179 //     test_cond(__test_callback_flags == 1);
1180 //     redisFree(c);
1181 //
1182 //     test("Calls disconnect callback on redisDisconnect: ");
1183 //     c = __connect_nonblock();
1184 //     redisSetDisconnectCallback(c,__test_callback,(void*)2);
1185 //     redisDisconnect(c);
1186 //     test_cond(__test_callback_flags == 2);
1187 //     redisFree(c);
1188 //
1189 //     test("Calls disconnect callback and free callback on redisFree: ");
1190 //     c = __connect_nonblock();
1191 //     redisSetDisconnectCallback(c,__test_callback,(void*)2);
1192 //     redisSetFreeCallback(c,__test_callback,(void*)4);
1193 //     redisFree(c);
1194 //     test_cond(__test_callback_flags == ((2 << 8) | 4));
1195 //
1196 //     test("redisBufferWrite against empty write buffer: ");
1197 //     c = __connect_nonblock();
1198 //     test_cond(redisBufferWrite(c,&wdone) == REDIS_OK && wdone == 1);
1199 //     redisFree(c);
1200 //
1201 //     test("redisBufferWrite against not yet connected fd: ");
1202 //     c = __connect_nonblock();
1203 //     redisCommand(c,"PING");
1204 //     test_cond(redisBufferWrite(c,NULL) == REDIS_ERR &&
1205 //               strncmp(c->error,"write:",6) == 0);
1206 //     redisFree(c);
1207 //
1208 //     test("redisBufferWrite against closed fd: ");
1209 //     c = __connect_nonblock();
1210 //     redisCommand(c,"PING");
1211 //     redisDisconnect(c);
1212 //     test_cond(redisBufferWrite(c,NULL) == REDIS_ERR &&
1213 //               strncmp(c->error,"write:",6) == 0);
1214 //     redisFree(c);
1215 //
1216 //     test("Process callbacks in the right sequence: ");
1217 //     c = __connect_nonblock();
1218 //     redisCommandWithCallback(c,__test_reply_callback,(void*)1,"PING");
1219 //     redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING");
1220 //     redisCommandWithCallback(c,__test_reply_callback,(void*)3,"PING");
1221 //
1222 //     /* Write output buffer */
1223 //     wdone = 0;
1224 //     while(!wdone) {
1225 //         usleep(500);
1226 //         redisBufferWrite(c,&wdone);
1227 //     }
1228 //
1229 //     /* Read until at least one callback is executed (the 3 replies will
1230 //      * arrive in a single packet, causing all callbacks to be executed in
1231 //      * a single pass). */
1232 //     while(__test_callback_flags == 0) {
1233 //         assert(redisBufferRead(c) == REDIS_OK);
1234 //         redisProcessCallbacks(c);
1235 //     }
1236 //     test_cond(__test_callback_flags == 0x010203);
1237 //     redisFree(c);
1238 //
1239 //     test("redisDisconnect executes pending callbacks with NULL reply: ");
1240 //     c = __connect_nonblock();
1241 //     redisSetDisconnectCallback(c,__test_callback,(void*)1);
1242 //     redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING");
1243 //     redisDisconnect(c);
1244 //     test_cond(__test_callback_flags == 0x0201);
1245 //     redisFree(c);
1246 // }
1247 
main(int argc,char ** argv)1248 int main(int argc, char **argv) {
1249     struct config cfg = {
1250         .tcp = {
1251             .host = "127.0.0.1",
1252             .port = 6379
1253         },
1254         .unix_sock = {
1255             .path = "/tmp/redis.sock"
1256         }
1257     };
1258     int throughput = 1;
1259     int test_inherit_fd = 1;
1260     int skips_as_fails = 0;
1261     int test_unix_socket;
1262 
1263     /* Parse command line options. */
1264     argv++; argc--;
1265     while (argc) {
1266         if (argc >= 2 && !strcmp(argv[0],"-h")) {
1267             argv++; argc--;
1268             cfg.tcp.host = argv[0];
1269         } else if (argc >= 2 && !strcmp(argv[0],"-p")) {
1270             argv++; argc--;
1271             cfg.tcp.port = atoi(argv[0]);
1272         } else if (argc >= 2 && !strcmp(argv[0],"-s")) {
1273             argv++; argc--;
1274             cfg.unix_sock.path = argv[0];
1275         } else if (argc >= 1 && !strcmp(argv[0],"--skip-throughput")) {
1276             throughput = 0;
1277         } else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) {
1278             test_inherit_fd = 0;
1279         } else if (argc >= 1 && !strcmp(argv[0],"--skips-as-fails")) {
1280             skips_as_fails = 1;
1281 #ifdef HIREDIS_TEST_SSL
1282         } else if (argc >= 2 && !strcmp(argv[0],"--ssl-port")) {
1283             argv++; argc--;
1284             cfg.ssl.port = atoi(argv[0]);
1285         } else if (argc >= 2 && !strcmp(argv[0],"--ssl-host")) {
1286             argv++; argc--;
1287             cfg.ssl.host = argv[0];
1288         } else if (argc >= 2 && !strcmp(argv[0],"--ssl-ca-cert")) {
1289             argv++; argc--;
1290             cfg.ssl.ca_cert  = argv[0];
1291         } else if (argc >= 2 && !strcmp(argv[0],"--ssl-cert")) {
1292             argv++; argc--;
1293             cfg.ssl.cert = argv[0];
1294         } else if (argc >= 2 && !strcmp(argv[0],"--ssl-key")) {
1295             argv++; argc--;
1296             cfg.ssl.key = argv[0];
1297 #endif
1298         } else {
1299             fprintf(stderr, "Invalid argument: %s\n", argv[0]);
1300             exit(1);
1301         }
1302         argv++; argc--;
1303     }
1304 
1305 #ifndef _WIN32
1306     /* Ignore broken pipe signal (for I/O error tests). */
1307     signal(SIGPIPE, SIG_IGN);
1308 
1309     test_unix_socket = access(cfg.unix_sock.path, F_OK) == 0;
1310 
1311 #else
1312     /* Unix sockets don't exist in Windows */
1313     test_unix_socket = 0;
1314 #endif
1315 
1316     test_allocator_injection();
1317 
1318     test_format_commands();
1319     test_reply_reader();
1320     test_blocking_connection_errors();
1321     test_free_null();
1322 
1323     printf("\nTesting against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port);
1324     cfg.type = CONN_TCP;
1325     test_blocking_connection(cfg);
1326     test_blocking_connection_timeouts(cfg);
1327     test_blocking_io_errors(cfg);
1328     test_invalid_timeout_errors(cfg);
1329     test_append_formatted_commands(cfg);
1330     if (throughput) test_throughput(cfg);
1331 
1332     printf("\nTesting against Unix socket connection (%s): ", cfg.unix_sock.path);
1333     if (test_unix_socket) {
1334         printf("\n");
1335         cfg.type = CONN_UNIX;
1336         test_blocking_connection(cfg);
1337         test_blocking_connection_timeouts(cfg);
1338         test_blocking_io_errors(cfg);
1339         if (throughput) test_throughput(cfg);
1340     } else {
1341         test_skipped();
1342     }
1343 
1344 #ifdef HIREDIS_TEST_SSL
1345     if (cfg.ssl.port && cfg.ssl.host) {
1346 
1347         redisInitOpenSSL();
1348         _ssl_ctx = redisCreateSSLContext(cfg.ssl.ca_cert, NULL, cfg.ssl.cert, cfg.ssl.key, NULL, NULL);
1349         assert(_ssl_ctx != NULL);
1350 
1351         printf("\nTesting against SSL connection (%s:%d):\n", cfg.ssl.host, cfg.ssl.port);
1352         cfg.type = CONN_SSL;
1353 
1354         test_blocking_connection(cfg);
1355         test_blocking_connection_timeouts(cfg);
1356         test_blocking_io_errors(cfg);
1357         test_invalid_timeout_errors(cfg);
1358         test_append_formatted_commands(cfg);
1359         if (throughput) test_throughput(cfg);
1360 
1361         redisFreeSSLContext(_ssl_ctx);
1362         _ssl_ctx = NULL;
1363     }
1364 #endif
1365 
1366     if (test_inherit_fd) {
1367         printf("\nTesting against inherited fd (%s): ", cfg.unix_sock.path);
1368         if (test_unix_socket) {
1369             printf("\n");
1370             cfg.type = CONN_FD;
1371             test_blocking_connection(cfg);
1372         } else {
1373             test_skipped();
1374         }
1375     }
1376 
1377     if (fails || (skips_as_fails && skips)) {
1378         printf("*** %d TESTS FAILED ***\n", fails);
1379         if (skips) {
1380             printf("*** %d TESTS SKIPPED ***\n", skips);
1381         }
1382         return 1;
1383     }
1384 
1385     printf("ALL TESTS PASSED (%d skipped)\n", skips);
1386     return 0;
1387 }
1388