hiredis-client 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +5 -0
  3. data/ext/redis_client/hiredis/export.clang +2 -0
  4. data/ext/redis_client/hiredis/export.gcc +7 -0
  5. data/ext/redis_client/hiredis/extconf.rb +69 -0
  6. data/ext/redis_client/hiredis/hiredis_connection.c +708 -0
  7. data/ext/redis_client/hiredis/vendor/.gitignore +9 -0
  8. data/ext/redis_client/hiredis/vendor/.travis.yml +131 -0
  9. data/ext/redis_client/hiredis/vendor/CHANGELOG.md +364 -0
  10. data/ext/redis_client/hiredis/vendor/CMakeLists.txt +165 -0
  11. data/ext/redis_client/hiredis/vendor/COPYING +29 -0
  12. data/ext/redis_client/hiredis/vendor/Makefile +308 -0
  13. data/ext/redis_client/hiredis/vendor/README.md +664 -0
  14. data/ext/redis_client/hiredis/vendor/adapters/ae.h +130 -0
  15. data/ext/redis_client/hiredis/vendor/adapters/glib.h +156 -0
  16. data/ext/redis_client/hiredis/vendor/adapters/ivykis.h +84 -0
  17. data/ext/redis_client/hiredis/vendor/adapters/libev.h +179 -0
  18. data/ext/redis_client/hiredis/vendor/adapters/libevent.h +175 -0
  19. data/ext/redis_client/hiredis/vendor/adapters/libuv.h +117 -0
  20. data/ext/redis_client/hiredis/vendor/adapters/macosx.h +115 -0
  21. data/ext/redis_client/hiredis/vendor/adapters/qt.h +135 -0
  22. data/ext/redis_client/hiredis/vendor/alloc.c +86 -0
  23. data/ext/redis_client/hiredis/vendor/alloc.h +91 -0
  24. data/ext/redis_client/hiredis/vendor/appveyor.yml +24 -0
  25. data/ext/redis_client/hiredis/vendor/async.c +887 -0
  26. data/ext/redis_client/hiredis/vendor/async.h +147 -0
  27. data/ext/redis_client/hiredis/vendor/async_private.h +75 -0
  28. data/ext/redis_client/hiredis/vendor/dict.c +352 -0
  29. data/ext/redis_client/hiredis/vendor/dict.h +126 -0
  30. data/ext/redis_client/hiredis/vendor/fmacros.h +12 -0
  31. data/ext/redis_client/hiredis/vendor/hiredis-config.cmake.in +13 -0
  32. data/ext/redis_client/hiredis/vendor/hiredis.c +1174 -0
  33. data/ext/redis_client/hiredis/vendor/hiredis.h +336 -0
  34. data/ext/redis_client/hiredis/vendor/hiredis.pc.in +12 -0
  35. data/ext/redis_client/hiredis/vendor/hiredis_ssl-config.cmake.in +13 -0
  36. data/ext/redis_client/hiredis/vendor/hiredis_ssl.h +157 -0
  37. data/ext/redis_client/hiredis/vendor/hiredis_ssl.pc.in +12 -0
  38. data/ext/redis_client/hiredis/vendor/net.c +612 -0
  39. data/ext/redis_client/hiredis/vendor/net.h +56 -0
  40. data/ext/redis_client/hiredis/vendor/read.c +739 -0
  41. data/ext/redis_client/hiredis/vendor/read.h +129 -0
  42. data/ext/redis_client/hiredis/vendor/sds.c +1289 -0
  43. data/ext/redis_client/hiredis/vendor/sds.h +278 -0
  44. data/ext/redis_client/hiredis/vendor/sdsalloc.h +44 -0
  45. data/ext/redis_client/hiredis/vendor/sockcompat.c +248 -0
  46. data/ext/redis_client/hiredis/vendor/sockcompat.h +92 -0
  47. data/ext/redis_client/hiredis/vendor/ssl.c +544 -0
  48. data/ext/redis_client/hiredis/vendor/test.c +1401 -0
  49. data/ext/redis_client/hiredis/vendor/test.sh +78 -0
  50. data/ext/redis_client/hiredis/vendor/win32.h +56 -0
  51. data/hiredis-client.gemspec +33 -0
  52. data/lib/hiredis-client.rb +11 -0
  53. data/lib/redis_client/hiredis_connection.rb +94 -0
  54. metadata +114 -0
