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