redis-client 0.2.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -0
  3. data/Gemfile +1 -2
  4. data/Gemfile.lock +2 -3
  5. data/README.md +66 -3
  6. data/Rakefile +43 -23
  7. data/lib/redis_client/command_builder.rb +83 -0
  8. data/lib/redis_client/config.rb +9 -48
  9. data/lib/redis_client/connection_mixin.rb +38 -0
  10. data/lib/redis_client/decorator.rb +84 -0
  11. data/lib/redis_client/pooled.rb +38 -30
  12. data/lib/redis_client/ruby_connection/buffered_io.rb +153 -0
  13. data/lib/redis_client/{resp3.rb → ruby_connection/resp3.rb} +0 -26
  14. data/lib/redis_client/{connection.rb → ruby_connection.rb} +26 -31
  15. data/lib/redis_client/version.rb +1 -1
  16. data/lib/redis_client.rb +162 -36
  17. data/redis-client.gemspec +2 -4
  18. metadata +12 -59
  19. data/.rubocop.yml +0 -190
  20. data/ext/redis_client/hiredis/export.clang +0 -2
  21. data/ext/redis_client/hiredis/export.gcc +0 -7
  22. data/ext/redis_client/hiredis/extconf.rb +0 -62
  23. data/ext/redis_client/hiredis/hiredis_connection.c +0 -708
  24. data/ext/redis_client/hiredis/vendor/.gitignore +0 -9
  25. data/ext/redis_client/hiredis/vendor/.travis.yml +0 -131
  26. data/ext/redis_client/hiredis/vendor/CHANGELOG.md +0 -364
  27. data/ext/redis_client/hiredis/vendor/CMakeLists.txt +0 -165
  28. data/ext/redis_client/hiredis/vendor/COPYING +0 -29
  29. data/ext/redis_client/hiredis/vendor/Makefile +0 -308
  30. data/ext/redis_client/hiredis/vendor/README.md +0 -664
  31. data/ext/redis_client/hiredis/vendor/adapters/ae.h +0 -130
  32. data/ext/redis_client/hiredis/vendor/adapters/glib.h +0 -156
  33. data/ext/redis_client/hiredis/vendor/adapters/ivykis.h +0 -84
  34. data/ext/redis_client/hiredis/vendor/adapters/libev.h +0 -179
  35. data/ext/redis_client/hiredis/vendor/adapters/libevent.h +0 -175
  36. data/ext/redis_client/hiredis/vendor/adapters/libuv.h +0 -117
  37. data/ext/redis_client/hiredis/vendor/adapters/macosx.h +0 -115
  38. data/ext/redis_client/hiredis/vendor/adapters/qt.h +0 -135
  39. data/ext/redis_client/hiredis/vendor/alloc.c +0 -86
  40. data/ext/redis_client/hiredis/vendor/alloc.h +0 -91
  41. data/ext/redis_client/hiredis/vendor/appveyor.yml +0 -24
  42. data/ext/redis_client/hiredis/vendor/async.c +0 -887
  43. data/ext/redis_client/hiredis/vendor/async.h +0 -147
  44. data/ext/redis_client/hiredis/vendor/async_private.h +0 -75
  45. data/ext/redis_client/hiredis/vendor/dict.c +0 -352
  46. data/ext/redis_client/hiredis/vendor/dict.h +0 -126
  47. data/ext/redis_client/hiredis/vendor/fmacros.h +0 -12
  48. data/ext/redis_client/hiredis/vendor/hiredis-config.cmake.in +0 -13
  49. data/ext/redis_client/hiredis/vendor/hiredis.c +0 -1174
  50. data/ext/redis_client/hiredis/vendor/hiredis.h +0 -336
  51. data/ext/redis_client/hiredis/vendor/hiredis.pc.in +0 -12
  52. data/ext/redis_client/hiredis/vendor/hiredis_ssl-config.cmake.in +0 -13
  53. data/ext/redis_client/hiredis/vendor/hiredis_ssl.h +0 -157
  54. data/ext/redis_client/hiredis/vendor/hiredis_ssl.pc.in +0 -12
  55. data/ext/redis_client/hiredis/vendor/net.c +0 -612
  56. data/ext/redis_client/hiredis/vendor/net.h +0 -56
  57. data/ext/redis_client/hiredis/vendor/read.c +0 -739
  58. data/ext/redis_client/hiredis/vendor/read.h +0 -129
  59. data/ext/redis_client/hiredis/vendor/sds.c +0 -1289
  60. data/ext/redis_client/hiredis/vendor/sds.h +0 -278
  61. data/ext/redis_client/hiredis/vendor/sdsalloc.h +0 -44
  62. data/ext/redis_client/hiredis/vendor/sockcompat.c +0 -248
  63. data/ext/redis_client/hiredis/vendor/sockcompat.h +0 -92
  64. data/ext/redis_client/hiredis/vendor/ssl.c +0 -544
  65. data/ext/redis_client/hiredis/vendor/test.c +0 -1401
  66. data/ext/redis_client/hiredis/vendor/test.sh +0 -78
  67. data/ext/redis_client/hiredis/vendor/win32.h +0 -56
  68. data/lib/redis_client/buffered_io.rb +0 -151
  69. data/lib/redis_client/hiredis_connection.rb +0 -80
@@ -1,1401 +0,0 @@
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
- }