rugged-redis 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,823 @@
1
+ #include "fmacros.h"
2
+ #include <stdio.h>
3
+ #include <stdlib.h>
4
+ #include <string.h>
5
+ #include <strings.h>
6
+ #include <sys/time.h>
7
+ #include <assert.h>
8
+ #include <unistd.h>
9
+ #include <signal.h>
10
+ #include <errno.h>
11
+ #include <limits.h>
12
+
13
+ #include "hiredis.h"
14
+ #include "net.h"
15
+
16
+ enum connection_type {
17
+ CONN_TCP,
18
+ CONN_UNIX,
19
+ CONN_FD
20
+ };
21
+
22
+ struct config {
23
+ enum connection_type type;
24
+
25
+ struct {
26
+ const char *host;
27
+ int port;
28
+ struct timeval timeout;
29
+ } tcp;
30
+
31
+ struct {
32
+ const char *path;
33
+ } unix_sock;
34
+ };
35
+
36
+ /* The following lines make up our testing "framework" :) */
37
+ static int tests = 0, fails = 0;
38
+ #define test(_s) { printf("#%02d ", ++tests); printf(_s); }
39
+ #define test_cond(_c) if(_c) printf("\033[0;32mPASSED\033[0;0m\n"); else {printf("\033[0;31mFAILED\033[0;0m\n"); fails++;}
40
+
41
+ static long long usec(void) {
42
+ struct timeval tv;
43
+ gettimeofday(&tv,NULL);
44
+ return (((long long)tv.tv_sec)*1000000)+tv.tv_usec;
45
+ }
46
+
47
+ /* The assert() calls below have side effects, so we need assert()
48
+ * even if we are compiling without asserts (-DNDEBUG). */
49
+ #ifdef NDEBUG
50
+ #undef assert
51
+ #define assert(e) (void)(e)
52
+ #endif
53
+
54
+ static redisContext *select_database(redisContext *c) {
55
+ redisReply *reply;
56
+
57
+ /* Switch to DB 9 for testing, now that we know we can chat. */
58
+ reply = redisCommand(c,"SELECT 9");
59
+ assert(reply != NULL);
60
+ freeReplyObject(reply);
61
+
62
+ /* Make sure the DB is emtpy */
63
+ reply = redisCommand(c,"DBSIZE");
64
+ assert(reply != NULL);
65
+ if (reply->type == REDIS_REPLY_INTEGER && reply->integer == 0) {
66
+ /* Awesome, DB 9 is empty and we can continue. */
67
+ freeReplyObject(reply);
68
+ } else {
69
+ printf("Database #9 is not empty, test can not continue\n");
70
+ exit(1);
71
+ }
72
+
73
+ return c;
74
+ }
75
+
76
+ static int disconnect(redisContext *c, int keep_fd) {
77
+ redisReply *reply;
78
+
79
+ /* Make sure we're on DB 9. */
80
+ reply = redisCommand(c,"SELECT 9");
81
+ assert(reply != NULL);
82
+ freeReplyObject(reply);
83
+ reply = redisCommand(c,"FLUSHDB");
84
+ assert(reply != NULL);
85
+ freeReplyObject(reply);
86
+
87
+ /* Free the context as well, but keep the fd if requested. */
88
+ if (keep_fd)
89
+ return redisFreeKeepFd(c);
90
+ redisFree(c);
91
+ return -1;
92
+ }
93
+
94
+ static redisContext *connect(struct config config) {
95
+ redisContext *c = NULL;
96
+
97
+ if (config.type == CONN_TCP) {
98
+ c = redisConnect(config.tcp.host, config.tcp.port);
99
+ } else if (config.type == CONN_UNIX) {
100
+ c = redisConnectUnix(config.unix_sock.path);
101
+ } else if (config.type == CONN_FD) {
102
+ /* Create a dummy connection just to get an fd to inherit */
103
+ redisContext *dummy_ctx = redisConnectUnix(config.unix_sock.path);
104
+ if (dummy_ctx) {
105
+ int fd = disconnect(dummy_ctx, 1);
106
+ printf("Connecting to inherited fd %d\n", fd);
107
+ c = redisConnectFd(fd);
108
+ }
109
+ } else {
110
+ assert(NULL);
111
+ }
112
+
113
+ if (c == NULL) {
114
+ printf("Connection error: can't allocate redis context\n");
115
+ exit(1);
116
+ } else if (c->err) {
117
+ printf("Connection error: %s\n", c->errstr);
118
+ redisFree(c);
119
+ exit(1);
120
+ }
121
+
122
+ return select_database(c);
123
+ }
124
+
125
+ static void test_format_commands(void) {
126
+ char *cmd;
127
+ int len;
128
+
129
+ test("Format command without interpolation: ");
130
+ len = redisFormatCommand(&cmd,"SET foo bar");
131
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
132
+ len == 4+4+(3+2)+4+(3+2)+4+(3+2));
133
+ free(cmd);
134
+
135
+ test("Format command with %%s string interpolation: ");
136
+ len = redisFormatCommand(&cmd,"SET %s %s","foo","bar");
137
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
138
+ len == 4+4+(3+2)+4+(3+2)+4+(3+2));
139
+ free(cmd);
140
+
141
+ test("Format command with %%s and an empty string: ");
142
+ len = redisFormatCommand(&cmd,"SET %s %s","foo","");
143
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 &&
144
+ len == 4+4+(3+2)+4+(3+2)+4+(0+2));
145
+ free(cmd);
146
+
147
+ test("Format command with an empty string in between proper interpolations: ");
148
+ len = redisFormatCommand(&cmd,"SET %s %s","","foo");
149
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$0\r\n\r\n$3\r\nfoo\r\n",len) == 0 &&
150
+ len == 4+4+(3+2)+4+(0+2)+4+(3+2));
151
+ free(cmd);
152
+
153
+ test("Format command with %%b string interpolation: ");
154
+ len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"b\0r",(size_t)3);
155
+ 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 &&
156
+ len == 4+4+(3+2)+4+(3+2)+4+(3+2));
157
+ free(cmd);
158
+
159
+ test("Format command with %%b and an empty string: ");
160
+ len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"",(size_t)0);
161
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 &&
162
+ len == 4+4+(3+2)+4+(3+2)+4+(0+2));
163
+ free(cmd);
164
+
165
+ test("Format command with literal %%: ");
166
+ len = redisFormatCommand(&cmd,"SET %% %%");
167
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$1\r\n%\r\n$1\r\n%\r\n",len) == 0 &&
168
+ len == 4+4+(3+2)+4+(1+2)+4+(1+2));
169
+ free(cmd);
170
+
171
+ /* Vararg width depends on the type. These tests make sure that the
172
+ * width is correctly determined using the format and subsequent varargs
173
+ * can correctly be interpolated. */
174
+ #define INTEGER_WIDTH_TEST(fmt, type) do { \
175
+ type value = 123; \
176
+ test("Format command with printf-delegation (" #type "): "); \
177
+ len = redisFormatCommand(&cmd,"key:%08" fmt " str:%s", value, "hello"); \
178
+ test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:00000123\r\n$9\r\nstr:hello\r\n",len) == 0 && \
179
+ len == 4+5+(12+2)+4+(9+2)); \
180
+ free(cmd); \
181
+ } while(0)
182
+
183
+ #define FLOAT_WIDTH_TEST(type) do { \
184
+ type value = 123.0; \
185
+ test("Format command with printf-delegation (" #type "): "); \
186
+ len = redisFormatCommand(&cmd,"key:%08.3f str:%s", value, "hello"); \
187
+ test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:0123.000\r\n$9\r\nstr:hello\r\n",len) == 0 && \
188
+ len == 4+5+(12+2)+4+(9+2)); \
189
+ free(cmd); \
190
+ } while(0)
191
+
192
+ INTEGER_WIDTH_TEST("d", int);
193
+ INTEGER_WIDTH_TEST("hhd", char);
194
+ INTEGER_WIDTH_TEST("hd", short);
195
+ INTEGER_WIDTH_TEST("ld", long);
196
+ INTEGER_WIDTH_TEST("lld", long long);
197
+ INTEGER_WIDTH_TEST("u", unsigned int);
198
+ INTEGER_WIDTH_TEST("hhu", unsigned char);
199
+ INTEGER_WIDTH_TEST("hu", unsigned short);
200
+ INTEGER_WIDTH_TEST("lu", unsigned long);
201
+ INTEGER_WIDTH_TEST("llu", unsigned long long);
202
+ FLOAT_WIDTH_TEST(float);
203
+ FLOAT_WIDTH_TEST(double);
204
+
205
+ test("Format command with invalid printf format: ");
206
+ len = redisFormatCommand(&cmd,"key:%08p %b",(void*)1234,"foo",(size_t)3);
207
+ test_cond(len == -1);
208
+
209
+ const char *argv[3];
210
+ argv[0] = "SET";
211
+ argv[1] = "foo\0xxx";
212
+ argv[2] = "bar";
213
+ size_t lens[3] = { 3, 7, 3 };
214
+ int argc = 3;
215
+
216
+ test("Format command by passing argc/argv without lengths: ");
217
+ len = redisFormatCommandArgv(&cmd,argc,argv,NULL);
218
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
219
+ len == 4+4+(3+2)+4+(3+2)+4+(3+2));
220
+ free(cmd);
221
+
222
+ test("Format command by passing argc/argv with lengths: ");
223
+ len = redisFormatCommandArgv(&cmd,argc,argv,lens);
224
+ 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 &&
225
+ len == 4+4+(3+2)+4+(7+2)+4+(3+2));
226
+ free(cmd);
227
+
228
+ sds sds_cmd;
229
+
230
+ sds_cmd = sdsempty();
231
+ test("Format command into sds by passing argc/argv without lengths: ");
232
+ len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,NULL);
233
+ 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 &&
234
+ len == 4+4+(3+2)+4+(3+2)+4+(3+2));
235
+ sdsfree(sds_cmd);
236
+
237
+ sds_cmd = sdsempty();
238
+ test("Format command into sds by passing argc/argv with lengths: ");
239
+ len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,lens);
240
+ 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 &&
241
+ len == 4+4+(3+2)+4+(7+2)+4+(3+2));
242
+ sdsfree(sds_cmd);
243
+ }
244
+
245
+ static void test_append_formatted_commands(struct config config) {
246
+ redisContext *c;
247
+ redisReply *reply;
248
+ char *cmd;
249
+ int len;
250
+
251
+ c = connect(config);
252
+
253
+ test("Append format command: ");
254
+
255
+ len = redisFormatCommand(&cmd, "SET foo bar");
256
+
257
+ test_cond(redisAppendFormattedCommand(c, cmd, len) == REDIS_OK);
258
+
259
+ assert(redisGetReply(c, (void*)&reply) == REDIS_OK);
260
+
261
+ free(cmd);
262
+ freeReplyObject(reply);
263
+
264
+ disconnect(c, 0);
265
+ }
266
+
267
+ static void test_reply_reader(void) {
268
+ redisReader *reader;
269
+ void *reply;
270
+ int ret;
271
+ int i;
272
+
273
+ test("Error handling in reply parser: ");
274
+ reader = redisReaderCreate();
275
+ redisReaderFeed(reader,(char*)"@foo\r\n",6);
276
+ ret = redisReaderGetReply(reader,NULL);
277
+ test_cond(ret == REDIS_ERR &&
278
+ strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0);
279
+ redisReaderFree(reader);
280
+
281
+ /* when the reply already contains multiple items, they must be free'd
282
+ * on an error. valgrind will bark when this doesn't happen. */
283
+ test("Memory cleanup in reply parser: ");
284
+ reader = redisReaderCreate();
285
+ redisReaderFeed(reader,(char*)"*2\r\n",4);
286
+ redisReaderFeed(reader,(char*)"$5\r\nhello\r\n",11);
287
+ redisReaderFeed(reader,(char*)"@foo\r\n",6);
288
+ ret = redisReaderGetReply(reader,NULL);
289
+ test_cond(ret == REDIS_ERR &&
290
+ strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0);
291
+ redisReaderFree(reader);
292
+
293
+ test("Set error on nested multi bulks with depth > 7: ");
294
+ reader = redisReaderCreate();
295
+
296
+ for (i = 0; i < 9; i++) {
297
+ redisReaderFeed(reader,(char*)"*1\r\n",4);
298
+ }
299
+
300
+ ret = redisReaderGetReply(reader,NULL);
301
+ test_cond(ret == REDIS_ERR &&
302
+ strncasecmp(reader->errstr,"No support for",14) == 0);
303
+ redisReaderFree(reader);
304
+
305
+ test("Works with NULL functions for reply: ");
306
+ reader = redisReaderCreate();
307
+ reader->fn = NULL;
308
+ redisReaderFeed(reader,(char*)"+OK\r\n",5);
309
+ ret = redisReaderGetReply(reader,&reply);
310
+ test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS);
311
+ redisReaderFree(reader);
312
+
313
+ test("Works when a single newline (\\r\\n) covers two calls to feed: ");
314
+ reader = redisReaderCreate();
315
+ reader->fn = NULL;
316
+ redisReaderFeed(reader,(char*)"+OK\r",4);
317
+ ret = redisReaderGetReply(reader,&reply);
318
+ assert(ret == REDIS_OK && reply == NULL);
319
+ redisReaderFeed(reader,(char*)"\n",1);
320
+ ret = redisReaderGetReply(reader,&reply);
321
+ test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS);
322
+ redisReaderFree(reader);
323
+
324
+ test("Don't reset state after protocol error: ");
325
+ reader = redisReaderCreate();
326
+ reader->fn = NULL;
327
+ redisReaderFeed(reader,(char*)"x",1);
328
+ ret = redisReaderGetReply(reader,&reply);
329
+ assert(ret == REDIS_ERR);
330
+ ret = redisReaderGetReply(reader,&reply);
331
+ test_cond(ret == REDIS_ERR && reply == NULL);
332
+ redisReaderFree(reader);
333
+
334
+ /* Regression test for issue #45 on GitHub. */
335
+ test("Don't do empty allocation for empty multi bulk: ");
336
+ reader = redisReaderCreate();
337
+ redisReaderFeed(reader,(char*)"*0\r\n",4);
338
+ ret = redisReaderGetReply(reader,&reply);
339
+ test_cond(ret == REDIS_OK &&
340
+ ((redisReply*)reply)->type == REDIS_REPLY_ARRAY &&
341
+ ((redisReply*)reply)->elements == 0);
342
+ freeReplyObject(reply);
343
+ redisReaderFree(reader);
344
+ }
345
+
346
+ static void test_free_null(void) {
347
+ void *redisCtx = NULL;
348
+ void *reply = NULL;
349
+
350
+ test("Don't fail when redisFree is passed a NULL value: ");
351
+ redisFree(redisCtx);
352
+ test_cond(redisCtx == NULL);
353
+
354
+ test("Don't fail when freeReplyObject is passed a NULL value: ");
355
+ freeReplyObject(reply);
356
+ test_cond(reply == NULL);
357
+ }
358
+
359
+ static void test_blocking_connection_errors(void) {
360
+ redisContext *c;
361
+
362
+ test("Returns error when host cannot be resolved: ");
363
+ c = redisConnect((char*)"idontexist.test", 6379);
364
+ test_cond(c->err == REDIS_ERR_OTHER &&
365
+ (strcmp(c->errstr,"Name or service not known") == 0 ||
366
+ strcmp(c->errstr,"Can't resolve: idontexist.test") == 0 ||
367
+ strcmp(c->errstr,"nodename nor servname provided, or not known") == 0 ||
368
+ strcmp(c->errstr,"No address associated with hostname") == 0 ||
369
+ strcmp(c->errstr,"Temporary failure in name resolution") == 0 ||
370
+ strcmp(c->errstr,"hostname nor servname provided, or not known") == 0 ||
371
+ strcmp(c->errstr,"no address associated with name") == 0));
372
+ redisFree(c);
373
+
374
+ test("Returns error when the port is not open: ");
375
+ c = redisConnect((char*)"localhost", 1);
376
+ test_cond(c->err == REDIS_ERR_IO &&
377
+ strcmp(c->errstr,"Connection refused") == 0);
378
+ redisFree(c);
379
+
380
+ test("Returns error when the unix_sock socket path doesn't accept connections: ");
381
+ c = redisConnectUnix((char*)"/tmp/idontexist.sock");
382
+ test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */
383
+ redisFree(c);
384
+ }
385
+
386
+ static void test_blocking_connection(struct config config) {
387
+ redisContext *c;
388
+ redisReply *reply;
389
+
390
+ c = connect(config);
391
+
392
+ test("Is able to deliver commands: ");
393
+ reply = redisCommand(c,"PING");
394
+ test_cond(reply->type == REDIS_REPLY_STATUS &&
395
+ strcasecmp(reply->str,"pong") == 0)
396
+ freeReplyObject(reply);
397
+
398
+ test("Is a able to send commands verbatim: ");
399
+ reply = redisCommand(c,"SET foo bar");
400
+ test_cond (reply->type == REDIS_REPLY_STATUS &&
401
+ strcasecmp(reply->str,"ok") == 0)
402
+ freeReplyObject(reply);
403
+
404
+ test("%%s String interpolation works: ");
405
+ reply = redisCommand(c,"SET %s %s","foo","hello world");
406
+ freeReplyObject(reply);
407
+ reply = redisCommand(c,"GET foo");
408
+ test_cond(reply->type == REDIS_REPLY_STRING &&
409
+ strcmp(reply->str,"hello world") == 0);
410
+ freeReplyObject(reply);
411
+
412
+ test("%%b String interpolation works: ");
413
+ reply = redisCommand(c,"SET %b %b","foo",(size_t)3,"hello\x00world",(size_t)11);
414
+ freeReplyObject(reply);
415
+ reply = redisCommand(c,"GET foo");
416
+ test_cond(reply->type == REDIS_REPLY_STRING &&
417
+ memcmp(reply->str,"hello\x00world",11) == 0)
418
+
419
+ test("Binary reply length is correct: ");
420
+ test_cond(reply->len == 11)
421
+ freeReplyObject(reply);
422
+
423
+ test("Can parse nil replies: ");
424
+ reply = redisCommand(c,"GET nokey");
425
+ test_cond(reply->type == REDIS_REPLY_NIL)
426
+ freeReplyObject(reply);
427
+
428
+ /* test 7 */
429
+ test("Can parse integer replies: ");
430
+ reply = redisCommand(c,"INCR mycounter");
431
+ test_cond(reply->type == REDIS_REPLY_INTEGER && reply->integer == 1)
432
+ freeReplyObject(reply);
433
+
434
+ test("Can parse multi bulk replies: ");
435
+ freeReplyObject(redisCommand(c,"LPUSH mylist foo"));
436
+ freeReplyObject(redisCommand(c,"LPUSH mylist bar"));
437
+ reply = redisCommand(c,"LRANGE mylist 0 -1");
438
+ test_cond(reply->type == REDIS_REPLY_ARRAY &&
439
+ reply->elements == 2 &&
440
+ !memcmp(reply->element[0]->str,"bar",3) &&
441
+ !memcmp(reply->element[1]->str,"foo",3))
442
+ freeReplyObject(reply);
443
+
444
+ /* m/e with multi bulk reply *before* other reply.
445
+ * specifically test ordering of reply items to parse. */
446
+ test("Can handle nested multi bulk replies: ");
447
+ freeReplyObject(redisCommand(c,"MULTI"));
448
+ freeReplyObject(redisCommand(c,"LRANGE mylist 0 -1"));
449
+ freeReplyObject(redisCommand(c,"PING"));
450
+ reply = (redisCommand(c,"EXEC"));
451
+ test_cond(reply->type == REDIS_REPLY_ARRAY &&
452
+ reply->elements == 2 &&
453
+ reply->element[0]->type == REDIS_REPLY_ARRAY &&
454
+ reply->element[0]->elements == 2 &&
455
+ !memcmp(reply->element[0]->element[0]->str,"bar",3) &&
456
+ !memcmp(reply->element[0]->element[1]->str,"foo",3) &&
457
+ reply->element[1]->type == REDIS_REPLY_STATUS &&
458
+ strcasecmp(reply->element[1]->str,"pong") == 0);
459
+ freeReplyObject(reply);
460
+
461
+ disconnect(c, 0);
462
+ }
463
+
464
+ static void test_blocking_connection_timeouts(struct config config) {
465
+ redisContext *c;
466
+ redisReply *reply;
467
+ ssize_t s;
468
+ const char *cmd = "DEBUG SLEEP 3\r\n";
469
+ struct timeval tv;
470
+
471
+ c = connect(config);
472
+ test("Successfully completes a command when the timeout is not exceeded: ");
473
+ reply = redisCommand(c,"SET foo fast");
474
+ freeReplyObject(reply);
475
+ tv.tv_sec = 0;
476
+ tv.tv_usec = 10000;
477
+ redisSetTimeout(c, tv);
478
+ reply = redisCommand(c, "GET foo");
479
+ test_cond(reply != NULL && reply->type == REDIS_REPLY_STRING && memcmp(reply->str, "fast", 4) == 0);
480
+ freeReplyObject(reply);
481
+ disconnect(c, 0);
482
+
483
+ c = connect(config);
484
+ test("Does not return a reply when the command times out: ");
485
+ s = write(c->fd, cmd, strlen(cmd));
486
+ tv.tv_sec = 0;
487
+ tv.tv_usec = 10000;
488
+ redisSetTimeout(c, tv);
489
+ reply = redisCommand(c, "GET foo");
490
+ test_cond(s > 0 && reply == NULL && c->err == REDIS_ERR_IO && strcmp(c->errstr, "Resource temporarily unavailable") == 0);
491
+ freeReplyObject(reply);
492
+
493
+ test("Reconnect properly reconnects after a timeout: ");
494
+ redisReconnect(c);
495
+ reply = redisCommand(c, "PING");
496
+ test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
497
+ freeReplyObject(reply);
498
+
499
+ test("Reconnect properly uses owned parameters: ");
500
+ config.tcp.host = "foo";
501
+ config.unix_sock.path = "foo";
502
+ redisReconnect(c);
503
+ reply = redisCommand(c, "PING");
504
+ test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
505
+ freeReplyObject(reply);
506
+
507
+ disconnect(c, 0);
508
+ }
509
+
510
+ static void test_blocking_io_errors(struct config config) {
511
+ redisContext *c;
512
+ redisReply *reply;
513
+ void *_reply;
514
+ int major, minor;
515
+
516
+ /* Connect to target given by config. */
517
+ c = connect(config);
518
+ {
519
+ /* Find out Redis version to determine the path for the next test */
520
+ const char *field = "redis_version:";
521
+ char *p, *eptr;
522
+
523
+ reply = redisCommand(c,"INFO");
524
+ p = strstr(reply->str,field);
525
+ major = strtol(p+strlen(field),&eptr,10);
526
+ p = eptr+1; /* char next to the first "." */
527
+ minor = strtol(p,&eptr,10);
528
+ freeReplyObject(reply);
529
+ }
530
+
531
+ test("Returns I/O error when the connection is lost: ");
532
+ reply = redisCommand(c,"QUIT");
533
+ if (major > 2 || (major == 2 && minor > 0)) {
534
+ /* > 2.0 returns OK on QUIT and read() should be issued once more
535
+ * to know the descriptor is at EOF. */
536
+ test_cond(strcasecmp(reply->str,"OK") == 0 &&
537
+ redisGetReply(c,&_reply) == REDIS_ERR);
538
+ freeReplyObject(reply);
539
+ } else {
540
+ test_cond(reply == NULL);
541
+ }
542
+
543
+ /* On 2.0, QUIT will cause the connection to be closed immediately and
544
+ * the read(2) for the reply on QUIT will set the error to EOF.
545
+ * On >2.0, QUIT will return with OK and another read(2) needed to be
546
+ * issued to find out the socket was closed by the server. In both
547
+ * conditions, the error will be set to EOF. */
548
+ assert(c->err == REDIS_ERR_EOF &&
549
+ strcmp(c->errstr,"Server closed the connection") == 0);
550
+ redisFree(c);
551
+
552
+ c = connect(config);
553
+ test("Returns I/O error on socket timeout: ");
554
+ struct timeval tv = { 0, 1000 };
555
+ assert(redisSetTimeout(c,tv) == REDIS_OK);
556
+ test_cond(redisGetReply(c,&_reply) == REDIS_ERR &&
557
+ c->err == REDIS_ERR_IO && errno == EAGAIN);
558
+ redisFree(c);
559
+ }
560
+
561
+ static void test_invalid_timeout_errors(struct config config) {
562
+ redisContext *c;
563
+
564
+ test("Set error when an invalid timeout usec value is given to redisConnectWithTimeout: ");
565
+
566
+ config.tcp.timeout.tv_sec = 0;
567
+ config.tcp.timeout.tv_usec = 10000001;
568
+
569
+ c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout);
570
+
571
+ test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0);
572
+ redisFree(c);
573
+
574
+ test("Set error when an invalid timeout sec value is given to redisConnectWithTimeout: ");
575
+
576
+ config.tcp.timeout.tv_sec = (((LONG_MAX) - 999) / 1000) + 1;
577
+ config.tcp.timeout.tv_usec = 0;
578
+
579
+ c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout);
580
+
581
+ test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0);
582
+ redisFree(c);
583
+ }
584
+
585
+ static void test_throughput(struct config config) {
586
+ redisContext *c = connect(config);
587
+ redisReply **replies;
588
+ int i, num;
589
+ long long t1, t2;
590
+
591
+ test("Throughput:\n");
592
+ for (i = 0; i < 500; i++)
593
+ freeReplyObject(redisCommand(c,"LPUSH mylist foo"));
594
+
595
+ num = 1000;
596
+ replies = malloc(sizeof(redisReply*)*num);
597
+ t1 = usec();
598
+ for (i = 0; i < num; i++) {
599
+ replies[i] = redisCommand(c,"PING");
600
+ assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS);
601
+ }
602
+ t2 = usec();
603
+ for (i = 0; i < num; i++) freeReplyObject(replies[i]);
604
+ free(replies);
605
+ printf("\t(%dx PING: %.3fs)\n", num, (t2-t1)/1000000.0);
606
+
607
+ replies = malloc(sizeof(redisReply*)*num);
608
+ t1 = usec();
609
+ for (i = 0; i < num; i++) {
610
+ replies[i] = redisCommand(c,"LRANGE mylist 0 499");
611
+ assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY);
612
+ assert(replies[i] != NULL && replies[i]->elements == 500);
613
+ }
614
+ t2 = usec();
615
+ for (i = 0; i < num; i++) freeReplyObject(replies[i]);
616
+ free(replies);
617
+ printf("\t(%dx LRANGE with 500 elements: %.3fs)\n", num, (t2-t1)/1000000.0);
618
+
619
+ num = 10000;
620
+ replies = malloc(sizeof(redisReply*)*num);
621
+ for (i = 0; i < num; i++)
622
+ redisAppendCommand(c,"PING");
623
+ t1 = usec();
624
+ for (i = 0; i < num; i++) {
625
+ assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);
626
+ assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS);
627
+ }
628
+ t2 = usec();
629
+ for (i = 0; i < num; i++) freeReplyObject(replies[i]);
630
+ free(replies);
631
+ printf("\t(%dx PING (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
632
+
633
+ replies = malloc(sizeof(redisReply*)*num);
634
+ for (i = 0; i < num; i++)
635
+ redisAppendCommand(c,"LRANGE mylist 0 499");
636
+ t1 = usec();
637
+ for (i = 0; i < num; i++) {
638
+ assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);
639
+ assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY);
640
+ assert(replies[i] != NULL && replies[i]->elements == 500);
641
+ }
642
+ t2 = usec();
643
+ for (i = 0; i < num; i++) freeReplyObject(replies[i]);
644
+ free(replies);
645
+ printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
646
+
647
+ disconnect(c, 0);
648
+ }
649
+
650
+ // static long __test_callback_flags = 0;
651
+ // static void __test_callback(redisContext *c, void *privdata) {
652
+ // ((void)c);
653
+ // /* Shift to detect execution order */
654
+ // __test_callback_flags <<= 8;
655
+ // __test_callback_flags |= (long)privdata;
656
+ // }
657
+ //
658
+ // static void __test_reply_callback(redisContext *c, redisReply *reply, void *privdata) {
659
+ // ((void)c);
660
+ // /* Shift to detect execution order */
661
+ // __test_callback_flags <<= 8;
662
+ // __test_callback_flags |= (long)privdata;
663
+ // if (reply) freeReplyObject(reply);
664
+ // }
665
+ //
666
+ // static redisContext *__connect_nonblock() {
667
+ // /* Reset callback flags */
668
+ // __test_callback_flags = 0;
669
+ // return redisConnectNonBlock("127.0.0.1", port, NULL);
670
+ // }
671
+ //
672
+ // static void test_nonblocking_connection() {
673
+ // redisContext *c;
674
+ // int wdone = 0;
675
+ //
676
+ // test("Calls command callback when command is issued: ");
677
+ // c = __connect_nonblock();
678
+ // redisSetCommandCallback(c,__test_callback,(void*)1);
679
+ // redisCommand(c,"PING");
680
+ // test_cond(__test_callback_flags == 1);
681
+ // redisFree(c);
682
+ //
683
+ // test("Calls disconnect callback on redisDisconnect: ");
684
+ // c = __connect_nonblock();
685
+ // redisSetDisconnectCallback(c,__test_callback,(void*)2);
686
+ // redisDisconnect(c);
687
+ // test_cond(__test_callback_flags == 2);
688
+ // redisFree(c);
689
+ //
690
+ // test("Calls disconnect callback and free callback on redisFree: ");
691
+ // c = __connect_nonblock();
692
+ // redisSetDisconnectCallback(c,__test_callback,(void*)2);
693
+ // redisSetFreeCallback(c,__test_callback,(void*)4);
694
+ // redisFree(c);
695
+ // test_cond(__test_callback_flags == ((2 << 8) | 4));
696
+ //
697
+ // test("redisBufferWrite against empty write buffer: ");
698
+ // c = __connect_nonblock();
699
+ // test_cond(redisBufferWrite(c,&wdone) == REDIS_OK && wdone == 1);
700
+ // redisFree(c);
701
+ //
702
+ // test("redisBufferWrite against not yet connected fd: ");
703
+ // c = __connect_nonblock();
704
+ // redisCommand(c,"PING");
705
+ // test_cond(redisBufferWrite(c,NULL) == REDIS_ERR &&
706
+ // strncmp(c->error,"write:",6) == 0);
707
+ // redisFree(c);
708
+ //
709
+ // test("redisBufferWrite against closed fd: ");
710
+ // c = __connect_nonblock();
711
+ // redisCommand(c,"PING");
712
+ // redisDisconnect(c);
713
+ // test_cond(redisBufferWrite(c,NULL) == REDIS_ERR &&
714
+ // strncmp(c->error,"write:",6) == 0);
715
+ // redisFree(c);
716
+ //
717
+ // test("Process callbacks in the right sequence: ");
718
+ // c = __connect_nonblock();
719
+ // redisCommandWithCallback(c,__test_reply_callback,(void*)1,"PING");
720
+ // redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING");
721
+ // redisCommandWithCallback(c,__test_reply_callback,(void*)3,"PING");
722
+ //
723
+ // /* Write output buffer */
724
+ // wdone = 0;
725
+ // while(!wdone) {
726
+ // usleep(500);
727
+ // redisBufferWrite(c,&wdone);
728
+ // }
729
+ //
730
+ // /* Read until at least one callback is executed (the 3 replies will
731
+ // * arrive in a single packet, causing all callbacks to be executed in
732
+ // * a single pass). */
733
+ // while(__test_callback_flags == 0) {
734
+ // assert(redisBufferRead(c) == REDIS_OK);
735
+ // redisProcessCallbacks(c);
736
+ // }
737
+ // test_cond(__test_callback_flags == 0x010203);
738
+ // redisFree(c);
739
+ //
740
+ // test("redisDisconnect executes pending callbacks with NULL reply: ");
741
+ // c = __connect_nonblock();
742
+ // redisSetDisconnectCallback(c,__test_callback,(void*)1);
743
+ // redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING");
744
+ // redisDisconnect(c);
745
+ // test_cond(__test_callback_flags == 0x0201);
746
+ // redisFree(c);
747
+ // }
748
+
749
+ int main(int argc, char **argv) {
750
+ struct config cfg = {
751
+ .tcp = {
752
+ .host = "127.0.0.1",
753
+ .port = 6379
754
+ },
755
+ .unix_sock = {
756
+ .path = "/tmp/redis.sock"
757
+ }
758
+ };
759
+ int throughput = 1;
760
+ int test_inherit_fd = 1;
761
+
762
+ /* Ignore broken pipe signal (for I/O error tests). */
763
+ signal(SIGPIPE, SIG_IGN);
764
+
765
+ /* Parse command line options. */
766
+ argv++; argc--;
767
+ while (argc) {
768
+ if (argc >= 2 && !strcmp(argv[0],"-h")) {
769
+ argv++; argc--;
770
+ cfg.tcp.host = argv[0];
771
+ } else if (argc >= 2 && !strcmp(argv[0],"-p")) {
772
+ argv++; argc--;
773
+ cfg.tcp.port = atoi(argv[0]);
774
+ } else if (argc >= 2 && !strcmp(argv[0],"-s")) {
775
+ argv++; argc--;
776
+ cfg.unix_sock.path = argv[0];
777
+ } else if (argc >= 1 && !strcmp(argv[0],"--skip-throughput")) {
778
+ throughput = 0;
779
+ } else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) {
780
+ test_inherit_fd = 0;
781
+ } else {
782
+ fprintf(stderr, "Invalid argument: %s\n", argv[0]);
783
+ exit(1);
784
+ }
785
+ argv++; argc--;
786
+ }
787
+
788
+ test_format_commands();
789
+ test_reply_reader();
790
+ test_blocking_connection_errors();
791
+ test_free_null();
792
+
793
+ printf("\nTesting against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port);
794
+ cfg.type = CONN_TCP;
795
+ test_blocking_connection(cfg);
796
+ test_blocking_connection_timeouts(cfg);
797
+ test_blocking_io_errors(cfg);
798
+ test_invalid_timeout_errors(cfg);
799
+ test_append_formatted_commands(cfg);
800
+ if (throughput) test_throughput(cfg);
801
+
802
+ printf("\nTesting against Unix socket connection (%s):\n", cfg.unix_sock.path);
803
+ cfg.type = CONN_UNIX;
804
+ test_blocking_connection(cfg);
805
+ test_blocking_connection_timeouts(cfg);
806
+ test_blocking_io_errors(cfg);
807
+ if (throughput) test_throughput(cfg);
808
+
809
+ if (test_inherit_fd) {
810
+ printf("\nTesting against inherited fd (%s):\n", cfg.unix_sock.path);
811
+ cfg.type = CONN_FD;
812
+ test_blocking_connection(cfg);
813
+ }
814
+
815
+
816
+ if (fails) {
817
+ printf("*** %d TESTS FAILED ***\n", fails);
818
+ return 1;
819
+ }
820
+
821
+ printf("ALL TESTS PASSED\n");
822
+ return 0;
823
+ }