]> git.saurik.com Git - redis.git/blob - deps/hiredis/test.c
TODO updated
[redis.git] / deps / hiredis / test.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <strings.h>
5 #include <sys/time.h>
6 #include <assert.h>
7 #include <unistd.h>
8 #include <signal.h>
9 #include <errno.h>
10
11 #include "hiredis.h"
12
13 /* The following lines make up our testing "framework" :) */
14 static int tests = 0, fails = 0;
15 #define test(_s) { printf("#%02d ", ++tests); printf(_s); }
16 #define test_cond(_c) if(_c) printf("PASSED\n"); else {printf("FAILED\n"); fails++;}
17
18 static long long usec(void) {
19 struct timeval tv;
20 gettimeofday(&tv,NULL);
21 return (((long long)tv.tv_sec)*1000000)+tv.tv_usec;
22 }
23
24 static int use_unix = 0;
25 static redisContext *blocking_context = NULL;
26 static void __connect(redisContext **target) {
27 *target = blocking_context = (use_unix ?
28 redisConnectUnix("/tmp/redis.sock") : redisConnect((char*)"127.0.0.1", 6379));
29 if (blocking_context->err) {
30 printf("Connection error: %s\n", blocking_context->errstr);
31 exit(1);
32 }
33 }
34
35 static void test_format_commands(void) {
36 char *cmd;
37 int len;
38
39 test("Format command without interpolation: ");
40 len = redisFormatCommand(&cmd,"SET foo bar");
41 test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
42 len == 4+4+(3+2)+4+(3+2)+4+(3+2));
43 free(cmd);
44
45 test("Format command with %%s string interpolation: ");
46 len = redisFormatCommand(&cmd,"SET %s %s","foo","bar");
47 test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
48 len == 4+4+(3+2)+4+(3+2)+4+(3+2));
49 free(cmd);
50
51 test("Format command with %%s and an empty string: ");
52 len = redisFormatCommand(&cmd,"SET %s %s","foo","");
53 test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 &&
54 len == 4+4+(3+2)+4+(3+2)+4+(0+2));
55 free(cmd);
56
57 test("Format command with an empty string in between proper interpolations: ");
58 len = redisFormatCommand(&cmd,"SET %s %s","","foo");
59 test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$0\r\n\r\n$3\r\nfoo\r\n",len) == 0 &&
60 len == 4+4+(3+2)+4+(0+2)+4+(3+2));
61 free(cmd);
62
63 test("Format command with %%b string interpolation: ");
64 len = redisFormatCommand(&cmd,"SET %b %b","foo",3,"b\0r",3);
65 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 &&
66 len == 4+4+(3+2)+4+(3+2)+4+(3+2));
67 free(cmd);
68
69 test("Format command with %%b and an empty string: ");
70 len = redisFormatCommand(&cmd,"SET %b %b","foo",3,"",0);
71 test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 &&
72 len == 4+4+(3+2)+4+(3+2)+4+(0+2));
73 free(cmd);
74
75 test("Format command with literal %%: ");
76 len = redisFormatCommand(&cmd,"SET %% %%");
77 test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$1\r\n%\r\n$1\r\n%\r\n",len) == 0 &&
78 len == 4+4+(3+2)+4+(1+2)+4+(1+2));
79 free(cmd);
80
81 test("Format command with printf-delegation (long long): ");
82 len = redisFormatCommand(&cmd,"key:%08lld",1234ll);
83 test_cond(strncmp(cmd,"*1\r\n$12\r\nkey:00001234\r\n",len) == 0 &&
84 len == 4+5+(12+2));
85 free(cmd);
86
87 test("Format command with printf-delegation (float): ");
88 len = redisFormatCommand(&cmd,"v:%06.1f",12.34f);
89 test_cond(strncmp(cmd,"*1\r\n$8\r\nv:0012.3\r\n",len) == 0 &&
90 len == 4+4+(8+2));
91 free(cmd);
92
93 test("Format command with printf-delegation and extra interpolation: ");
94 len = redisFormatCommand(&cmd,"key:%d %b",1234,"foo",3);
95 test_cond(strncmp(cmd,"*2\r\n$8\r\nkey:1234\r\n$3\r\nfoo\r\n",len) == 0 &&
96 len == 4+4+(8+2)+4+(3+2));
97 free(cmd);
98
99 test("Format command with wrong printf format and extra interpolation: ");
100 len = redisFormatCommand(&cmd,"key:%08p %b",1234,"foo",3);
101 test_cond(strncmp(cmd,"*2\r\n$6\r\nkey:8p\r\n$3\r\nfoo\r\n",len) == 0 &&
102 len == 4+4+(6+2)+4+(3+2));
103 free(cmd);
104
105 const char *argv[3];
106 argv[0] = "SET";
107 argv[1] = "foo\0xxx";
108 argv[2] = "bar";
109 size_t lens[3] = { 3, 7, 3 };
110 int argc = 3;
111
112 test("Format command by passing argc/argv without lengths: ");
113 len = redisFormatCommandArgv(&cmd,argc,argv,NULL);
114 test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
115 len == 4+4+(3+2)+4+(3+2)+4+(3+2));
116 free(cmd);
117
118 test("Format command by passing argc/argv with lengths: ");
119 len = redisFormatCommandArgv(&cmd,argc,argv,lens);
120 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 &&
121 len == 4+4+(3+2)+4+(7+2)+4+(3+2));
122 free(cmd);
123 }
124
125 static void test_blocking_connection(void) {
126 redisContext *c;
127 redisReply *reply;
128 int major, minor;
129
130 test("Returns error when host cannot be resolved: ");
131 c = redisConnect((char*)"idontexist.local", 6379);
132 test_cond(c->err == REDIS_ERR_OTHER &&
133 strcmp(c->errstr,"Can't resolve: idontexist.local") == 0);
134 redisFree(c);
135
136 test("Returns error when the port is not open: ");
137 c = redisConnect((char*)"localhost", 56380);
138 test_cond(c->err == REDIS_ERR_IO &&
139 strcmp(c->errstr,"Connection refused") == 0);
140 redisFree(c);
141
142 __connect(&c);
143 test("Is able to deliver commands: ");
144 reply = redisCommand(c,"PING");
145 test_cond(reply->type == REDIS_REPLY_STATUS &&
146 strcasecmp(reply->str,"pong") == 0)
147 freeReplyObject(reply);
148
149 /* Switch to DB 9 for testing, now that we know we can chat. */
150 reply = redisCommand(c,"SELECT 9");
151 freeReplyObject(reply);
152
153 /* Make sure the DB is emtpy */
154 reply = redisCommand(c,"DBSIZE");
155 if (reply->type != REDIS_REPLY_INTEGER || reply->integer != 0) {
156 printf("Database #9 is not empty, test can not continue\n");
157 exit(1);
158 }
159 freeReplyObject(reply);
160
161 test("Is a able to send commands verbatim: ");
162 reply = redisCommand(c,"SET foo bar");
163 test_cond (reply->type == REDIS_REPLY_STATUS &&
164 strcasecmp(reply->str,"ok") == 0)
165 freeReplyObject(reply);
166
167 test("%%s String interpolation works: ");
168 reply = redisCommand(c,"SET %s %s","foo","hello world");
169 freeReplyObject(reply);
170 reply = redisCommand(c,"GET foo");
171 test_cond(reply->type == REDIS_REPLY_STRING &&
172 strcmp(reply->str,"hello world") == 0);
173 freeReplyObject(reply);
174
175 test("%%b String interpolation works: ");
176 reply = redisCommand(c,"SET %b %b","foo",3,"hello\x00world",11);
177 freeReplyObject(reply);
178 reply = redisCommand(c,"GET foo");
179 test_cond(reply->type == REDIS_REPLY_STRING &&
180 memcmp(reply->str,"hello\x00world",11) == 0)
181
182 test("Binary reply length is correct: ");
183 test_cond(reply->len == 11)
184 freeReplyObject(reply);
185
186 test("Can parse nil replies: ");
187 reply = redisCommand(c,"GET nokey");
188 test_cond(reply->type == REDIS_REPLY_NIL)
189 freeReplyObject(reply);
190
191 /* test 7 */
192 test("Can parse integer replies: ");
193 reply = redisCommand(c,"INCR mycounter");
194 test_cond(reply->type == REDIS_REPLY_INTEGER && reply->integer == 1)
195 freeReplyObject(reply);
196
197 test("Can parse multi bulk replies: ");
198 freeReplyObject(redisCommand(c,"LPUSH mylist foo"));
199 freeReplyObject(redisCommand(c,"LPUSH mylist bar"));
200 reply = redisCommand(c,"LRANGE mylist 0 -1");
201 test_cond(reply->type == REDIS_REPLY_ARRAY &&
202 reply->elements == 2 &&
203 !memcmp(reply->element[0]->str,"bar",3) &&
204 !memcmp(reply->element[1]->str,"foo",3))
205 freeReplyObject(reply);
206
207 /* m/e with multi bulk reply *before* other reply.
208 * specifically test ordering of reply items to parse. */
209 test("Can handle nested multi bulk replies: ");
210 freeReplyObject(redisCommand(c,"MULTI"));
211 freeReplyObject(redisCommand(c,"LRANGE mylist 0 -1"));
212 freeReplyObject(redisCommand(c,"PING"));
213 reply = (redisCommand(c,"EXEC"));
214 test_cond(reply->type == REDIS_REPLY_ARRAY &&
215 reply->elements == 2 &&
216 reply->element[0]->type == REDIS_REPLY_ARRAY &&
217 reply->element[0]->elements == 2 &&
218 !memcmp(reply->element[0]->element[0]->str,"bar",3) &&
219 !memcmp(reply->element[0]->element[1]->str,"foo",3) &&
220 reply->element[1]->type == REDIS_REPLY_STATUS &&
221 strcasecmp(reply->element[1]->str,"pong") == 0);
222 freeReplyObject(reply);
223
224 {
225 /* Find out Redis version to determine the path for the next test */
226 const char *field = "redis_version:";
227 char *p, *eptr;
228
229 reply = redisCommand(c,"INFO");
230 p = strstr(reply->str,field);
231 major = strtol(p+strlen(field),&eptr,10);
232 p = eptr+1; /* char next to the first "." */
233 minor = strtol(p,&eptr,10);
234 freeReplyObject(reply);
235 }
236
237 test("Returns I/O error when the connection is lost: ");
238 reply = redisCommand(c,"QUIT");
239 if (major >= 2 && minor > 0) {
240 /* > 2.0 returns OK on QUIT and read() should be issued once more
241 * to know the descriptor is at EOF. */
242 test_cond(strcasecmp(reply->str,"OK") == 0 &&
243 redisGetReply(c,(void**)&reply) == REDIS_ERR);
244 freeReplyObject(reply);
245 } else {
246 test_cond(reply == NULL);
247 }
248
249 /* On 2.0, QUIT will cause the connection to be closed immediately and
250 * the read(2) for the reply on QUIT will set the error to EOF.
251 * On >2.0, QUIT will return with OK and another read(2) needed to be
252 * issued to find out the socket was closed by the server. In both
253 * conditions, the error will be set to EOF. */
254 assert(c->err == REDIS_ERR_EOF &&
255 strcmp(c->errstr,"Server closed the connection") == 0);
256 redisFree(c);
257
258 __connect(&c);
259 test("Returns I/O error on socket timeout: ");
260 struct timeval tv = { 0, 1000 };
261 assert(redisSetTimeout(c,tv) == REDIS_OK);
262 test_cond(redisGetReply(c,(void**)&reply) == REDIS_ERR &&
263 c->err == REDIS_ERR_IO && errno == EAGAIN);
264 redisFree(c);
265
266 /* Context should be connected */
267 __connect(&c);
268 }
269
270 static void test_reply_reader(void) {
271 void *reader;
272 void *reply;
273 char *err;
274 int ret;
275
276 test("Error handling in reply parser: ");
277 reader = redisReplyReaderCreate();
278 redisReplyReaderFeed(reader,(char*)"@foo\r\n",6);
279 ret = redisReplyReaderGetReply(reader,NULL);
280 err = redisReplyReaderGetError(reader);
281 test_cond(ret == REDIS_ERR &&
282 strcasecmp(err,"Protocol error, got \"@\" as reply type byte") == 0);
283 redisReplyReaderFree(reader);
284
285 /* when the reply already contains multiple items, they must be free'd
286 * on an error. valgrind will bark when this doesn't happen. */
287 test("Memory cleanup in reply parser: ");
288 reader = redisReplyReaderCreate();
289 redisReplyReaderFeed(reader,(char*)"*2\r\n",4);
290 redisReplyReaderFeed(reader,(char*)"$5\r\nhello\r\n",11);
291 redisReplyReaderFeed(reader,(char*)"@foo\r\n",6);
292 ret = redisReplyReaderGetReply(reader,NULL);
293 err = redisReplyReaderGetError(reader);
294 test_cond(ret == REDIS_ERR &&
295 strcasecmp(err,"Protocol error, got \"@\" as reply type byte") == 0);
296 redisReplyReaderFree(reader);
297
298 test("Set error on nested multi bulks with depth > 1: ");
299 reader = redisReplyReaderCreate();
300 redisReplyReaderFeed(reader,(char*)"*1\r\n",4);
301 redisReplyReaderFeed(reader,(char*)"*1\r\n",4);
302 redisReplyReaderFeed(reader,(char*)"*1\r\n",4);
303 ret = redisReplyReaderGetReply(reader,NULL);
304 err = redisReplyReaderGetError(reader);
305 test_cond(ret == REDIS_ERR &&
306 strncasecmp(err,"No support for",14) == 0);
307 redisReplyReaderFree(reader);
308
309 test("Works with NULL functions for reply: ");
310 reader = redisReplyReaderCreate();
311 redisReplyReaderSetReplyObjectFunctions(reader,NULL);
312 redisReplyReaderFeed(reader,(char*)"+OK\r\n",5);
313 ret = redisReplyReaderGetReply(reader,&reply);
314 test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS);
315 redisReplyReaderFree(reader);
316
317 test("Works when a single newline (\\r\\n) covers two calls to feed: ");
318 reader = redisReplyReaderCreate();
319 redisReplyReaderSetReplyObjectFunctions(reader,NULL);
320 redisReplyReaderFeed(reader,(char*)"+OK\r",4);
321 ret = redisReplyReaderGetReply(reader,&reply);
322 assert(ret == REDIS_OK && reply == NULL);
323 redisReplyReaderFeed(reader,(char*)"\n",1);
324 ret = redisReplyReaderGetReply(reader,&reply);
325 test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS);
326 redisReplyReaderFree(reader);
327
328 test("Properly reset state after protocol error: ");
329 reader = redisReplyReaderCreate();
330 redisReplyReaderSetReplyObjectFunctions(reader,NULL);
331 redisReplyReaderFeed(reader,(char*)"x",1);
332 ret = redisReplyReaderGetReply(reader,&reply);
333 assert(ret == REDIS_ERR);
334 ret = redisReplyReaderGetReply(reader,&reply);
335 test_cond(ret == REDIS_OK && reply == NULL)
336 }
337
338 static void test_throughput(void) {
339 int i, num;
340 long long t1, t2;
341 redisContext *c = blocking_context;
342 redisReply **replies;
343
344 test("Throughput:\n");
345 for (i = 0; i < 500; i++)
346 freeReplyObject(redisCommand(c,"LPUSH mylist foo"));
347
348 num = 1000;
349 replies = malloc(sizeof(redisReply*)*num);
350 t1 = usec();
351 for (i = 0; i < num; i++) {
352 replies[i] = redisCommand(c,"PING");
353 assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS);
354 }
355 t2 = usec();
356 for (i = 0; i < num; i++) freeReplyObject(replies[i]);
357 free(replies);
358 printf("\t(%dx PING: %.3fs)\n", num, (t2-t1)/1000000.0);
359
360 replies = malloc(sizeof(redisReply*)*num);
361 t1 = usec();
362 for (i = 0; i < num; i++) {
363 replies[i] = redisCommand(c,"LRANGE mylist 0 499");
364 assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY);
365 assert(replies[i] != NULL && replies[i]->elements == 500);
366 }
367 t2 = usec();
368 for (i = 0; i < num; i++) freeReplyObject(replies[i]);
369 free(replies);
370 printf("\t(%dx LRANGE with 500 elements: %.3fs)\n", num, (t2-t1)/1000000.0);
371
372 num = 10000;
373 replies = malloc(sizeof(redisReply*)*num);
374 for (i = 0; i < num; i++)
375 redisAppendCommand(c,"PING");
376 t1 = usec();
377 for (i = 0; i < num; i++) {
378 assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);
379 assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS);
380 }
381 t2 = usec();
382 for (i = 0; i < num; i++) freeReplyObject(replies[i]);
383 free(replies);
384 printf("\t(%dx PING (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
385
386 replies = malloc(sizeof(redisReply*)*num);
387 for (i = 0; i < num; i++)
388 redisAppendCommand(c,"LRANGE mylist 0 499");
389 t1 = usec();
390 for (i = 0; i < num; i++) {
391 assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);
392 assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY);
393 assert(replies[i] != NULL && replies[i]->elements == 500);
394 }
395 t2 = usec();
396 for (i = 0; i < num; i++) freeReplyObject(replies[i]);
397 free(replies);
398 printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
399 }
400
401 static void cleanup(void) {
402 redisContext *c = blocking_context;
403 redisReply *reply;
404
405 /* Make sure we're on DB 9 */
406 reply = redisCommand(c,"SELECT 9");
407 assert(reply != NULL); freeReplyObject(reply);
408 reply = redisCommand(c,"FLUSHDB");
409 assert(reply != NULL); freeReplyObject(reply);
410 redisFree(c);
411 }
412
413 // static long __test_callback_flags = 0;
414 // static void __test_callback(redisContext *c, void *privdata) {
415 // ((void)c);
416 // /* Shift to detect execution order */
417 // __test_callback_flags <<= 8;
418 // __test_callback_flags |= (long)privdata;
419 // }
420 //
421 // static void __test_reply_callback(redisContext *c, redisReply *reply, void *privdata) {
422 // ((void)c);
423 // /* Shift to detect execution order */
424 // __test_callback_flags <<= 8;
425 // __test_callback_flags |= (long)privdata;
426 // if (reply) freeReplyObject(reply);
427 // }
428 //
429 // static redisContext *__connect_nonblock() {
430 // /* Reset callback flags */
431 // __test_callback_flags = 0;
432 // return redisConnectNonBlock("127.0.0.1", 6379, NULL);
433 // }
434 //
435 // static void test_nonblocking_connection() {
436 // redisContext *c;
437 // int wdone = 0;
438 //
439 // test("Calls command callback when command is issued: ");
440 // c = __connect_nonblock();
441 // redisSetCommandCallback(c,__test_callback,(void*)1);
442 // redisCommand(c,"PING");
443 // test_cond(__test_callback_flags == 1);
444 // redisFree(c);
445 //
446 // test("Calls disconnect callback on redisDisconnect: ");
447 // c = __connect_nonblock();
448 // redisSetDisconnectCallback(c,__test_callback,(void*)2);
449 // redisDisconnect(c);
450 // test_cond(__test_callback_flags == 2);
451 // redisFree(c);
452 //
453 // test("Calls disconnect callback and free callback on redisFree: ");
454 // c = __connect_nonblock();
455 // redisSetDisconnectCallback(c,__test_callback,(void*)2);
456 // redisSetFreeCallback(c,__test_callback,(void*)4);
457 // redisFree(c);
458 // test_cond(__test_callback_flags == ((2 << 8) | 4));
459 //
460 // test("redisBufferWrite against empty write buffer: ");
461 // c = __connect_nonblock();
462 // test_cond(redisBufferWrite(c,&wdone) == REDIS_OK && wdone == 1);
463 // redisFree(c);
464 //
465 // test("redisBufferWrite against not yet connected fd: ");
466 // c = __connect_nonblock();
467 // redisCommand(c,"PING");
468 // test_cond(redisBufferWrite(c,NULL) == REDIS_ERR &&
469 // strncmp(c->error,"write:",6) == 0);
470 // redisFree(c);
471 //
472 // test("redisBufferWrite against closed fd: ");
473 // c = __connect_nonblock();
474 // redisCommand(c,"PING");
475 // redisDisconnect(c);
476 // test_cond(redisBufferWrite(c,NULL) == REDIS_ERR &&
477 // strncmp(c->error,"write:",6) == 0);
478 // redisFree(c);
479 //
480 // test("Process callbacks in the right sequence: ");
481 // c = __connect_nonblock();
482 // redisCommandWithCallback(c,__test_reply_callback,(void*)1,"PING");
483 // redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING");
484 // redisCommandWithCallback(c,__test_reply_callback,(void*)3,"PING");
485 //
486 // /* Write output buffer */
487 // wdone = 0;
488 // while(!wdone) {
489 // usleep(500);
490 // redisBufferWrite(c,&wdone);
491 // }
492 //
493 // /* Read until at least one callback is executed (the 3 replies will
494 // * arrive in a single packet, causing all callbacks to be executed in
495 // * a single pass). */
496 // while(__test_callback_flags == 0) {
497 // assert(redisBufferRead(c) == REDIS_OK);
498 // redisProcessCallbacks(c);
499 // }
500 // test_cond(__test_callback_flags == 0x010203);
501 // redisFree(c);
502 //
503 // test("redisDisconnect executes pending callbacks with NULL reply: ");
504 // c = __connect_nonblock();
505 // redisSetDisconnectCallback(c,__test_callback,(void*)1);
506 // redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING");
507 // redisDisconnect(c);
508 // test_cond(__test_callback_flags == 0x0201);
509 // redisFree(c);
510 // }
511
512 int main(int argc, char **argv) {
513 if (argc > 1) {
514 if (strcmp(argv[1],"-s") == 0)
515 use_unix = 1;
516 }
517
518 signal(SIGPIPE, SIG_IGN);
519 test_format_commands();
520 test_blocking_connection();
521 test_reply_reader();
522 // test_nonblocking_connection();
523 test_throughput();
524 cleanup();
525
526 if (fails == 0) {
527 printf("ALL TESTS PASSED\n");
528 } else {
529 printf("*** %d TESTS FAILED ***\n", fails);
530 }
531 return 0;
532 }