hiredis-futureproof 0.6.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/COPYING +28 -0
  3. data/Rakefile +53 -0
  4. data/ext/hiredis_ext/connection.c +611 -0
  5. data/ext/hiredis_ext/extconf.rb +48 -0
  6. data/ext/hiredis_ext/hiredis_ext.c +15 -0
  7. data/ext/hiredis_ext/hiredis_ext.h +44 -0
  8. data/ext/hiredis_ext/reader.c +124 -0
  9. data/lib/hiredis/connection.rb +10 -0
  10. data/lib/hiredis/ext/connection.rb +29 -0
  11. data/lib/hiredis/ext/reader.rb +2 -0
  12. data/lib/hiredis/reader.rb +10 -0
  13. data/lib/hiredis/ruby/connection.rb +316 -0
  14. data/lib/hiredis/ruby/reader.rb +183 -0
  15. data/lib/hiredis/version.rb +3 -0
  16. data/lib/hiredis.rb +2 -0
  17. data/vendor/hiredis/COPYING +29 -0
  18. data/vendor/hiredis/Makefile +308 -0
  19. data/vendor/hiredis/alloc.c +86 -0
  20. data/vendor/hiredis/alloc.h +91 -0
  21. data/vendor/hiredis/async.c +892 -0
  22. data/vendor/hiredis/async.h +147 -0
  23. data/vendor/hiredis/async_private.h +75 -0
  24. data/vendor/hiredis/dict.c +352 -0
  25. data/vendor/hiredis/dict.h +126 -0
  26. data/vendor/hiredis/fmacros.h +12 -0
  27. data/vendor/hiredis/hiredis.c +1173 -0
  28. data/vendor/hiredis/hiredis.h +336 -0
  29. data/vendor/hiredis/hiredis_ssl.h +127 -0
  30. data/vendor/hiredis/net.c +612 -0
  31. data/vendor/hiredis/net.h +56 -0
  32. data/vendor/hiredis/read.c +739 -0
  33. data/vendor/hiredis/read.h +129 -0
  34. data/vendor/hiredis/sds.c +1289 -0
  35. data/vendor/hiredis/sds.h +278 -0
  36. data/vendor/hiredis/sdsalloc.h +44 -0
  37. data/vendor/hiredis/sockcompat.c +248 -0
  38. data/vendor/hiredis/sockcompat.h +92 -0
  39. data/vendor/hiredis/ssl.c +526 -0
  40. data/vendor/hiredis/test.c +1387 -0
  41. data/vendor/hiredis/win32.h +56 -0
  42. metadata +128 -0
@@ -0,0 +1,1387 @@
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
+
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:"
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
+
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 */
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 */
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
+
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
+
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
+
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
+
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
+
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
+
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
+
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
+
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
+
596
+ static void *hi_malloc_fail(size_t size) {
597
+ (void)size;
598
+ return NULL;
599
+ }
600
+
601
+ static void *hi_calloc_fail(size_t nmemb, size_t size) {
602
+ (void)nmemb;
603
+ (void)size;
604
+ return NULL;
605
+ }
606
+
607
+ static void *hi_realloc_fail(void *ptr, size_t size) {
608
+ (void)ptr;
609
+ (void)size;
610
+ return NULL;
611
+ }
612
+
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"
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 */
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 */
688
+ void push_handler_async(redisAsyncContext *ac, void *reply) {
689
+ (void)ac;
690
+ (void)reply;
691
+ }
692
+
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
+
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
+
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
+
793
+ void free_privdata(void *privdata) {
794
+ struct privdata *data = privdata;
795
+ data->dtor_counter++;
796
+ }
797
+
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
+
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 */
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
+
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
+
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
+
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. */
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
+
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
+
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
+ }