hiredis 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,77 @@
1
+ /* SDSLib, A C dynamic strings library
2
+ *
3
+ * Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
4
+ * All rights reserved.
5
+ *
6
+ * Redistribution and use in source and binary forms, with or without
7
+ * modification, are permitted provided that the following conditions are met:
8
+ *
9
+ * * Redistributions of source code must retain the above copyright notice,
10
+ * this list of conditions and the following disclaimer.
11
+ * * Redistributions in binary form must reproduce the above copyright
12
+ * notice, this list of conditions and the following disclaimer in the
13
+ * documentation and/or other materials provided with the distribution.
14
+ * * Neither the name of Redis nor the names of its contributors may be used
15
+ * to endorse or promote products derived from this software without
16
+ * specific prior written permission.
17
+ *
18
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28
+ * POSSIBILITY OF SUCH DAMAGE.
29
+ */
30
+
31
+ #ifndef __SDS_H
32
+ #define __SDS_H
33
+
34
+ #include <sys/types.h>
35
+ #include <stdarg.h>
36
+
37
+ typedef char *sds;
38
+
39
+ struct sdshdr {
40
+ int len;
41
+ int free;
42
+ char buf[];
43
+ };
44
+
45
+ sds sdsnewlen(const void *init, size_t initlen);
46
+ sds sdsnew(const char *init);
47
+ sds sdsempty();
48
+ size_t sdslen(const sds s);
49
+ sds sdsdup(const sds s);
50
+ void sdsfree(sds s);
51
+ size_t sdsavail(sds s);
52
+ sds sdscatlen(sds s, const void *t, size_t len);
53
+ sds sdscat(sds s, const char *t);
54
+ sds sdscpylen(sds s, char *t, size_t len);
55
+ sds sdscpy(sds s, char *t);
56
+
57
+ sds sdscatvprintf(sds s, const char *fmt, va_list ap);
58
+ #ifdef __GNUC__
59
+ sds sdscatprintf(sds s, const char *fmt, ...)
60
+ __attribute__((format(printf, 2, 3)));
61
+ #else
62
+ sds sdscatprintf(sds s, const char *fmt, ...);
63
+ #endif
64
+
65
+ sds sdstrim(sds s, const char *cset);
66
+ sds sdsrange(sds s, int start, int end);
67
+ void sdsupdatelen(sds s);
68
+ int sdscmp(sds s1, sds s2);
69
+ sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count);
70
+ void sdsfreesplitres(sds *tokens, int count);
71
+ void sdstolower(sds s);
72
+ void sdstoupper(sds s);
73
+ sds sdsfromlonglong(long long value);
74
+ sds sdscatrepr(sds s, char *p, size_t len);
75
+ sds *sdssplitargs(char *line, int *argc);
76
+
77
+ #endif
@@ -0,0 +1,400 @@
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
+
9
+ #include "hiredis.h"
10
+
11
+ /* The following lines make up our testing "framework" :) */
12
+ static int tests = 0, fails = 0;
13
+ #define test(_s) { printf("#%02d ", ++tests); printf(_s); }
14
+ #define test_cond(_c) if(_c) printf("PASSED\n"); else {printf("FAILED\n"); fails++;}
15
+
16
+ static long long usec(void) {
17
+ struct timeval tv;
18
+ gettimeofday(&tv,NULL);
19
+ return (((long long)tv.tv_sec)*1000000)+tv.tv_usec;
20
+ }
21
+
22
+ static int use_unix = 0;
23
+ static redisContext *blocking_context = NULL;
24
+ static void __connect(redisContext **target) {
25
+ *target = blocking_context = (use_unix ?
26
+ redisConnectUnix("/tmp/redis.sock") : redisConnect((char*)"127.0.0.1", 6379));
27
+ if (blocking_context->err) {
28
+ printf("Connection error: %s\n", blocking_context->errstr);
29
+ exit(1);
30
+ }
31
+ }
32
+
33
+ static void test_format_commands() {
34
+ char *cmd;
35
+ int len;
36
+
37
+ test("Format command without interpolation: ");
38
+ len = redisFormatCommand(&cmd,"SET foo bar");
39
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
40
+ len == 4+4+(3+2)+4+(3+2)+4+(3+2));
41
+ free(cmd);
42
+
43
+ test("Format command with %%s string interpolation: ");
44
+ len = redisFormatCommand(&cmd,"SET %s %s","foo","bar");
45
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
46
+ len == 4+4+(3+2)+4+(3+2)+4+(3+2));
47
+ free(cmd);
48
+
49
+ test("Format command with %%b string interpolation: ");
50
+ len = redisFormatCommand(&cmd,"SET %b %b","foo",3,"b\0r",3);
51
+ 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 &&
52
+ len == 4+4+(3+2)+4+(3+2)+4+(3+2));
53
+ free(cmd);
54
+
55
+ const char *argv[3];
56
+ argv[0] = "SET";
57
+ argv[1] = "foo";
58
+ argv[2] = "bar";
59
+ size_t lens[3] = { 3, 3, 3 };
60
+ int argc = 3;
61
+
62
+ test("Format command by passing argc/argv without lengths: ");
63
+ len = redisFormatCommandArgv(&cmd,argc,argv,NULL);
64
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
65
+ len == 4+4+(3+2)+4+(3+2)+4+(3+2));
66
+ free(cmd);
67
+
68
+ test("Format command by passing argc/argv with lengths: ");
69
+ len = redisFormatCommandArgv(&cmd,argc,argv,lens);
70
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
71
+ len == 4+4+(3+2)+4+(3+2)+4+(3+2));
72
+ free(cmd);
73
+ }
74
+
75
+ static void test_blocking_connection() {
76
+ redisContext *c;
77
+ redisReply *reply;
78
+
79
+ __connect(&c);
80
+ test("Returns I/O error when the connection is lost: ");
81
+ reply = redisCommand(c,"QUIT");
82
+ test_cond(strcasecmp(reply->str,"OK") == 0 && redisCommand(c,"PING") == NULL);
83
+
84
+ /* Two conditions may happen, depending on the type of connection.
85
+ * When connected via TCP, the socket will not yet be aware of the closed
86
+ * connection and the write(2) call will succeed, but the read(2) will
87
+ * result in an EOF. When connected via Unix sockets, the socket will be
88
+ * immediately aware that it was closed and fail on the write(2) call. */
89
+ if (use_unix) {
90
+ fprintf(stderr,"Error: %s\n", c->errstr);
91
+ assert(c->err == REDIS_ERR_IO &&
92
+ strcmp(c->errstr,"Broken pipe") == 0);
93
+ } else {
94
+ fprintf(stderr,"Error: %s\n", c->errstr);
95
+ assert(c->err == REDIS_ERR_EOF &&
96
+ strcmp(c->errstr,"Server closed the connection") == 0);
97
+ }
98
+ freeReplyObject(reply);
99
+ redisFree(c);
100
+
101
+ __connect(&c); /* reconnect */
102
+ test("Is able to deliver commands: ");
103
+ reply = redisCommand(c,"PING");
104
+ test_cond(reply->type == REDIS_REPLY_STATUS &&
105
+ strcasecmp(reply->str,"pong") == 0)
106
+ freeReplyObject(reply);
107
+
108
+ /* Switch to DB 9 for testing, now that we know we can chat. */
109
+ reply = redisCommand(c,"SELECT 9");
110
+ freeReplyObject(reply);
111
+
112
+ /* Make sure the DB is emtpy */
113
+ reply = redisCommand(c,"DBSIZE");
114
+ if (reply->type != REDIS_REPLY_INTEGER ||
115
+ reply->integer != 0) {
116
+ printf("Sorry DB 9 is not empty, test can not continue\n");
117
+ exit(1);
118
+ } else {
119
+ printf("DB 9 is empty... test can continue\n");
120
+ }
121
+ freeReplyObject(reply);
122
+
123
+ test("Is a able to send commands verbatim: ");
124
+ reply = redisCommand(c,"SET foo bar");
125
+ test_cond (reply->type == REDIS_REPLY_STATUS &&
126
+ strcasecmp(reply->str,"ok") == 0)
127
+ freeReplyObject(reply);
128
+
129
+ test("%%s String interpolation works: ");
130
+ reply = redisCommand(c,"SET %s %s","foo","hello world");
131
+ freeReplyObject(reply);
132
+ reply = redisCommand(c,"GET foo");
133
+ test_cond(reply->type == REDIS_REPLY_STRING &&
134
+ strcmp(reply->str,"hello world") == 0);
135
+ freeReplyObject(reply);
136
+
137
+ test("%%b String interpolation works: ");
138
+ reply = redisCommand(c,"SET %b %b","foo",3,"hello\x00world",11);
139
+ freeReplyObject(reply);
140
+ reply = redisCommand(c,"GET foo");
141
+ test_cond(reply->type == REDIS_REPLY_STRING &&
142
+ memcmp(reply->str,"hello\x00world",11) == 0)
143
+
144
+ test("Binary reply length is correct: ");
145
+ test_cond(reply->len == 11)
146
+ freeReplyObject(reply);
147
+
148
+ test("Can parse nil replies: ");
149
+ reply = redisCommand(c,"GET nokey");
150
+ test_cond(reply->type == REDIS_REPLY_NIL)
151
+ freeReplyObject(reply);
152
+
153
+ /* test 7 */
154
+ test("Can parse integer replies: ");
155
+ reply = redisCommand(c,"INCR mycounter");
156
+ test_cond(reply->type == REDIS_REPLY_INTEGER && reply->integer == 1)
157
+ freeReplyObject(reply);
158
+
159
+ test("Can parse multi bulk replies: ");
160
+ freeReplyObject(redisCommand(c,"LPUSH mylist foo"));
161
+ freeReplyObject(redisCommand(c,"LPUSH mylist bar"));
162
+ reply = redisCommand(c,"LRANGE mylist 0 -1");
163
+ test_cond(reply->type == REDIS_REPLY_ARRAY &&
164
+ reply->elements == 2 &&
165
+ !memcmp(reply->element[0]->str,"bar",3) &&
166
+ !memcmp(reply->element[1]->str,"foo",3))
167
+ freeReplyObject(reply);
168
+
169
+ /* m/e with multi bulk reply *before* other reply.
170
+ * specifically test ordering of reply items to parse. */
171
+ test("Can handle nested multi bulk replies: ");
172
+ freeReplyObject(redisCommand(c,"MULTI"));
173
+ freeReplyObject(redisCommand(c,"LRANGE mylist 0 -1"));
174
+ freeReplyObject(redisCommand(c,"PING"));
175
+ reply = (redisCommand(c,"EXEC"));
176
+ test_cond(reply->type == REDIS_REPLY_ARRAY &&
177
+ reply->elements == 2 &&
178
+ reply->element[0]->type == REDIS_REPLY_ARRAY &&
179
+ reply->element[0]->elements == 2 &&
180
+ !memcmp(reply->element[0]->element[0]->str,"bar",3) &&
181
+ !memcmp(reply->element[0]->element[1]->str,"foo",3) &&
182
+ reply->element[1]->type == REDIS_REPLY_STATUS &&
183
+ strcasecmp(reply->element[1]->str,"pong") == 0);
184
+ freeReplyObject(reply);
185
+ }
186
+
187
+ static void test_reply_reader() {
188
+ void *reader;
189
+ void *reply;
190
+ char *err;
191
+ int ret;
192
+
193
+ test("Error handling in reply parser: ");
194
+ reader = redisReplyReaderCreate();
195
+ redisReplyReaderFeed(reader,(char*)"@foo\r\n",6);
196
+ ret = redisReplyReaderGetReply(reader,NULL);
197
+ err = redisReplyReaderGetError(reader);
198
+ test_cond(ret == REDIS_ERR &&
199
+ strcasecmp(err,"protocol error, got \"@\" as reply type byte") == 0);
200
+ redisReplyReaderFree(reader);
201
+
202
+ /* when the reply already contains multiple items, they must be free'd
203
+ * on an error. valgrind will bark when this doesn't happen. */
204
+ test("Memory cleanup in reply parser: ");
205
+ reader = redisReplyReaderCreate();
206
+ redisReplyReaderFeed(reader,(char*)"*2\r\n",4);
207
+ redisReplyReaderFeed(reader,(char*)"$5\r\nhello\r\n",11);
208
+ redisReplyReaderFeed(reader,(char*)"@foo\r\n",6);
209
+ ret = redisReplyReaderGetReply(reader,NULL);
210
+ err = redisReplyReaderGetError(reader);
211
+ test_cond(ret == REDIS_ERR &&
212
+ strcasecmp(err,"protocol error, got \"@\" as reply type byte") == 0);
213
+ redisReplyReaderFree(reader);
214
+
215
+ test("Works with NULL functions for reply: ");
216
+ reader = redisReplyReaderCreate();
217
+ redisReplyReaderSetReplyObjectFunctions(reader,NULL);
218
+ redisReplyReaderFeed(reader,(char*)"+OK\r\n",5);
219
+ ret = redisReplyReaderGetReply(reader,&reply);
220
+ test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS);
221
+ redisReplyReaderFree(reader);
222
+
223
+ test("Works when a single newline (\\r\\n) covers two calls to feed: ");
224
+ reader = redisReplyReaderCreate();
225
+ redisReplyReaderSetReplyObjectFunctions(reader,NULL);
226
+ redisReplyReaderFeed(reader,(char*)"+OK\r",4);
227
+ ret = redisReplyReaderGetReply(reader,&reply);
228
+ assert(ret == REDIS_OK && reply == NULL);
229
+ redisReplyReaderFeed(reader,(char*)"\n",1);
230
+ ret = redisReplyReaderGetReply(reader,&reply);
231
+ test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS);
232
+ redisReplyReaderFree(reader);
233
+ }
234
+
235
+ static void test_throughput() {
236
+ int i;
237
+ long long t1, t2;
238
+ redisContext *c = blocking_context;
239
+ redisReply **replies;
240
+
241
+ test("Throughput:\n");
242
+ for (i = 0; i < 500; i++)
243
+ freeReplyObject(redisCommand(c,"LPUSH mylist foo"));
244
+
245
+ replies = malloc(sizeof(redisReply*)*1000);
246
+ t1 = usec();
247
+ for (i = 0; i < 1000; i++) {
248
+ replies[i] = redisCommand(c,"PING");
249
+ assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS);
250
+ }
251
+ t2 = usec();
252
+ for (i = 0; i < 1000; i++) freeReplyObject(replies[i]);
253
+ free(replies);
254
+ printf("\t(1000x PING: %.2fs)\n", (t2-t1)/1000000.0);
255
+
256
+ replies = malloc(sizeof(redisReply*)*1000);
257
+ t1 = usec();
258
+ for (i = 0; i < 1000; i++) {
259
+ replies[i] = redisCommand(c,"LRANGE mylist 0 499");
260
+ assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY);
261
+ assert(replies[i] != NULL && replies[i]->elements == 500);
262
+ }
263
+ t2 = usec();
264
+ for (i = 0; i < 1000; i++) freeReplyObject(replies[i]);
265
+ free(replies);
266
+ printf("\t(1000x LRANGE with 500 elements: %.2fs)\n", (t2-t1)/1000000.0);
267
+ }
268
+
269
+ static void cleanup() {
270
+ redisContext *c = blocking_context;
271
+ redisReply *reply;
272
+
273
+ /* Make sure we're on DB 9 */
274
+ reply = redisCommand(c,"SELECT 9");
275
+ assert(reply != NULL); freeReplyObject(reply);
276
+ reply = redisCommand(c,"FLUSHDB");
277
+ assert(reply != NULL); freeReplyObject(reply);
278
+ redisFree(c);
279
+ }
280
+
281
+ // static long __test_callback_flags = 0;
282
+ // static void __test_callback(redisContext *c, void *privdata) {
283
+ // ((void)c);
284
+ // /* Shift to detect execution order */
285
+ // __test_callback_flags <<= 8;
286
+ // __test_callback_flags |= (long)privdata;
287
+ // }
288
+ //
289
+ // static void __test_reply_callback(redisContext *c, redisReply *reply, void *privdata) {
290
+ // ((void)c);
291
+ // /* Shift to detect execution order */
292
+ // __test_callback_flags <<= 8;
293
+ // __test_callback_flags |= (long)privdata;
294
+ // if (reply) freeReplyObject(reply);
295
+ // }
296
+ //
297
+ // static redisContext *__connect_nonblock() {
298
+ // /* Reset callback flags */
299
+ // __test_callback_flags = 0;
300
+ // return redisConnectNonBlock("127.0.0.1", 6379, NULL);
301
+ // }
302
+ //
303
+ // static void test_nonblocking_connection() {
304
+ // redisContext *c;
305
+ // int wdone = 0;
306
+ //
307
+ // test("Calls command callback when command is issued: ");
308
+ // c = __connect_nonblock();
309
+ // redisSetCommandCallback(c,__test_callback,(void*)1);
310
+ // redisCommand(c,"PING");
311
+ // test_cond(__test_callback_flags == 1);
312
+ // redisFree(c);
313
+ //
314
+ // test("Calls disconnect callback on redisDisconnect: ");
315
+ // c = __connect_nonblock();
316
+ // redisSetDisconnectCallback(c,__test_callback,(void*)2);
317
+ // redisDisconnect(c);
318
+ // test_cond(__test_callback_flags == 2);
319
+ // redisFree(c);
320
+ //
321
+ // test("Calls disconnect callback and free callback on redisFree: ");
322
+ // c = __connect_nonblock();
323
+ // redisSetDisconnectCallback(c,__test_callback,(void*)2);
324
+ // redisSetFreeCallback(c,__test_callback,(void*)4);
325
+ // redisFree(c);
326
+ // test_cond(__test_callback_flags == ((2 << 8) | 4));
327
+ //
328
+ // test("redisBufferWrite against empty write buffer: ");
329
+ // c = __connect_nonblock();
330
+ // test_cond(redisBufferWrite(c,&wdone) == REDIS_OK && wdone == 1);
331
+ // redisFree(c);
332
+ //
333
+ // test("redisBufferWrite against not yet connected fd: ");
334
+ // c = __connect_nonblock();
335
+ // redisCommand(c,"PING");
336
+ // test_cond(redisBufferWrite(c,NULL) == REDIS_ERR &&
337
+ // strncmp(c->error,"write:",6) == 0);
338
+ // redisFree(c);
339
+ //
340
+ // test("redisBufferWrite against closed fd: ");
341
+ // c = __connect_nonblock();
342
+ // redisCommand(c,"PING");
343
+ // redisDisconnect(c);
344
+ // test_cond(redisBufferWrite(c,NULL) == REDIS_ERR &&
345
+ // strncmp(c->error,"write:",6) == 0);
346
+ // redisFree(c);
347
+ //
348
+ // test("Process callbacks in the right sequence: ");
349
+ // c = __connect_nonblock();
350
+ // redisCommandWithCallback(c,__test_reply_callback,(void*)1,"PING");
351
+ // redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING");
352
+ // redisCommandWithCallback(c,__test_reply_callback,(void*)3,"PING");
353
+ //
354
+ // /* Write output buffer */
355
+ // wdone = 0;
356
+ // while(!wdone) {
357
+ // usleep(500);
358
+ // redisBufferWrite(c,&wdone);
359
+ // }
360
+ //
361
+ // /* Read until at least one callback is executed (the 3 replies will
362
+ // * arrive in a single packet, causing all callbacks to be executed in
363
+ // * a single pass). */
364
+ // while(__test_callback_flags == 0) {
365
+ // assert(redisBufferRead(c) == REDIS_OK);
366
+ // redisProcessCallbacks(c);
367
+ // }
368
+ // test_cond(__test_callback_flags == 0x010203);
369
+ // redisFree(c);
370
+ //
371
+ // test("redisDisconnect executes pending callbacks with NULL reply: ");
372
+ // c = __connect_nonblock();
373
+ // redisSetDisconnectCallback(c,__test_callback,(void*)1);
374
+ // redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING");
375
+ // redisDisconnect(c);
376
+ // test_cond(__test_callback_flags == 0x0201);
377
+ // redisFree(c);
378
+ // }
379
+
380
+ int main(int argc, char **argv) {
381
+ if (argc > 1) {
382
+ if (strcmp(argv[1],"-s") == 0)
383
+ use_unix = 1;
384
+ }
385
+
386
+ signal(SIGPIPE, SIG_IGN);
387
+ test_format_commands();
388
+ test_blocking_connection();
389
+ test_reply_reader();
390
+ // test_nonblocking_connection();
391
+ test_throughput();
392
+ cleanup();
393
+
394
+ if (fails == 0) {
395
+ printf("ALL TESTS PASSED\n");
396
+ } else {
397
+ printf("*** %d TESTS FAILED ***\n", fails);
398
+ }
399
+ return 0;
400
+ }