@@ -0,0 +1,1401 @@
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
+ test("Multi-bulk never overflows regardless of maxelements: ");
497
+ size_t bad_mbulk_len = (SIZE_MAX / sizeof(void *)) + 3;
498
+ char bad_mbulk_reply[100];
499
+ snprintf(bad_mbulk_reply, sizeof(bad_mbulk_reply), "*%llu\r\n+asdf\r\n",
500
+ (unsigned long long) bad_mbulk_len);
501
+
502
+ reader = redisReaderCreate();
503
+ reader->maxelements = 0; /* Don't rely on default limit */
504
+ redisReaderFeed(reader, bad_mbulk_reply, strlen(bad_mbulk_reply));
505
+ ret = redisReaderGetReply(reader,&reply);
506
+ test_cond(ret == REDIS_ERR && strcasecmp(reader->errstr, "Out of memory") == 0);
507
+ freeReplyObject(reply);
508
+ redisReaderFree(reader);
509
+
510
+ #if LLONG_MAX > SIZE_MAX
511
+ test("Set error when array > SIZE_MAX: ");
512
+ reader = redisReaderCreate();
513
+ redisReaderFeed(reader, "*9223372036854775807\r\n+asdf\r\n",29);
514
+ ret = redisReaderGetReply(reader,&reply);
515
+ test_cond(ret == REDIS_ERR &&
516
+ strcasecmp(reader->errstr,"Multi-bulk length out of range") == 0);
517
+ freeReplyObject(reply);
518
+ redisReaderFree(reader);
519
+
520
+ test("Set error when bulk > SIZE_MAX: ");
521
+ reader = redisReaderCreate();
522
+ redisReaderFeed(reader, "$9223372036854775807\r\nasdf\r\n",28);
523
+ ret = redisReaderGetReply(reader,&reply);
524
+ test_cond(ret == REDIS_ERR &&
525
+ strcasecmp(reader->errstr,"Bulk string length out of range") == 0);
526
+ freeReplyObject(reply);
527
+ redisReaderFree(reader);
528
+ #endif
529
+
530
+ test("Works with NULL functions for reply: ");
531
+ reader = redisReaderCreate();
532
+ reader->fn = NULL;
533
+ redisReaderFeed(reader,(char*)"+OK\r\n",5);
534
+ ret = redisReaderGetReply(reader,&reply);
535
+ test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS);
536
+ redisReaderFree(reader);
537
+
538
+ test("Works when a single newline (\\r\\n) covers two calls to feed: ");
539
+ reader = redisReaderCreate();
540
+ reader->fn = NULL;
541
+ redisReaderFeed(reader,(char*)"+OK\r",4);
542
+ ret = redisReaderGetReply(reader,&reply);
543
+ assert(ret == REDIS_OK && reply == NULL);
544
+ redisReaderFeed(reader,(char*)"\n",1);
545
+ ret = redisReaderGetReply(reader,&reply);
546
+ test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS);
547
+ redisReaderFree(reader);
548
+
549
+ test("Don't reset state after protocol error: ");
550
+ reader = redisReaderCreate();
551
+ reader->fn = NULL;
552
+ redisReaderFeed(reader,(char*)"x",1);
553
+ ret = redisReaderGetReply(reader,&reply);
554
+ assert(ret == REDIS_ERR);
555
+ ret = redisReaderGetReply(reader,&reply);
556
+ test_cond(ret == REDIS_ERR && reply == NULL);
557
+ redisReaderFree(reader);
558
+
559
+ /* Regression test for issue #45 on GitHub. */
560
+ test("Don't do empty allocation for empty multi bulk: ");
561
+ reader = redisReaderCreate();
562
+ redisReaderFeed(reader,(char*)"*0\r\n",4);
563
+ ret = redisReaderGetReply(reader,&reply);
564
+ test_cond(ret == REDIS_OK &&
565
+ ((redisReply*)reply)->type == REDIS_REPLY_ARRAY &&
566
+ ((redisReply*)reply)->elements == 0);
567
+ freeReplyObject(reply);
568
+ redisReaderFree(reader);
569
+
570
+ /* RESP3 verbatim strings (GitHub issue #802) */
571
+ test("Can parse RESP3 verbatim strings: ");
572
+ reader = redisReaderCreate();
573
+ redisReaderFeed(reader,(char*)"=10\r\ntxt:LOLWUT\r\n",17);
574
+ ret = redisReaderGetReply(reader,&reply);
575
+ test_cond(ret == REDIS_OK &&
576
+ ((redisReply*)reply)->type == REDIS_REPLY_VERB &&
577
+ !memcmp(((redisReply*)reply)->str,"LOLWUT", 6));
578
+ freeReplyObject(reply);
579
+ redisReaderFree(reader);
580
+
581
+ /* RESP3 push messages (Github issue #815) */
582
+ test("Can parse RESP3 push messages: ");
583
+ reader = redisReaderCreate();
584
+ redisReaderFeed(reader,(char*)">2\r\n$6\r\nLOLWUT\r\n:42\r\n",21);
585
+ ret = redisReaderGetReply(reader,&reply);
586
+ test_cond(ret == REDIS_OK &&
587
+ ((redisReply*)reply)->type == REDIS_REPLY_PUSH &&
588
+ ((redisReply*)reply)->elements == 2 &&
589
+ ((redisReply*)reply)->element[0]->type == REDIS_REPLY_STRING &&
590
+ !memcmp(((redisReply*)reply)->element[0]->str,"LOLWUT",6) &&
591
+ ((redisReply*)reply)->element[1]->type == REDIS_REPLY_INTEGER &&
592
+ ((redisReply*)reply)->element[1]->integer == 42);
593
+ freeReplyObject(reply);
594
+ redisReaderFree(reader);
595
+ }
596
+
597
+ static void test_free_null(void) {
598
+ void *redisCtx = NULL;
599
+ void *reply = NULL;
600
+
601
+ test("Don't fail when redisFree is passed a NULL value: ");
602
+ redisFree(redisCtx);
603
+ test_cond(redisCtx == NULL);
604
+
605
+ test("Don't fail when freeReplyObject is passed a NULL value: ");
606
+ freeReplyObject(reply);
607
+ test_cond(reply == NULL);
608
+ }
609
+
610
+ static void *hi_malloc_fail(size_t size) {
611
+ (void)size;
612
+ return NULL;
613
+ }
614
+
615
+ static void *hi_calloc_fail(size_t nmemb, size_t size) {
616
+ (void)nmemb;
617
+ (void)size;
618
+ return NULL;
619
+ }
620
+
621
+ static void *hi_realloc_fail(void *ptr, size_t size) {
622
+ (void)ptr;
623
+ (void)size;
624
+ return NULL;
625
+ }
626
+
627
+ static void test_allocator_injection(void) {
628
+ hiredisAllocFuncs ha = {
629
+ .mallocFn = hi_malloc_fail,
630
+ .callocFn = hi_calloc_fail,
631
+ .reallocFn = hi_realloc_fail,
632
+ .strdupFn = strdup,
633
+ .freeFn = free,
634
+ };
635
+
636
+ // Override hiredis allocators
637
+ hiredisSetAllocators(&ha);
638
+
639
+ test("redisContext uses injected allocators: ");
640
+ redisContext *c = redisConnect("localhost", 6379);
641
+ test_cond(c == NULL);
642
+
643
+ test("redisReader uses injected allocators: ");
644
+ redisReader *reader = redisReaderCreate();
645
+ test_cond(reader == NULL);
646
+
647
+ // Return allocators to default
648
+ hiredisResetAllocators();
649
+ }
650
+
651
+ #define HIREDIS_BAD_DOMAIN "idontexist-noreally.com"
652
+ static void test_blocking_connection_errors(void) {
653
+ redisContext *c;
654
+ struct addrinfo hints = {.ai_family = AF_INET};
655
+ struct addrinfo *ai_tmp = NULL;
656
+
657
+ int rv = getaddrinfo(HIREDIS_BAD_DOMAIN, "6379", &hints, &ai_tmp);
658
+ if (rv != 0) {
659
+ // Address does *not* exist
660
+ test("Returns error when host cannot be resolved: ");
661
+ // First see if this domain name *actually* resolves to NXDOMAIN
662
+ c = redisConnect(HIREDIS_BAD_DOMAIN, 6379);
663
+ test_cond(
664
+ c->err == REDIS_ERR_OTHER &&
665
+ (strcmp(c->errstr, "Name or service not known") == 0 ||
666
+ strcmp(c->errstr, "Can't resolve: " HIREDIS_BAD_DOMAIN) == 0 ||
667
+ strcmp(c->errstr, "Name does not resolve") == 0 ||
668
+ strcmp(c->errstr, "nodename nor servname provided, or not known") == 0 ||
669
+ strcmp(c->errstr, "No address associated with hostname") == 0 ||
670
+ strcmp(c->errstr, "Temporary failure in name resolution") == 0 ||
671
+ strcmp(c->errstr, "hostname nor servname provided, or not known") == 0 ||
672
+ strcmp(c->errstr, "no address associated with name") == 0 ||
673
+ strcmp(c->errstr, "No such host is known. ") == 0));
674
+ redisFree(c);
675
+ } else {
676
+ printf("Skipping NXDOMAIN test. Found evil ISP!\n");
677
+ freeaddrinfo(ai_tmp);
678
+ }
679
+
680
+ #ifndef _WIN32
681
+ test("Returns error when the port is not open: ");
682
+ c = redisConnect((char*)"localhost", 1);
683
+ test_cond(c->err == REDIS_ERR_IO &&
684
+ strcmp(c->errstr,"Connection refused") == 0);
685
+ redisFree(c);
686
+
687
+ test("Returns error when the unix_sock socket path doesn't accept connections: ");
688
+ c = redisConnectUnix((char*)"/tmp/idontexist.sock");
689
+ test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */
690
+ redisFree(c);
691
+ #endif
692
+ }
693
+
694
+ /* Dummy push handler */
695
+ void push_handler(void *privdata, void *reply) {
696
+ int *counter = privdata;
697
+ freeReplyObject(reply);
698
+ *counter += 1;
699
+ }
700
+
701
+ /* Dummy function just to test setting a callback with redisOptions */
702
+ void push_handler_async(redisAsyncContext *ac, void *reply) {
703
+ (void)ac;
704
+ (void)reply;
705
+ }
706
+
707
+ static void test_resp3_push_handler(redisContext *c) {
708
+ redisPushFn *old = NULL;
709
+ redisReply *reply;
710
+ void *privdata;
711
+ int n = 0;
712
+
713
+ /* Switch to RESP3 and turn on client tracking */
714
+ send_hello(c, 3);
715
+ send_client_tracking(c, "ON");
716
+ privdata = c->privdata;
717
+ c->privdata = &n;
718
+
719
+ reply = redisCommand(c, "GET key:0");
720
+ assert(reply != NULL);
721
+ freeReplyObject(reply);
722
+
723
+ test("RESP3 PUSH messages are handled out of band by default: ");
724
+ reply = redisCommand(c, "SET key:0 val:0");
725
+ test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS);
726
+ freeReplyObject(reply);
727
+
728
+ assert((reply = redisCommand(c, "GET key:0")) != NULL);
729
+ freeReplyObject(reply);
730
+
731
+ old = redisSetPushCallback(c, push_handler);
732
+ test("We can set a custom RESP3 PUSH handler: ");
733
+ reply = redisCommand(c, "SET key:0 val:0");
734
+ test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && n == 1);
735
+ freeReplyObject(reply);
736
+
737
+ /* Unset the push callback and generate an invalidate message making
738
+ * sure it is not handled out of band. */
739
+ test("With no handler, PUSH replies come in-band: ");
740
+ redisSetPushCallback(c, NULL);
741
+ assert((reply = redisCommand(c, "GET key:0")) != NULL);
742
+ freeReplyObject(reply);
743
+ assert((reply = redisCommand(c, "SET key:0 invalid")) != NULL);
744
+ test_cond(reply->type == REDIS_REPLY_PUSH);
745
+ freeReplyObject(reply);
746
+
747
+ test("With no PUSH handler, no replies are lost: ");
748
+ assert(redisGetReply(c, (void**)&reply) == REDIS_OK);
749
+ test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS);
750
+ freeReplyObject(reply);
751
+
752
+ /* Return to the originally set PUSH handler */
753
+ assert(old != NULL);
754
+ redisSetPushCallback(c, old);
755
+
756
+ /* Switch back to RESP2 and disable tracking */
757
+ c->privdata = privdata;
758
+ send_client_tracking(c, "OFF");
759
+ send_hello(c, 2);
760
+ }
761
+
762
+ redisOptions get_redis_tcp_options(struct config config) {
763
+ redisOptions options = {0};
764
+ REDIS_OPTIONS_SET_TCP(&options, config.tcp.host, config.tcp.port);
765
+ return options;
766
+ }
767
+
768
+ static void test_resp3_push_options(struct config config) {
769
+ redisAsyncContext *ac;
770
+ redisContext *c;
771
+ redisOptions options;
772
+
773
+ test("We set a default RESP3 handler for redisContext: ");
774
+ options = get_redis_tcp_options(config);
775
+ assert((c = redisConnectWithOptions(&options)) != NULL);
776
+ test_cond(c->push_cb != NULL);
777
+ redisFree(c);
778
+
779
+ test("We don't set a default RESP3 push handler for redisAsyncContext: ");
780
+ options = get_redis_tcp_options(config);
781
+ assert((ac = redisAsyncConnectWithOptions(&options)) != NULL);
782
+ test_cond(ac->c.push_cb == NULL);
783
+ redisAsyncFree(ac);
784
+
785
+ test("Our REDIS_OPT_NO_PUSH_AUTOFREE flag works: ");
786
+ options = get_redis_tcp_options(config);
787
+ options.options |= REDIS_OPT_NO_PUSH_AUTOFREE;
788
+ assert((c = redisConnectWithOptions(&options)) != NULL);
789
+ test_cond(c->push_cb == NULL);
790
+ redisFree(c);
791
+
792
+ test("We can use redisOptions to set a custom PUSH handler for redisContext: ");
793
+ options = get_redis_tcp_options(config);
794
+ options.push_cb = push_handler;
795
+ assert((c = redisConnectWithOptions(&options)) != NULL);
796
+ test_cond(c->push_cb == push_handler);
797
+ redisFree(c);
798
+
799
+ test("We can use redisOptions to set a custom PUSH handler for redisAsyncContext: ");
800
+ options = get_redis_tcp_options(config);
801
+ options.async_push_cb = push_handler_async;
802
+ assert((ac = redisAsyncConnectWithOptions(&options)) != NULL);
803
+ test_cond(ac->push_cb == push_handler_async);
804
+ redisAsyncFree(ac);
805
+ }
806
+
807
+ void free_privdata(void *privdata) {
808
+ struct privdata *data = privdata;
809
+ data->dtor_counter++;
810
+ }
811
+
812
+ static void test_privdata_hooks(struct config config) {
813
+ struct privdata data = {0};
814
+ redisOptions options;
815
+ redisContext *c;
816
+
817
+ test("We can use redisOptions to set privdata: ");
818
+ options = get_redis_tcp_options(config);
819
+ REDIS_OPTIONS_SET_PRIVDATA(&options, &data, free_privdata);
820
+ assert((c = redisConnectWithOptions(&options)) != NULL);
821
+ test_cond(c->privdata == &data);
822
+
823
+ test("Our privdata destructor fires when we free the context: ");
824
+ redisFree(c);
825
+ test_cond(data.dtor_counter == 1);
826
+ }
827
+
828
+ static void test_blocking_connection(struct config config) {
829
+ redisContext *c;
830
+ redisReply *reply;
831
+ int major;
832
+
833
+ c = do_connect(config);
834
+
835
+ test("Is able to deliver commands: ");
836
+ reply = redisCommand(c,"PING");
837
+ test_cond(reply->type == REDIS_REPLY_STATUS &&
838
+ strcasecmp(reply->str,"pong") == 0)
839
+ freeReplyObject(reply);
840
+
841
+ test("Is a able to send commands verbatim: ");
842
+ reply = redisCommand(c,"SET foo bar");
843
+ test_cond (reply->type == REDIS_REPLY_STATUS &&
844
+ strcasecmp(reply->str,"ok") == 0)
845
+ freeReplyObject(reply);
846
+
847
+ test("%%s String interpolation works: ");
848
+ reply = redisCommand(c,"SET %s %s","foo","hello world");
849
+ freeReplyObject(reply);
850
+ reply = redisCommand(c,"GET foo");
851
+ test_cond(reply->type == REDIS_REPLY_STRING &&
852
+ strcmp(reply->str,"hello world") == 0);
853
+ freeReplyObject(reply);
854
+
855
+ test("%%b String interpolation works: ");
856
+ reply = redisCommand(c,"SET %b %b","foo",(size_t)3,"hello\x00world",(size_t)11);
857
+ freeReplyObject(reply);
858
+ reply = redisCommand(c,"GET foo");
859
+ test_cond(reply->type == REDIS_REPLY_STRING &&
860
+ memcmp(reply->str,"hello\x00world",11) == 0)
861
+
862
+ test("Binary reply length is correct: ");
863
+ test_cond(reply->len == 11)
864
+ freeReplyObject(reply);
865
+
866
+ test("Can parse nil replies: ");
867
+ reply = redisCommand(c,"GET nokey");
868
+ test_cond(reply->type == REDIS_REPLY_NIL)
869
+ freeReplyObject(reply);
870
+
871
+ /* test 7 */
872
+ test("Can parse integer replies: ");
873
+ reply = redisCommand(c,"INCR mycounter");
874
+ test_cond(reply->type == REDIS_REPLY_INTEGER && reply->integer == 1)
875
+ freeReplyObject(reply);
876
+
877
+ test("Can parse multi bulk replies: ");
878
+ freeReplyObject(redisCommand(c,"LPUSH mylist foo"));
879
+ freeReplyObject(redisCommand(c,"LPUSH mylist bar"));
880
+ reply = redisCommand(c,"LRANGE mylist 0 -1");
881
+ test_cond(reply->type == REDIS_REPLY_ARRAY &&
882
+ reply->elements == 2 &&
883
+ !memcmp(reply->element[0]->str,"bar",3) &&
884
+ !memcmp(reply->element[1]->str,"foo",3))
885
+ freeReplyObject(reply);
886
+
887
+ /* m/e with multi bulk reply *before* other reply.
888
+ * specifically test ordering of reply items to parse. */
889
+ test("Can handle nested multi bulk replies: ");
890
+ freeReplyObject(redisCommand(c,"MULTI"));
891
+ freeReplyObject(redisCommand(c,"LRANGE mylist 0 -1"));
892
+ freeReplyObject(redisCommand(c,"PING"));
893
+ reply = (redisCommand(c,"EXEC"));
894
+ test_cond(reply->type == REDIS_REPLY_ARRAY &&
895
+ reply->elements == 2 &&
896
+ reply->element[0]->type == REDIS_REPLY_ARRAY &&
897
+ reply->element[0]->elements == 2 &&
898
+ !memcmp(reply->element[0]->element[0]->str,"bar",3) &&
899
+ !memcmp(reply->element[0]->element[1]->str,"foo",3) &&
900
+ reply->element[1]->type == REDIS_REPLY_STATUS &&
901
+ strcasecmp(reply->element[1]->str,"pong") == 0);
902
+ freeReplyObject(reply);
903
+
904
+ /* Make sure passing NULL to redisGetReply is safe */
905
+ test("Can pass NULL to redisGetReply: ");
906
+ assert(redisAppendCommand(c, "PING") == REDIS_OK);
907
+ test_cond(redisGetReply(c, NULL) == REDIS_OK);
908
+
909
+ get_redis_version(c, &major, NULL);
910
+ if (major >= 6) test_resp3_push_handler(c);
911
+ test_resp3_push_options(config);
912
+
913
+ test_privdata_hooks(config);
914
+
915
+ disconnect(c, 0);
916
+ }
917
+
918
+ /* Send DEBUG SLEEP 0 to detect if we have this command */
919
+ static int detect_debug_sleep(redisContext *c) {
920
+ int detected;
921
+ redisReply *reply = redisCommand(c, "DEBUG SLEEP 0\r\n");
922
+
923
+ if (reply == NULL || c->err) {
924
+ const char *cause = c->err ? c->errstr : "(none)";
925
+ fprintf(stderr, "Error testing for DEBUG SLEEP (Redis error: %s), exiting\n", cause);
926
+ exit(-1);
927
+ }
928
+
929
+ detected = reply->type == REDIS_REPLY_STATUS;
930
+ freeReplyObject(reply);
931
+
932
+ return detected;
933
+ }
934
+
935
+ static void test_blocking_connection_timeouts(struct config config) {
936
+ redisContext *c;
937
+ redisReply *reply;
938
+ ssize_t s;
939
+ const char *sleep_cmd = "DEBUG SLEEP 3\r\n";
940
+ struct timeval tv;
941
+
942
+ c = do_connect(config);
943
+ test("Successfully completes a command when the timeout is not exceeded: ");
944
+ reply = redisCommand(c,"SET foo fast");
945
+ freeReplyObject(reply);
946
+ tv.tv_sec = 0;
947
+ tv.tv_usec = 10000;
948
+ redisSetTimeout(c, tv);
949
+ reply = redisCommand(c, "GET foo");
950
+ test_cond(reply != NULL && reply->type == REDIS_REPLY_STRING && memcmp(reply->str, "fast", 4) == 0);
951
+ freeReplyObject(reply);
952
+ disconnect(c, 0);
953
+
954
+ c = do_connect(config);
955
+ test("Does not return a reply when the command times out: ");
956
+ if (detect_debug_sleep(c)) {
957
+ redisAppendFormattedCommand(c, sleep_cmd, strlen(sleep_cmd));
958
+ s = c->funcs->write(c);
959
+ tv.tv_sec = 0;
960
+ tv.tv_usec = 10000;
961
+ redisSetTimeout(c, tv);
962
+ reply = redisCommand(c, "GET foo");
963
+ #ifndef _WIN32
964
+ test_cond(s > 0 && reply == NULL && c->err == REDIS_ERR_IO &&
965
+ strcmp(c->errstr, "Resource temporarily unavailable") == 0);
966
+ #else
967
+ test_cond(s > 0 && reply == NULL && c->err == REDIS_ERR_TIMEOUT &&
968
+ strcmp(c->errstr, "recv timeout") == 0);
969
+ #endif
970
+ freeReplyObject(reply);
971
+ } else {
972
+ test_skipped();
973
+ }
974
+
975
+ test("Reconnect properly reconnects after a timeout: ");
976
+ do_reconnect(c, config);
977
+ reply = redisCommand(c, "PING");
978
+ test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
979
+ freeReplyObject(reply);
980
+
981
+ test("Reconnect properly uses owned parameters: ");
982
+ config.tcp.host = "foo";
983
+ config.unix_sock.path = "foo";
984
+ do_reconnect(c, config);
985
+ reply = redisCommand(c, "PING");
986
+ test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
987
+ freeReplyObject(reply);
988
+
989
+ disconnect(c, 0);
990
+ }
991
+
992
+ static void test_blocking_io_errors(struct config config) {
993
+ redisContext *c;
994
+ redisReply *reply;
995
+ void *_reply;
996
+ int major, minor;
997
+
998
+ /* Connect to target given by config. */
999
+ c = do_connect(config);
1000
+ get_redis_version(c, &major, &minor);
1001
+
1002
+ test("Returns I/O error when the connection is lost: ");
1003
+ reply = redisCommand(c,"QUIT");
1004
+ if (major > 2 || (major == 2 && minor > 0)) {
1005
+ /* > 2.0 returns OK on QUIT and read() should be issued once more
1006
+ * to know the descriptor is at EOF. */
1007
+ test_cond(strcasecmp(reply->str,"OK") == 0 &&
1008
+ redisGetReply(c,&_reply) == REDIS_ERR);
1009
+ freeReplyObject(reply);
1010
+ } else {
1011
+ test_cond(reply == NULL);
1012
+ }
1013
+
1014
+ #ifndef _WIN32
1015
+ /* On 2.0, QUIT will cause the connection to be closed immediately and
1016
+ * the read(2) for the reply on QUIT will set the error to EOF.
1017
+ * On >2.0, QUIT will return with OK and another read(2) needed to be
1018
+ * issued to find out the socket was closed by the server. In both
1019
+ * conditions, the error will be set to EOF. */
1020
+ assert(c->err == REDIS_ERR_EOF &&
1021
+ strcmp(c->errstr,"Server closed the connection") == 0);
1022
+ #endif
1023
+ redisFree(c);
1024
+
1025
+ c = do_connect(config);
1026
+ test("Returns I/O error on socket timeout: ");
1027
+ struct timeval tv = { 0, 1000 };
1028
+ assert(redisSetTimeout(c,tv) == REDIS_OK);
1029
+ int respcode = redisGetReply(c,&_reply);
1030
+ #ifndef _WIN32
1031
+ test_cond(respcode == REDIS_ERR && c->err == REDIS_ERR_IO && errno == EAGAIN);
1032
+ #else
1033
+ test_cond(respcode == REDIS_ERR && c->err == REDIS_ERR_TIMEOUT);
1034
+ #endif
1035
+ redisFree(c);
1036
+ }
1037
+
1038
+ static void test_invalid_timeout_errors(struct config config) {
1039
+ redisContext *c;
1040
+
1041
+ test("Set error when an invalid timeout usec value is given to redisConnectWithTimeout: ");
1042
+
1043
+ config.tcp.timeout.tv_sec = 0;
1044
+ config.tcp.timeout.tv_usec = 10000001;
1045
+
1046
+ c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout);
1047
+
1048
+ test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0);
1049
+ redisFree(c);
1050
+
1051
+ test("Set error when an invalid timeout sec value is given to redisConnectWithTimeout: ");
1052
+
1053
+ config.tcp.timeout.tv_sec = (((LONG_MAX) - 999) / 1000) + 1;
1054
+ config.tcp.timeout.tv_usec = 0;
1055
+
1056
+ c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout);
1057
+
1058
+ test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0);
1059
+ redisFree(c);
1060
+ }
1061
+
1062
+ /* Wrap malloc to abort on failure so OOM checks don't make the test logic
1063
+ * harder to follow. */
1064
+ void *hi_malloc_safe(size_t size) {
1065
+ void *ptr = hi_malloc(size);
1066
+ if (ptr == NULL) {
1067
+ fprintf(stderr, "Error: Out of memory\n");
1068
+ exit(-1);
1069
+ }
1070
+
1071
+ return ptr;
1072
+ }
1073
+
1074
+ static void test_throughput(struct config config) {
1075
+ redisContext *c = do_connect(config);
1076
+ redisReply **replies;
1077
+ int i, num;
1078
+ long long t1, t2;
1079
+
1080
+ test("Throughput:\n");
1081
+ for (i = 0; i < 500; i++)
1082
+ freeReplyObject(redisCommand(c,"LPUSH mylist foo"));
1083
+
1084
+ num = 1000;
1085
+ replies = hi_malloc_safe(sizeof(redisReply*)*num);
1086
+ t1 = usec();
1087
+ for (i = 0; i < num; i++) {
1088
+ replies[i] = redisCommand(c,"PING");
1089
+ assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS);
1090
+ }
1091
+ t2 = usec();
1092
+ for (i = 0; i < num; i++) freeReplyObject(replies[i]);
1093
+ hi_free(replies);
1094
+ printf("\t(%dx PING: %.3fs)\n", num, (t2-t1)/1000000.0);
1095
+
1096
+ replies = hi_malloc_safe(sizeof(redisReply*)*num);
1097
+ t1 = usec();
1098
+ for (i = 0; i < num; i++) {
1099
+ replies[i] = redisCommand(c,"LRANGE mylist 0 499");
1100
+ assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY);
1101
+ assert(replies[i] != NULL && replies[i]->elements == 500);
1102
+ }
1103
+ t2 = usec();
1104
+ for (i = 0; i < num; i++) freeReplyObject(replies[i]);
1105
+ hi_free(replies);
1106
+ printf("\t(%dx LRANGE with 500 elements: %.3fs)\n", num, (t2-t1)/1000000.0);
1107
+
1108
+ replies = hi_malloc_safe(sizeof(redisReply*)*num);
1109
+ t1 = usec();
1110
+ for (i = 0; i < num; i++) {
1111
+ replies[i] = redisCommand(c, "INCRBY incrkey %d", 1000000);
1112
+ assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_INTEGER);
1113
+ }
1114
+ t2 = usec();
1115
+ for (i = 0; i < num; i++) freeReplyObject(replies[i]);
1116
+ hi_free(replies);
1117
+ printf("\t(%dx INCRBY: %.3fs)\n", num, (t2-t1)/1000000.0);
1118
+
1119
+ num = 10000;
1120
+ replies = hi_malloc_safe(sizeof(redisReply*)*num);
1121
+ for (i = 0; i < num; i++)
1122
+ redisAppendCommand(c,"PING");
1123
+ t1 = usec();
1124
+ for (i = 0; i < num; i++) {
1125
+ assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);
1126
+ assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS);
1127
+ }
1128
+ t2 = usec();
1129
+ for (i = 0; i < num; i++) freeReplyObject(replies[i]);
1130
+ hi_free(replies);
1131
+ printf("\t(%dx PING (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,"LRANGE mylist 0 499");
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_ARRAY);
1140
+ assert(replies[i] != NULL && replies[i]->elements == 500);
1141
+ }
1142
+ t2 = usec();
1143
+ for (i = 0; i < num; i++) freeReplyObject(replies[i]);
1144
+ hi_free(replies);
1145
+ printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
1146
+
1147
+ replies = hi_malloc_safe(sizeof(redisReply*)*num);
1148
+ for (i = 0; i < num; i++)
1149
+ redisAppendCommand(c,"INCRBY incrkey %d", 1000000);
1150
+ t1 = usec();
1151
+ for (i = 0; i < num; i++) {
1152
+ assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);
1153
+ assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_INTEGER);
1154
+ }
1155
+ t2 = usec();
1156
+ for (i = 0; i < num; i++) freeReplyObject(replies[i]);
1157
+ hi_free(replies);
1158
+ printf("\t(%dx INCRBY (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
1159
+
1160
+ disconnect(c, 0);
1161
+ }
1162
+
1163
+ // static long __test_callback_flags = 0;
1164
+ // static void __test_callback(redisContext *c, void *privdata) {
1165
+ // ((void)c);
1166
+ // /* Shift to detect execution order */
1167
+ // __test_callback_flags <<= 8;
1168
+ // __test_callback_flags |= (long)privdata;
1169
+ // }
1170
+ //
1171
+ // static void __test_reply_callback(redisContext *c, redisReply *reply, void *privdata) {
1172
+ // ((void)c);
1173
+ // /* Shift to detect execution order */
1174
+ // __test_callback_flags <<= 8;
1175
+ // __test_callback_flags |= (long)privdata;
1176
+ // if (reply) freeReplyObject(reply);
1177
+ // }
1178
+ //
1179
+ // static redisContext *__connect_nonblock() {
1180
+ // /* Reset callback flags */
1181
+ // __test_callback_flags = 0;
1182
+ // return redisConnectNonBlock("127.0.0.1", port, NULL);
1183
+ // }
1184
+ //
1185
+ // static void test_nonblocking_connection() {
1186
+ // redisContext *c;
1187
+ // int wdone = 0;
1188
+ //
1189
+ // test("Calls command callback when command is issued: ");
1190
+ // c = __connect_nonblock();
1191
+ // redisSetCommandCallback(c,__test_callback,(void*)1);
1192
+ // redisCommand(c,"PING");
1193
+ // test_cond(__test_callback_flags == 1);
1194
+ // redisFree(c);
1195
+ //
1196
+ // test("Calls disconnect callback on redisDisconnect: ");
1197
+ // c = __connect_nonblock();
1198
+ // redisSetDisconnectCallback(c,__test_callback,(void*)2);
1199
+ // redisDisconnect(c);
1200
+ // test_cond(__test_callback_flags == 2);
1201
+ // redisFree(c);
1202
+ //
1203
+ // test("Calls disconnect callback and free callback on redisFree: ");
1204
+ // c = __connect_nonblock();
1205
+ // redisSetDisconnectCallback(c,__test_callback,(void*)2);
1206
+ // redisSetFreeCallback(c,__test_callback,(void*)4);
1207
+ // redisFree(c);
1208
+ // test_cond(__test_callback_flags == ((2 << 8) | 4));
1209
+ //
1210
+ // test("redisBufferWrite against empty write buffer: ");
1211
+ // c = __connect_nonblock();
1212
+ // test_cond(redisBufferWrite(c,&wdone) == REDIS_OK && wdone == 1);
1213
+ // redisFree(c);
1214
+ //
1215
+ // test("redisBufferWrite against not yet connected fd: ");
1216
+ // c = __connect_nonblock();
1217
+ // redisCommand(c,"PING");
1218
+ // test_cond(redisBufferWrite(c,NULL) == REDIS_ERR &&
1219
+ // strncmp(c->error,"write:",6) == 0);
1220
+ // redisFree(c);
1221
+ //
1222
+ // test("redisBufferWrite against closed fd: ");
1223
+ // c = __connect_nonblock();
1224
+ // redisCommand(c,"PING");
1225
+ // redisDisconnect(c);
1226
+ // test_cond(redisBufferWrite(c,NULL) == REDIS_ERR &&
1227
+ // strncmp(c->error,"write:",6) == 0);
1228
+ // redisFree(c);
1229
+ //
1230
+ // test("Process callbacks in the right sequence: ");
1231
+ // c = __connect_nonblock();
1232
+ // redisCommandWithCallback(c,__test_reply_callback,(void*)1,"PING");
1233
+ // redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING");
1234
+ // redisCommandWithCallback(c,__test_reply_callback,(void*)3,"PING");
1235
+ //
1236
+ // /* Write output buffer */
1237
+ // wdone = 0;
1238
+ // while(!wdone) {
1239
+ // usleep(500);
1240
+ // redisBufferWrite(c,&wdone);
1241
+ // }
1242
+ //
1243
+ // /* Read until at least one callback is executed (the 3 replies will
1244
+ // * arrive in a single packet, causing all callbacks to be executed in
1245
+ // * a single pass). */
1246
+ // while(__test_callback_flags == 0) {
1247
+ // assert(redisBufferRead(c) == REDIS_OK);
1248
+ // redisProcessCallbacks(c);
1249
+ // }
1250
+ // test_cond(__test_callback_flags == 0x010203);
1251
+ // redisFree(c);
1252
+ //
1253
+ // test("redisDisconnect executes pending callbacks with NULL reply: ");
1254
+ // c = __connect_nonblock();
1255
+ // redisSetDisconnectCallback(c,__test_callback,(void*)1);
1256
+ // redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING");
1257
+ // redisDisconnect(c);
1258
+ // test_cond(__test_callback_flags == 0x0201);
1259
+ // redisFree(c);
1260
+ // }
1261
+
1262
+ int main(int argc, char **argv) {
1263
+ struct config cfg = {
1264
+ .tcp = {
1265
+ .host = "127.0.0.1",
1266
+ .port = 6379
1267
+ },
1268
+ .unix_sock = {
1269
+ .path = "/tmp/redis.sock"
1270
+ }
1271
+ };
1272
+ int throughput = 1;
1273
+ int test_inherit_fd = 1;
1274
+ int skips_as_fails = 0;
1275
+ int test_unix_socket;
1276
+
1277
+ /* Parse command line options. */
1278
+ argv++; argc--;
1279
+ while (argc) {
1280
+ if (argc >= 2 && !strcmp(argv[0],"-h")) {
1281
+ argv++; argc--;
1282
+ cfg.tcp.host = argv[0];
1283
+ } else if (argc >= 2 && !strcmp(argv[0],"-p")) {
1284
+ argv++; argc--;
1285
+ cfg.tcp.port = atoi(argv[0]);
1286
+ } else if (argc >= 2 && !strcmp(argv[0],"-s")) {
1287
+ argv++; argc--;
1288
+ cfg.unix_sock.path = argv[0];
1289
+ } else if (argc >= 1 && !strcmp(argv[0],"--skip-throughput")) {
1290
+ throughput = 0;
1291
+ } else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) {
1292
+ test_inherit_fd = 0;
1293
+ } else if (argc >= 1 && !strcmp(argv[0],"--skips-as-fails")) {
1294
+ skips_as_fails = 1;
1295
+ #ifdef HIREDIS_TEST_SSL
1296
+ } else if (argc >= 2 && !strcmp(argv[0],"--ssl-port")) {
1297
+ argv++; argc--;
1298
+ cfg.ssl.port = atoi(argv[0]);
1299
+ } else if (argc >= 2 && !strcmp(argv[0],"--ssl-host")) {
1300
+ argv++; argc--;
1301
+ cfg.ssl.host = argv[0];
1302
+ } else if (argc >= 2 && !strcmp(argv[0],"--ssl-ca-cert")) {
1303
+ argv++; argc--;
1304
+ cfg.ssl.ca_cert = argv[0];
1305
+ } else if (argc >= 2 && !strcmp(argv[0],"--ssl-cert")) {
1306
+ argv++; argc--;
1307
+ cfg.ssl.cert = argv[0];
1308
+ } else if (argc >= 2 && !strcmp(argv[0],"--ssl-key")) {
1309
+ argv++; argc--;
1310
+ cfg.ssl.key = argv[0];
1311
+ #endif
1312
+ } else {
1313
+ fprintf(stderr, "Invalid argument: %s\n", argv[0]);
1314
+ exit(1);
1315
+ }
1316
+ argv++; argc--;
1317
+ }
1318
+
1319
+ #ifndef _WIN32
1320
+ /* Ignore broken pipe signal (for I/O error tests). */
1321
+ signal(SIGPIPE, SIG_IGN);
1322
+
1323
+ test_unix_socket = access(cfg.unix_sock.path, F_OK) == 0;
1324
+
1325
+ #else
1326
+ /* Unix sockets don't exist in Windows */
1327
+ test_unix_socket = 0;
1328
+ #endif
1329
+
1330
+ test_allocator_injection();
1331
+
1332
+ test_format_commands();
1333
+ test_reply_reader();
1334
+ test_blocking_connection_errors();
1335
+ test_free_null();
1336
+
1337
+ printf("\nTesting against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port);
1338
+ cfg.type = CONN_TCP;
1339
+ test_blocking_connection(cfg);
1340
+ test_blocking_connection_timeouts(cfg);
1341
+ test_blocking_io_errors(cfg);
1342
+ test_invalid_timeout_errors(cfg);
1343
+ test_append_formatted_commands(cfg);
1344
+ if (throughput) test_throughput(cfg);
1345
+
1346
+ printf("\nTesting against Unix socket connection (%s): ", cfg.unix_sock.path);
1347
+ if (test_unix_socket) {
1348
+ printf("\n");
1349
+ cfg.type = CONN_UNIX;
1350
+ test_blocking_connection(cfg);
1351
+ test_blocking_connection_timeouts(cfg);
1352
+ test_blocking_io_errors(cfg);
1353
+ if (throughput) test_throughput(cfg);
1354
+ } else {
1355
+ test_skipped();
1356
+ }
1357
+
1358
+ #ifdef HIREDIS_TEST_SSL
1359
+ if (cfg.ssl.port && cfg.ssl.host) {
1360
+
1361
+ redisInitOpenSSL();
1362
+ _ssl_ctx = redisCreateSSLContext(cfg.ssl.ca_cert, NULL, cfg.ssl.cert, cfg.ssl.key, NULL, NULL);
1363
+ assert(_ssl_ctx != NULL);
1364
+
1365
+ printf("\nTesting against SSL connection (%s:%d):\n", cfg.ssl.host, cfg.ssl.port);
1366
+ cfg.type = CONN_SSL;
1367
+
1368
+ test_blocking_connection(cfg);
1369
+ test_blocking_connection_timeouts(cfg);
1370
+ test_blocking_io_errors(cfg);
1371
+ test_invalid_timeout_errors(cfg);
1372
+ test_append_formatted_commands(cfg);
1373
+ if (throughput) test_throughput(cfg);
1374
+
1375
+ redisFreeSSLContext(_ssl_ctx);
1376
+ _ssl_ctx = NULL;
1377
+ }
1378
+ #endif
1379
+
1380
+ if (test_inherit_fd) {
1381
+ printf("\nTesting against inherited fd (%s): ", cfg.unix_sock.path);
1382
+ if (test_unix_socket) {
1383
+ printf("\n");
1384
+ cfg.type = CONN_FD;
1385
+ test_blocking_connection(cfg);
1386
+ } else {
1387
+ test_skipped();
1388
+ }
1389
+ }
1390
+
1391
+ if (fails || (skips_as_fails && skips)) {
1392
+ printf("*** %d TESTS FAILED ***\n", fails);
1393
+ if (skips) {
1394
+ printf("*** %d TESTS SKIPPED ***\n", skips);
1395
+ }
1396
+ return 1;
1397
+ }
1398
+
1399
+ printf("ALL TESTS PASSED (%d skipped)\n", skips);
1400
+ return 0;
1401
+ }