]>
Commit | Line | Data |
---|---|---|
24f753a8 PN |
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> | |
57c9babd | 8 | #include <signal.h> |
24f753a8 PN |
9 | |
10 | #include "hiredis.h" | |
11 | ||
12 | /* The following lines make up our testing "framework" :) */ | |
13 | static int tests = 0, fails = 0; | |
14 | #define test(_s) { printf("#%02d ", ++tests); printf(_s); } | |
15 | #define test_cond(_c) if(_c) printf("PASSED\n"); else {printf("FAILED\n"); fails++;} | |
16 | ||
17 | static long long usec(void) { | |
18 | struct timeval tv; | |
19 | gettimeofday(&tv,NULL); | |
20 | return (((long long)tv.tv_sec)*1000000)+tv.tv_usec; | |
21 | } | |
22 | ||
23 | static int use_unix = 0; | |
24 | static redisContext *blocking_context = NULL; | |
25 | static void __connect(redisContext **target) { | |
26 | *target = blocking_context = (use_unix ? | |
27 | redisConnectUnix("/tmp/redis.sock") : redisConnect((char*)"127.0.0.1", 6379)); | |
28 | if (blocking_context->err) { | |
29 | printf("Connection error: %s\n", blocking_context->errstr); | |
30 | exit(1); | |
31 | } | |
32 | } | |
33 | ||
34 | static void test_format_commands() { | |
35 | char *cmd; | |
36 | int len; | |
37 | ||
38 | test("Format command without interpolation: "); | |
39 | len = redisFormatCommand(&cmd,"SET foo bar"); | |
40 | test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && | |
41 | len == 4+4+(3+2)+4+(3+2)+4+(3+2)); | |
42 | free(cmd); | |
43 | ||
44 | test("Format command with %%s string interpolation: "); | |
45 | len = redisFormatCommand(&cmd,"SET %s %s","foo","bar"); | |
46 | test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && | |
47 | len == 4+4+(3+2)+4+(3+2)+4+(3+2)); | |
48 | free(cmd); | |
49 | ||
50 | test("Format command with %%b string interpolation: "); | |
51 | len = redisFormatCommand(&cmd,"SET %b %b","foo",3,"b\0r",3); | |
52 | 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 && | |
53 | len == 4+4+(3+2)+4+(3+2)+4+(3+2)); | |
54 | free(cmd); | |
55 | ||
56 | const char *argv[3]; | |
57 | argv[0] = "SET"; | |
58 | argv[1] = "foo"; | |
59 | argv[2] = "bar"; | |
60 | size_t lens[3] = { 3, 3, 3 }; | |
61 | int argc = 3; | |
62 | ||
63 | test("Format command by passing argc/argv without lengths: "); | |
64 | len = redisFormatCommandArgv(&cmd,argc,argv,NULL); | |
65 | test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && | |
66 | len == 4+4+(3+2)+4+(3+2)+4+(3+2)); | |
67 | free(cmd); | |
68 | ||
69 | test("Format command by passing argc/argv with lengths: "); | |
70 | len = redisFormatCommandArgv(&cmd,argc,argv,lens); | |
71 | test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && | |
72 | len == 4+4+(3+2)+4+(3+2)+4+(3+2)); | |
73 | free(cmd); | |
74 | } | |
75 | ||
76 | static void test_blocking_connection() { | |
77 | redisContext *c; | |
78 | redisReply *reply; | |
79 | ||
80 | __connect(&c); | |
81 | test("Returns I/O error when the connection is lost: "); | |
82 | reply = redisCommand(c,"QUIT"); | |
83 | test_cond(strcasecmp(reply->str,"OK") == 0 && redisCommand(c,"PING") == NULL); | |
84 | ||
85 | /* Two conditions may happen, depending on the type of connection. | |
86 | * When connected via TCP, the socket will not yet be aware of the closed | |
87 | * connection and the write(2) call will succeed, but the read(2) will | |
88 | * result in an EOF. When connected via Unix sockets, the socket will be | |
89 | * immediately aware that it was closed and fail on the write(2) call. */ | |
90 | if (use_unix) { | |
91 | fprintf(stderr,"Error: %s\n", c->errstr); | |
92 | assert(c->err == REDIS_ERR_IO && | |
93 | strcmp(c->errstr,"Broken pipe") == 0); | |
94 | } else { | |
95 | fprintf(stderr,"Error: %s\n", c->errstr); | |
96 | assert(c->err == REDIS_ERR_EOF && | |
97 | strcmp(c->errstr,"Server closed the connection") == 0); | |
98 | } | |
99 | freeReplyObject(reply); | |
100 | redisFree(c); | |
101 | ||
102 | __connect(&c); /* reconnect */ | |
103 | test("Is able to deliver commands: "); | |
104 | reply = redisCommand(c,"PING"); | |
105 | test_cond(reply->type == REDIS_REPLY_STATUS && | |
106 | strcasecmp(reply->str,"pong") == 0) | |
107 | freeReplyObject(reply); | |
108 | ||
109 | /* Switch to DB 9 for testing, now that we know we can chat. */ | |
110 | reply = redisCommand(c,"SELECT 9"); | |
111 | freeReplyObject(reply); | |
112 | ||
113 | /* Make sure the DB is emtpy */ | |
114 | reply = redisCommand(c,"DBSIZE"); | |
115 | if (reply->type != REDIS_REPLY_INTEGER || | |
116 | reply->integer != 0) { | |
117 | printf("Sorry DB 9 is not empty, test can not continue\n"); | |
118 | exit(1); | |
119 | } else { | |
120 | printf("DB 9 is empty... test can continue\n"); | |
121 | } | |
122 | freeReplyObject(reply); | |
123 | ||
124 | test("Is a able to send commands verbatim: "); | |
125 | reply = redisCommand(c,"SET foo bar"); | |
126 | test_cond (reply->type == REDIS_REPLY_STATUS && | |
127 | strcasecmp(reply->str,"ok") == 0) | |
128 | freeReplyObject(reply); | |
129 | ||
130 | test("%%s String interpolation works: "); | |
131 | reply = redisCommand(c,"SET %s %s","foo","hello world"); | |
132 | freeReplyObject(reply); | |
133 | reply = redisCommand(c,"GET foo"); | |
134 | test_cond(reply->type == REDIS_REPLY_STRING && | |
135 | strcmp(reply->str,"hello world") == 0); | |
136 | freeReplyObject(reply); | |
137 | ||
138 | test("%%b String interpolation works: "); | |
139 | reply = redisCommand(c,"SET %b %b","foo",3,"hello\x00world",11); | |
140 | freeReplyObject(reply); | |
141 | reply = redisCommand(c,"GET foo"); | |
142 | test_cond(reply->type == REDIS_REPLY_STRING && | |
143 | memcmp(reply->str,"hello\x00world",11) == 0) | |
144 | ||
145 | test("Binary reply length is correct: "); | |
146 | test_cond(reply->len == 11) | |
147 | freeReplyObject(reply); | |
148 | ||
149 | test("Can parse nil replies: "); | |
150 | reply = redisCommand(c,"GET nokey"); | |
151 | test_cond(reply->type == REDIS_REPLY_NIL) | |
152 | freeReplyObject(reply); | |
153 | ||
154 | /* test 7 */ | |
155 | test("Can parse integer replies: "); | |
156 | reply = redisCommand(c,"INCR mycounter"); | |
157 | test_cond(reply->type == REDIS_REPLY_INTEGER && reply->integer == 1) | |
158 | freeReplyObject(reply); | |
159 | ||
160 | test("Can parse multi bulk replies: "); | |
161 | freeReplyObject(redisCommand(c,"LPUSH mylist foo")); | |
162 | freeReplyObject(redisCommand(c,"LPUSH mylist bar")); | |
163 | reply = redisCommand(c,"LRANGE mylist 0 -1"); | |
164 | test_cond(reply->type == REDIS_REPLY_ARRAY && | |
165 | reply->elements == 2 && | |
166 | !memcmp(reply->element[0]->str,"bar",3) && | |
167 | !memcmp(reply->element[1]->str,"foo",3)) | |
168 | freeReplyObject(reply); | |
169 | ||
170 | /* m/e with multi bulk reply *before* other reply. | |
171 | * specifically test ordering of reply items to parse. */ | |
172 | test("Can handle nested multi bulk replies: "); | |
173 | freeReplyObject(redisCommand(c,"MULTI")); | |
174 | freeReplyObject(redisCommand(c,"LRANGE mylist 0 -1")); | |
175 | freeReplyObject(redisCommand(c,"PING")); | |
176 | reply = (redisCommand(c,"EXEC")); | |
177 | test_cond(reply->type == REDIS_REPLY_ARRAY && | |
178 | reply->elements == 2 && | |
179 | reply->element[0]->type == REDIS_REPLY_ARRAY && | |
180 | reply->element[0]->elements == 2 && | |
181 | !memcmp(reply->element[0]->element[0]->str,"bar",3) && | |
182 | !memcmp(reply->element[0]->element[1]->str,"foo",3) && | |
183 | reply->element[1]->type == REDIS_REPLY_STATUS && | |
184 | strcasecmp(reply->element[1]->str,"pong") == 0); | |
185 | freeReplyObject(reply); | |
186 | } | |
187 | ||
188 | static void test_reply_reader() { | |
189 | void *reader; | |
afc156c2 | 190 | void *reply; |
24f753a8 PN |
191 | char *err; |
192 | int ret; | |
193 | ||
194 | test("Error handling in reply parser: "); | |
afc156c2 | 195 | reader = redisReplyReaderCreate(); |
24f753a8 PN |
196 | redisReplyReaderFeed(reader,(char*)"@foo\r\n",6); |
197 | ret = redisReplyReaderGetReply(reader,NULL); | |
198 | err = redisReplyReaderGetError(reader); | |
199 | test_cond(ret == REDIS_ERR && | |
200 | strcasecmp(err,"protocol error, got \"@\" as reply type byte") == 0); | |
201 | redisReplyReaderFree(reader); | |
202 | ||
203 | /* when the reply already contains multiple items, they must be free'd | |
204 | * on an error. valgrind will bark when this doesn't happen. */ | |
205 | test("Memory cleanup in reply parser: "); | |
afc156c2 | 206 | reader = redisReplyReaderCreate(); |
24f753a8 PN |
207 | redisReplyReaderFeed(reader,(char*)"*2\r\n",4); |
208 | redisReplyReaderFeed(reader,(char*)"$5\r\nhello\r\n",11); | |
209 | redisReplyReaderFeed(reader,(char*)"@foo\r\n",6); | |
210 | ret = redisReplyReaderGetReply(reader,NULL); | |
211 | err = redisReplyReaderGetError(reader); | |
212 | test_cond(ret == REDIS_ERR && | |
213 | strcasecmp(err,"protocol error, got \"@\" as reply type byte") == 0); | |
214 | redisReplyReaderFree(reader); | |
afc156c2 PN |
215 | |
216 | test("Works with NULL functions for reply: "); | |
217 | reader = redisReplyReaderCreate(); | |
218 | redisReplyReaderSetReplyObjectFunctions(reader,NULL); | |
219 | redisReplyReaderFeed(reader,(char*)"+OK\r\n",5); | |
220 | ret = redisReplyReaderGetReply(reader,&reply); | |
221 | test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS); | |
222 | redisReplyReaderFree(reader); | |
57c9babd PN |
223 | |
224 | test("Works when a single newline (\\r\\n) covers two calls to feed: "); | |
225 | reader = redisReplyReaderCreate(); | |
226 | redisReplyReaderSetReplyObjectFunctions(reader,NULL); | |
227 | redisReplyReaderFeed(reader,(char*)"+OK\r",4); | |
228 | ret = redisReplyReaderGetReply(reader,&reply); | |
229 | assert(ret == REDIS_OK && reply == NULL); | |
230 | redisReplyReaderFeed(reader,(char*)"\n",1); | |
231 | ret = redisReplyReaderGetReply(reader,&reply); | |
232 | test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS); | |
233 | redisReplyReaderFree(reader); | |
24f753a8 PN |
234 | } |
235 | ||
236 | static void test_throughput() { | |
237 | int i; | |
238 | long long t1, t2; | |
239 | redisContext *c = blocking_context; | |
240 | redisReply **replies; | |
241 | ||
242 | test("Throughput:\n"); | |
243 | for (i = 0; i < 500; i++) | |
244 | freeReplyObject(redisCommand(c,"LPUSH mylist foo")); | |
245 | ||
246 | replies = malloc(sizeof(redisReply*)*1000); | |
247 | t1 = usec(); | |
248 | for (i = 0; i < 1000; i++) { | |
249 | replies[i] = redisCommand(c,"PING"); | |
250 | assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS); | |
251 | } | |
252 | t2 = usec(); | |
253 | for (i = 0; i < 1000; i++) freeReplyObject(replies[i]); | |
254 | free(replies); | |
255 | printf("\t(1000x PING: %.2fs)\n", (t2-t1)/1000000.0); | |
256 | ||
257 | replies = malloc(sizeof(redisReply*)*1000); | |
258 | t1 = usec(); | |
259 | for (i = 0; i < 1000; i++) { | |
260 | replies[i] = redisCommand(c,"LRANGE mylist 0 499"); | |
261 | assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY); | |
262 | assert(replies[i] != NULL && replies[i]->elements == 500); | |
263 | } | |
264 | t2 = usec(); | |
265 | for (i = 0; i < 1000; i++) freeReplyObject(replies[i]); | |
266 | free(replies); | |
267 | printf("\t(1000x LRANGE with 500 elements: %.2fs)\n", (t2-t1)/1000000.0); | |
268 | } | |
269 | ||
270 | static void cleanup() { | |
271 | redisContext *c = blocking_context; | |
272 | redisReply *reply; | |
273 | ||
274 | /* Make sure we're on DB 9 */ | |
275 | reply = redisCommand(c,"SELECT 9"); | |
276 | assert(reply != NULL); freeReplyObject(reply); | |
277 | reply = redisCommand(c,"FLUSHDB"); | |
278 | assert(reply != NULL); freeReplyObject(reply); | |
279 | redisFree(c); | |
280 | } | |
281 | ||
282 | // static long __test_callback_flags = 0; | |
283 | // static void __test_callback(redisContext *c, void *privdata) { | |
284 | // ((void)c); | |
285 | // /* Shift to detect execution order */ | |
286 | // __test_callback_flags <<= 8; | |
287 | // __test_callback_flags |= (long)privdata; | |
288 | // } | |
289 | // | |
290 | // static void __test_reply_callback(redisContext *c, redisReply *reply, void *privdata) { | |
291 | // ((void)c); | |
292 | // /* Shift to detect execution order */ | |
293 | // __test_callback_flags <<= 8; | |
294 | // __test_callback_flags |= (long)privdata; | |
295 | // if (reply) freeReplyObject(reply); | |
296 | // } | |
297 | // | |
298 | // static redisContext *__connect_nonblock() { | |
299 | // /* Reset callback flags */ | |
300 | // __test_callback_flags = 0; | |
301 | // return redisConnectNonBlock("127.0.0.1", 6379, NULL); | |
302 | // } | |
303 | // | |
304 | // static void test_nonblocking_connection() { | |
305 | // redisContext *c; | |
306 | // int wdone = 0; | |
307 | // | |
308 | // test("Calls command callback when command is issued: "); | |
309 | // c = __connect_nonblock(); | |
310 | // redisSetCommandCallback(c,__test_callback,(void*)1); | |
311 | // redisCommand(c,"PING"); | |
312 | // test_cond(__test_callback_flags == 1); | |
313 | // redisFree(c); | |
314 | // | |
315 | // test("Calls disconnect callback on redisDisconnect: "); | |
316 | // c = __connect_nonblock(); | |
317 | // redisSetDisconnectCallback(c,__test_callback,(void*)2); | |
318 | // redisDisconnect(c); | |
319 | // test_cond(__test_callback_flags == 2); | |
320 | // redisFree(c); | |
321 | // | |
322 | // test("Calls disconnect callback and free callback on redisFree: "); | |
323 | // c = __connect_nonblock(); | |
324 | // redisSetDisconnectCallback(c,__test_callback,(void*)2); | |
325 | // redisSetFreeCallback(c,__test_callback,(void*)4); | |
326 | // redisFree(c); | |
327 | // test_cond(__test_callback_flags == ((2 << 8) | 4)); | |
328 | // | |
329 | // test("redisBufferWrite against empty write buffer: "); | |
330 | // c = __connect_nonblock(); | |
331 | // test_cond(redisBufferWrite(c,&wdone) == REDIS_OK && wdone == 1); | |
332 | // redisFree(c); | |
333 | // | |
334 | // test("redisBufferWrite against not yet connected fd: "); | |
335 | // c = __connect_nonblock(); | |
336 | // redisCommand(c,"PING"); | |
337 | // test_cond(redisBufferWrite(c,NULL) == REDIS_ERR && | |
338 | // strncmp(c->error,"write:",6) == 0); | |
339 | // redisFree(c); | |
340 | // | |
341 | // test("redisBufferWrite against closed fd: "); | |
342 | // c = __connect_nonblock(); | |
343 | // redisCommand(c,"PING"); | |
344 | // redisDisconnect(c); | |
345 | // test_cond(redisBufferWrite(c,NULL) == REDIS_ERR && | |
346 | // strncmp(c->error,"write:",6) == 0); | |
347 | // redisFree(c); | |
348 | // | |
349 | // test("Process callbacks in the right sequence: "); | |
350 | // c = __connect_nonblock(); | |
351 | // redisCommandWithCallback(c,__test_reply_callback,(void*)1,"PING"); | |
352 | // redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING"); | |
353 | // redisCommandWithCallback(c,__test_reply_callback,(void*)3,"PING"); | |
354 | // | |
355 | // /* Write output buffer */ | |
356 | // wdone = 0; | |
357 | // while(!wdone) { | |
358 | // usleep(500); | |
359 | // redisBufferWrite(c,&wdone); | |
360 | // } | |
361 | // | |
362 | // /* Read until at least one callback is executed (the 3 replies will | |
363 | // * arrive in a single packet, causing all callbacks to be executed in | |
364 | // * a single pass). */ | |
365 | // while(__test_callback_flags == 0) { | |
366 | // assert(redisBufferRead(c) == REDIS_OK); | |
367 | // redisProcessCallbacks(c); | |
368 | // } | |
369 | // test_cond(__test_callback_flags == 0x010203); | |
370 | // redisFree(c); | |
371 | // | |
372 | // test("redisDisconnect executes pending callbacks with NULL reply: "); | |
373 | // c = __connect_nonblock(); | |
374 | // redisSetDisconnectCallback(c,__test_callback,(void*)1); | |
375 | // redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING"); | |
376 | // redisDisconnect(c); | |
377 | // test_cond(__test_callback_flags == 0x0201); | |
378 | // redisFree(c); | |
379 | // } | |
380 | ||
381 | int main(int argc, char **argv) { | |
382 | if (argc > 1) { | |
383 | if (strcmp(argv[1],"-s") == 0) | |
384 | use_unix = 1; | |
385 | } | |
386 | ||
387 | signal(SIGPIPE, SIG_IGN); | |
388 | test_format_commands(); | |
389 | test_blocking_connection(); | |
390 | test_reply_reader(); | |
391 | // test_nonblocking_connection(); | |
392 | test_throughput(); | |
393 | cleanup(); | |
394 | ||
395 | if (fails == 0) { | |
396 | printf("ALL TESTS PASSED\n"); | |
397 | } else { | |
398 | printf("*** %d TESTS FAILED ***\n", fails); | |
399 | } | |
400 | return 0; | |
401 | } |