hiredis 0.6.2 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/hiredis/version.rb +1 -1
- data/vendor/hiredis/COPYING +29 -0
- data/vendor/hiredis/Makefile +213 -0
- data/vendor/hiredis/async.c +716 -0
- data/vendor/hiredis/async.h +130 -0
- data/vendor/hiredis/dict.c +338 -0
- data/vendor/hiredis/dict.h +126 -0
- data/vendor/hiredis/fmacros.h +12 -0
- data/vendor/hiredis/hiredis.c +1006 -0
- data/vendor/hiredis/hiredis.h +199 -0
- data/vendor/hiredis/net.c +477 -0
- data/vendor/hiredis/net.h +49 -0
- data/vendor/hiredis/read.c +598 -0
- data/vendor/hiredis/read.h +111 -0
- data/vendor/hiredis/sds.c +1272 -0
- data/vendor/hiredis/sds.h +273 -0
- data/vendor/hiredis/sdsalloc.h +42 -0
- data/vendor/hiredis/test.c +923 -0
- data/vendor/hiredis/win32.h +42 -0
- metadata +19 -1
@@ -0,0 +1,273 @@
|
|
1
|
+
/* SDSLib 2.0 -- A C dynamic strings library
|
2
|
+
*
|
3
|
+
* Copyright (c) 2006-2015, Salvatore Sanfilippo <antirez at gmail dot com>
|
4
|
+
* Copyright (c) 2015, Oran Agra
|
5
|
+
* Copyright (c) 2015, Redis Labs, Inc
|
6
|
+
* All rights reserved.
|
7
|
+
*
|
8
|
+
* Redistribution and use in source and binary forms, with or without
|
9
|
+
* modification, are permitted provided that the following conditions are met:
|
10
|
+
*
|
11
|
+
* * Redistributions of source code must retain the above copyright notice,
|
12
|
+
* this list of conditions and the following disclaimer.
|
13
|
+
* * Redistributions in binary form must reproduce the above copyright
|
14
|
+
* notice, this list of conditions and the following disclaimer in the
|
15
|
+
* documentation and/or other materials provided with the distribution.
|
16
|
+
* * Neither the name of Redis nor the names of its contributors may be used
|
17
|
+
* to endorse or promote products derived from this software without
|
18
|
+
* specific prior written permission.
|
19
|
+
*
|
20
|
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
21
|
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
22
|
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
23
|
+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
24
|
+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
25
|
+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
26
|
+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
27
|
+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
28
|
+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
29
|
+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
30
|
+
* POSSIBILITY OF SUCH DAMAGE.
|
31
|
+
*/
|
32
|
+
|
33
|
+
#ifndef __SDS_H
|
34
|
+
#define __SDS_H
|
35
|
+
|
36
|
+
#define SDS_MAX_PREALLOC (1024*1024)
|
37
|
+
|
38
|
+
#include <sys/types.h>
|
39
|
+
#include <stdarg.h>
|
40
|
+
#include <stdint.h>
|
41
|
+
|
42
|
+
typedef char *sds;
|
43
|
+
|
44
|
+
/* Note: sdshdr5 is never used, we just access the flags byte directly.
|
45
|
+
* However is here to document the layout of type 5 SDS strings. */
|
46
|
+
struct __attribute__ ((__packed__)) sdshdr5 {
|
47
|
+
unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
|
48
|
+
char buf[];
|
49
|
+
};
|
50
|
+
struct __attribute__ ((__packed__)) sdshdr8 {
|
51
|
+
uint8_t len; /* used */
|
52
|
+
uint8_t alloc; /* excluding the header and null terminator */
|
53
|
+
unsigned char flags; /* 3 lsb of type, 5 unused bits */
|
54
|
+
char buf[];
|
55
|
+
};
|
56
|
+
struct __attribute__ ((__packed__)) sdshdr16 {
|
57
|
+
uint16_t len; /* used */
|
58
|
+
uint16_t alloc; /* excluding the header and null terminator */
|
59
|
+
unsigned char flags; /* 3 lsb of type, 5 unused bits */
|
60
|
+
char buf[];
|
61
|
+
};
|
62
|
+
struct __attribute__ ((__packed__)) sdshdr32 {
|
63
|
+
uint32_t len; /* used */
|
64
|
+
uint32_t alloc; /* excluding the header and null terminator */
|
65
|
+
unsigned char flags; /* 3 lsb of type, 5 unused bits */
|
66
|
+
char buf[];
|
67
|
+
};
|
68
|
+
struct __attribute__ ((__packed__)) sdshdr64 {
|
69
|
+
uint64_t len; /* used */
|
70
|
+
uint64_t alloc; /* excluding the header and null terminator */
|
71
|
+
unsigned char flags; /* 3 lsb of type, 5 unused bits */
|
72
|
+
char buf[];
|
73
|
+
};
|
74
|
+
|
75
|
+
#define SDS_TYPE_5 0
|
76
|
+
#define SDS_TYPE_8 1
|
77
|
+
#define SDS_TYPE_16 2
|
78
|
+
#define SDS_TYPE_32 3
|
79
|
+
#define SDS_TYPE_64 4
|
80
|
+
#define SDS_TYPE_MASK 7
|
81
|
+
#define SDS_TYPE_BITS 3
|
82
|
+
#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T)));
|
83
|
+
#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
|
84
|
+
#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)
|
85
|
+
|
86
|
+
static inline size_t sdslen(const sds s) {
|
87
|
+
unsigned char flags = s[-1];
|
88
|
+
switch(flags&SDS_TYPE_MASK) {
|
89
|
+
case SDS_TYPE_5:
|
90
|
+
return SDS_TYPE_5_LEN(flags);
|
91
|
+
case SDS_TYPE_8:
|
92
|
+
return SDS_HDR(8,s)->len;
|
93
|
+
case SDS_TYPE_16:
|
94
|
+
return SDS_HDR(16,s)->len;
|
95
|
+
case SDS_TYPE_32:
|
96
|
+
return SDS_HDR(32,s)->len;
|
97
|
+
case SDS_TYPE_64:
|
98
|
+
return SDS_HDR(64,s)->len;
|
99
|
+
}
|
100
|
+
return 0;
|
101
|
+
}
|
102
|
+
|
103
|
+
static inline size_t sdsavail(const sds s) {
|
104
|
+
unsigned char flags = s[-1];
|
105
|
+
switch(flags&SDS_TYPE_MASK) {
|
106
|
+
case SDS_TYPE_5: {
|
107
|
+
return 0;
|
108
|
+
}
|
109
|
+
case SDS_TYPE_8: {
|
110
|
+
SDS_HDR_VAR(8,s);
|
111
|
+
return sh->alloc - sh->len;
|
112
|
+
}
|
113
|
+
case SDS_TYPE_16: {
|
114
|
+
SDS_HDR_VAR(16,s);
|
115
|
+
return sh->alloc - sh->len;
|
116
|
+
}
|
117
|
+
case SDS_TYPE_32: {
|
118
|
+
SDS_HDR_VAR(32,s);
|
119
|
+
return sh->alloc - sh->len;
|
120
|
+
}
|
121
|
+
case SDS_TYPE_64: {
|
122
|
+
SDS_HDR_VAR(64,s);
|
123
|
+
return sh->alloc - sh->len;
|
124
|
+
}
|
125
|
+
}
|
126
|
+
return 0;
|
127
|
+
}
|
128
|
+
|
129
|
+
static inline void sdssetlen(sds s, size_t newlen) {
|
130
|
+
unsigned char flags = s[-1];
|
131
|
+
switch(flags&SDS_TYPE_MASK) {
|
132
|
+
case SDS_TYPE_5:
|
133
|
+
{
|
134
|
+
unsigned char *fp = ((unsigned char*)s)-1;
|
135
|
+
*fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
|
136
|
+
}
|
137
|
+
break;
|
138
|
+
case SDS_TYPE_8:
|
139
|
+
SDS_HDR(8,s)->len = newlen;
|
140
|
+
break;
|
141
|
+
case SDS_TYPE_16:
|
142
|
+
SDS_HDR(16,s)->len = newlen;
|
143
|
+
break;
|
144
|
+
case SDS_TYPE_32:
|
145
|
+
SDS_HDR(32,s)->len = newlen;
|
146
|
+
break;
|
147
|
+
case SDS_TYPE_64:
|
148
|
+
SDS_HDR(64,s)->len = newlen;
|
149
|
+
break;
|
150
|
+
}
|
151
|
+
}
|
152
|
+
|
153
|
+
static inline void sdsinclen(sds s, size_t inc) {
|
154
|
+
unsigned char flags = s[-1];
|
155
|
+
switch(flags&SDS_TYPE_MASK) {
|
156
|
+
case SDS_TYPE_5:
|
157
|
+
{
|
158
|
+
unsigned char *fp = ((unsigned char*)s)-1;
|
159
|
+
unsigned char newlen = SDS_TYPE_5_LEN(flags)+inc;
|
160
|
+
*fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
|
161
|
+
}
|
162
|
+
break;
|
163
|
+
case SDS_TYPE_8:
|
164
|
+
SDS_HDR(8,s)->len += inc;
|
165
|
+
break;
|
166
|
+
case SDS_TYPE_16:
|
167
|
+
SDS_HDR(16,s)->len += inc;
|
168
|
+
break;
|
169
|
+
case SDS_TYPE_32:
|
170
|
+
SDS_HDR(32,s)->len += inc;
|
171
|
+
break;
|
172
|
+
case SDS_TYPE_64:
|
173
|
+
SDS_HDR(64,s)->len += inc;
|
174
|
+
break;
|
175
|
+
}
|
176
|
+
}
|
177
|
+
|
178
|
+
/* sdsalloc() = sdsavail() + sdslen() */
|
179
|
+
static inline size_t sdsalloc(const sds s) {
|
180
|
+
unsigned char flags = s[-1];
|
181
|
+
switch(flags&SDS_TYPE_MASK) {
|
182
|
+
case SDS_TYPE_5:
|
183
|
+
return SDS_TYPE_5_LEN(flags);
|
184
|
+
case SDS_TYPE_8:
|
185
|
+
return SDS_HDR(8,s)->alloc;
|
186
|
+
case SDS_TYPE_16:
|
187
|
+
return SDS_HDR(16,s)->alloc;
|
188
|
+
case SDS_TYPE_32:
|
189
|
+
return SDS_HDR(32,s)->alloc;
|
190
|
+
case SDS_TYPE_64:
|
191
|
+
return SDS_HDR(64,s)->alloc;
|
192
|
+
}
|
193
|
+
return 0;
|
194
|
+
}
|
195
|
+
|
196
|
+
static inline void sdssetalloc(sds s, size_t newlen) {
|
197
|
+
unsigned char flags = s[-1];
|
198
|
+
switch(flags&SDS_TYPE_MASK) {
|
199
|
+
case SDS_TYPE_5:
|
200
|
+
/* Nothing to do, this type has no total allocation info. */
|
201
|
+
break;
|
202
|
+
case SDS_TYPE_8:
|
203
|
+
SDS_HDR(8,s)->alloc = newlen;
|
204
|
+
break;
|
205
|
+
case SDS_TYPE_16:
|
206
|
+
SDS_HDR(16,s)->alloc = newlen;
|
207
|
+
break;
|
208
|
+
case SDS_TYPE_32:
|
209
|
+
SDS_HDR(32,s)->alloc = newlen;
|
210
|
+
break;
|
211
|
+
case SDS_TYPE_64:
|
212
|
+
SDS_HDR(64,s)->alloc = newlen;
|
213
|
+
break;
|
214
|
+
}
|
215
|
+
}
|
216
|
+
|
217
|
+
sds sdsnewlen(const void *init, size_t initlen);
|
218
|
+
sds sdsnew(const char *init);
|
219
|
+
sds sdsempty(void);
|
220
|
+
sds sdsdup(const sds s);
|
221
|
+
void sdsfree(sds s);
|
222
|
+
sds sdsgrowzero(sds s, size_t len);
|
223
|
+
sds sdscatlen(sds s, const void *t, size_t len);
|
224
|
+
sds sdscat(sds s, const char *t);
|
225
|
+
sds sdscatsds(sds s, const sds t);
|
226
|
+
sds sdscpylen(sds s, const char *t, size_t len);
|
227
|
+
sds sdscpy(sds s, const char *t);
|
228
|
+
|
229
|
+
sds sdscatvprintf(sds s, const char *fmt, va_list ap);
|
230
|
+
#ifdef __GNUC__
|
231
|
+
sds sdscatprintf(sds s, const char *fmt, ...)
|
232
|
+
__attribute__((format(printf, 2, 3)));
|
233
|
+
#else
|
234
|
+
sds sdscatprintf(sds s, const char *fmt, ...);
|
235
|
+
#endif
|
236
|
+
|
237
|
+
sds sdscatfmt(sds s, char const *fmt, ...);
|
238
|
+
sds sdstrim(sds s, const char *cset);
|
239
|
+
void sdsrange(sds s, int start, int end);
|
240
|
+
void sdsupdatelen(sds s);
|
241
|
+
void sdsclear(sds s);
|
242
|
+
int sdscmp(const sds s1, const sds s2);
|
243
|
+
sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count);
|
244
|
+
void sdsfreesplitres(sds *tokens, int count);
|
245
|
+
void sdstolower(sds s);
|
246
|
+
void sdstoupper(sds s);
|
247
|
+
sds sdsfromlonglong(long long value);
|
248
|
+
sds sdscatrepr(sds s, const char *p, size_t len);
|
249
|
+
sds *sdssplitargs(const char *line, int *argc);
|
250
|
+
sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen);
|
251
|
+
sds sdsjoin(char **argv, int argc, char *sep);
|
252
|
+
sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen);
|
253
|
+
|
254
|
+
/* Low level functions exposed to the user API */
|
255
|
+
sds sdsMakeRoomFor(sds s, size_t addlen);
|
256
|
+
void sdsIncrLen(sds s, int incr);
|
257
|
+
sds sdsRemoveFreeSpace(sds s);
|
258
|
+
size_t sdsAllocSize(sds s);
|
259
|
+
void *sdsAllocPtr(sds s);
|
260
|
+
|
261
|
+
/* Export the allocator used by SDS to the program using SDS.
|
262
|
+
* Sometimes the program SDS is linked to, may use a different set of
|
263
|
+
* allocators, but may want to allocate or free things that SDS will
|
264
|
+
* respectively free or allocate. */
|
265
|
+
void *sds_malloc(size_t size);
|
266
|
+
void *sds_realloc(void *ptr, size_t size);
|
267
|
+
void sds_free(void *ptr);
|
268
|
+
|
269
|
+
#ifdef REDIS_TEST
|
270
|
+
int sdsTest(int argc, char *argv[]);
|
271
|
+
#endif
|
272
|
+
|
273
|
+
#endif
|
@@ -0,0 +1,42 @@
|
|
1
|
+
/* SDSLib 2.0 -- A C dynamic strings library
|
2
|
+
*
|
3
|
+
* Copyright (c) 2006-2015, Salvatore Sanfilippo <antirez at gmail dot com>
|
4
|
+
* Copyright (c) 2015, Oran Agra
|
5
|
+
* Copyright (c) 2015, Redis Labs, Inc
|
6
|
+
* All rights reserved.
|
7
|
+
*
|
8
|
+
* Redistribution and use in source and binary forms, with or without
|
9
|
+
* modification, are permitted provided that the following conditions are met:
|
10
|
+
*
|
11
|
+
* * Redistributions of source code must retain the above copyright notice,
|
12
|
+
* this list of conditions and the following disclaimer.
|
13
|
+
* * Redistributions in binary form must reproduce the above copyright
|
14
|
+
* notice, this list of conditions and the following disclaimer in the
|
15
|
+
* documentation and/or other materials provided with the distribution.
|
16
|
+
* * Neither the name of Redis nor the names of its contributors may be used
|
17
|
+
* to endorse or promote products derived from this software without
|
18
|
+
* specific prior written permission.
|
19
|
+
*
|
20
|
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
21
|
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
22
|
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
23
|
+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
24
|
+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
25
|
+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
26
|
+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
27
|
+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
28
|
+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
29
|
+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
30
|
+
* POSSIBILITY OF SUCH DAMAGE.
|
31
|
+
*/
|
32
|
+
|
33
|
+
/* SDS allocator selection.
|
34
|
+
*
|
35
|
+
* This file is used in order to change the SDS allocator at compile time.
|
36
|
+
* Just define the following defines to what you want to use. Also add
|
37
|
+
* the include of your alternate allocator if needed (not needed in order
|
38
|
+
* to use the default libc allocator). */
|
39
|
+
|
40
|
+
#define s_malloc malloc
|
41
|
+
#define s_realloc realloc
|
42
|
+
#define s_free free
|
@@ -0,0 +1,923 @@
|
|
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("Correctly parses LLONG_MAX: ");
|
306
|
+
reader = redisReaderCreate();
|
307
|
+
redisReaderFeed(reader, ":9223372036854775807\r\n",22);
|
308
|
+
ret = redisReaderGetReply(reader,&reply);
|
309
|
+
test_cond(ret == REDIS_OK &&
|
310
|
+
((redisReply*)reply)->type == REDIS_REPLY_INTEGER &&
|
311
|
+
((redisReply*)reply)->integer == LLONG_MAX);
|
312
|
+
freeReplyObject(reply);
|
313
|
+
redisReaderFree(reader);
|
314
|
+
|
315
|
+
test("Set error when > LLONG_MAX: ");
|
316
|
+
reader = redisReaderCreate();
|
317
|
+
redisReaderFeed(reader, ":9223372036854775808\r\n",22);
|
318
|
+
ret = redisReaderGetReply(reader,&reply);
|
319
|
+
test_cond(ret == REDIS_ERR &&
|
320
|
+
strcasecmp(reader->errstr,"Bad integer value") == 0);
|
321
|
+
freeReplyObject(reply);
|
322
|
+
redisReaderFree(reader);
|
323
|
+
|
324
|
+
test("Correctly parses LLONG_MIN: ");
|
325
|
+
reader = redisReaderCreate();
|
326
|
+
redisReaderFeed(reader, ":-9223372036854775808\r\n",23);
|
327
|
+
ret = redisReaderGetReply(reader,&reply);
|
328
|
+
test_cond(ret == REDIS_OK &&
|
329
|
+
((redisReply*)reply)->type == REDIS_REPLY_INTEGER &&
|
330
|
+
((redisReply*)reply)->integer == LLONG_MIN);
|
331
|
+
freeReplyObject(reply);
|
332
|
+
redisReaderFree(reader);
|
333
|
+
|
334
|
+
test("Set error when < LLONG_MIN: ");
|
335
|
+
reader = redisReaderCreate();
|
336
|
+
redisReaderFeed(reader, ":-9223372036854775809\r\n",23);
|
337
|
+
ret = redisReaderGetReply(reader,&reply);
|
338
|
+
test_cond(ret == REDIS_ERR &&
|
339
|
+
strcasecmp(reader->errstr,"Bad integer value") == 0);
|
340
|
+
freeReplyObject(reply);
|
341
|
+
redisReaderFree(reader);
|
342
|
+
|
343
|
+
test("Set error when array < -1: ");
|
344
|
+
reader = redisReaderCreate();
|
345
|
+
redisReaderFeed(reader, "*-2\r\n+asdf\r\n",12);
|
346
|
+
ret = redisReaderGetReply(reader,&reply);
|
347
|
+
test_cond(ret == REDIS_ERR &&
|
348
|
+
strcasecmp(reader->errstr,"Multi-bulk length out of range") == 0);
|
349
|
+
freeReplyObject(reply);
|
350
|
+
redisReaderFree(reader);
|
351
|
+
|
352
|
+
test("Set error when bulk < -1: ");
|
353
|
+
reader = redisReaderCreate();
|
354
|
+
redisReaderFeed(reader, "$-2\r\nasdf\r\n",11);
|
355
|
+
ret = redisReaderGetReply(reader,&reply);
|
356
|
+
test_cond(ret == REDIS_ERR &&
|
357
|
+
strcasecmp(reader->errstr,"Bulk string length out of range") == 0);
|
358
|
+
freeReplyObject(reply);
|
359
|
+
redisReaderFree(reader);
|
360
|
+
|
361
|
+
test("Set error when array > INT_MAX: ");
|
362
|
+
reader = redisReaderCreate();
|
363
|
+
redisReaderFeed(reader, "*9223372036854775807\r\n+asdf\r\n",29);
|
364
|
+
ret = redisReaderGetReply(reader,&reply);
|
365
|
+
test_cond(ret == REDIS_ERR &&
|
366
|
+
strcasecmp(reader->errstr,"Multi-bulk length out of range") == 0);
|
367
|
+
freeReplyObject(reply);
|
368
|
+
redisReaderFree(reader);
|
369
|
+
|
370
|
+
#if LLONG_MAX > SIZE_MAX
|
371
|
+
test("Set error when bulk > SIZE_MAX: ");
|
372
|
+
reader = redisReaderCreate();
|
373
|
+
redisReaderFeed(reader, "$9223372036854775807\r\nasdf\r\n",28);
|
374
|
+
ret = redisReaderGetReply(reader,&reply);
|
375
|
+
test_cond(ret == REDIS_ERR &&
|
376
|
+
strcasecmp(reader->errstr,"Bulk string length out of range") == 0);
|
377
|
+
freeReplyObject(reply);
|
378
|
+
redisReaderFree(reader);
|
379
|
+
#endif
|
380
|
+
|
381
|
+
test("Works with NULL functions for reply: ");
|
382
|
+
reader = redisReaderCreate();
|
383
|
+
reader->fn = NULL;
|
384
|
+
redisReaderFeed(reader,(char*)"+OK\r\n",5);
|
385
|
+
ret = redisReaderGetReply(reader,&reply);
|
386
|
+
test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS);
|
387
|
+
redisReaderFree(reader);
|
388
|
+
|
389
|
+
test("Works when a single newline (\\r\\n) covers two calls to feed: ");
|
390
|
+
reader = redisReaderCreate();
|
391
|
+
reader->fn = NULL;
|
392
|
+
redisReaderFeed(reader,(char*)"+OK\r",4);
|
393
|
+
ret = redisReaderGetReply(reader,&reply);
|
394
|
+
assert(ret == REDIS_OK && reply == NULL);
|
395
|
+
redisReaderFeed(reader,(char*)"\n",1);
|
396
|
+
ret = redisReaderGetReply(reader,&reply);
|
397
|
+
test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS);
|
398
|
+
redisReaderFree(reader);
|
399
|
+
|
400
|
+
test("Don't reset state after protocol error: ");
|
401
|
+
reader = redisReaderCreate();
|
402
|
+
reader->fn = NULL;
|
403
|
+
redisReaderFeed(reader,(char*)"x",1);
|
404
|
+
ret = redisReaderGetReply(reader,&reply);
|
405
|
+
assert(ret == REDIS_ERR);
|
406
|
+
ret = redisReaderGetReply(reader,&reply);
|
407
|
+
test_cond(ret == REDIS_ERR && reply == NULL);
|
408
|
+
redisReaderFree(reader);
|
409
|
+
|
410
|
+
/* Regression test for issue #45 on GitHub. */
|
411
|
+
test("Don't do empty allocation for empty multi bulk: ");
|
412
|
+
reader = redisReaderCreate();
|
413
|
+
redisReaderFeed(reader,(char*)"*0\r\n",4);
|
414
|
+
ret = redisReaderGetReply(reader,&reply);
|
415
|
+
test_cond(ret == REDIS_OK &&
|
416
|
+
((redisReply*)reply)->type == REDIS_REPLY_ARRAY &&
|
417
|
+
((redisReply*)reply)->elements == 0);
|
418
|
+
freeReplyObject(reply);
|
419
|
+
redisReaderFree(reader);
|
420
|
+
}
|
421
|
+
|
422
|
+
static void test_free_null(void) {
|
423
|
+
void *redisCtx = NULL;
|
424
|
+
void *reply = NULL;
|
425
|
+
|
426
|
+
test("Don't fail when redisFree is passed a NULL value: ");
|
427
|
+
redisFree(redisCtx);
|
428
|
+
test_cond(redisCtx == NULL);
|
429
|
+
|
430
|
+
test("Don't fail when freeReplyObject is passed a NULL value: ");
|
431
|
+
freeReplyObject(reply);
|
432
|
+
test_cond(reply == NULL);
|
433
|
+
}
|
434
|
+
|
435
|
+
static void test_blocking_connection_errors(void) {
|
436
|
+
redisContext *c;
|
437
|
+
|
438
|
+
test("Returns error when host cannot be resolved: ");
|
439
|
+
c = redisConnect((char*)"idontexist.test", 6379);
|
440
|
+
test_cond(c->err == REDIS_ERR_OTHER &&
|
441
|
+
(strcmp(c->errstr,"Name or service not known") == 0 ||
|
442
|
+
strcmp(c->errstr,"Can't resolve: idontexist.test") == 0 ||
|
443
|
+
strcmp(c->errstr,"nodename nor servname provided, or not known") == 0 ||
|
444
|
+
strcmp(c->errstr,"No address associated with hostname") == 0 ||
|
445
|
+
strcmp(c->errstr,"Temporary failure in name resolution") == 0 ||
|
446
|
+
strcmp(c->errstr,"hostname nor servname provided, or not known") == 0 ||
|
447
|
+
strcmp(c->errstr,"no address associated with name") == 0));
|
448
|
+
redisFree(c);
|
449
|
+
|
450
|
+
test("Returns error when the port is not open: ");
|
451
|
+
c = redisConnect((char*)"localhost", 1);
|
452
|
+
test_cond(c->err == REDIS_ERR_IO &&
|
453
|
+
strcmp(c->errstr,"Connection refused") == 0);
|
454
|
+
redisFree(c);
|
455
|
+
|
456
|
+
test("Returns error when the unix_sock socket path doesn't accept connections: ");
|
457
|
+
c = redisConnectUnix((char*)"/tmp/idontexist.sock");
|
458
|
+
test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */
|
459
|
+
redisFree(c);
|
460
|
+
}
|
461
|
+
|
462
|
+
static void test_blocking_connection(struct config config) {
|
463
|
+
redisContext *c;
|
464
|
+
redisReply *reply;
|
465
|
+
|
466
|
+
c = connect(config);
|
467
|
+
|
468
|
+
test("Is able to deliver commands: ");
|
469
|
+
reply = redisCommand(c,"PING");
|
470
|
+
test_cond(reply->type == REDIS_REPLY_STATUS &&
|
471
|
+
strcasecmp(reply->str,"pong") == 0)
|
472
|
+
freeReplyObject(reply);
|
473
|
+
|
474
|
+
test("Is a able to send commands verbatim: ");
|
475
|
+
reply = redisCommand(c,"SET foo bar");
|
476
|
+
test_cond (reply->type == REDIS_REPLY_STATUS &&
|
477
|
+
strcasecmp(reply->str,"ok") == 0)
|
478
|
+
freeReplyObject(reply);
|
479
|
+
|
480
|
+
test("%%s String interpolation works: ");
|
481
|
+
reply = redisCommand(c,"SET %s %s","foo","hello world");
|
482
|
+
freeReplyObject(reply);
|
483
|
+
reply = redisCommand(c,"GET foo");
|
484
|
+
test_cond(reply->type == REDIS_REPLY_STRING &&
|
485
|
+
strcmp(reply->str,"hello world") == 0);
|
486
|
+
freeReplyObject(reply);
|
487
|
+
|
488
|
+
test("%%b String interpolation works: ");
|
489
|
+
reply = redisCommand(c,"SET %b %b","foo",(size_t)3,"hello\x00world",(size_t)11);
|
490
|
+
freeReplyObject(reply);
|
491
|
+
reply = redisCommand(c,"GET foo");
|
492
|
+
test_cond(reply->type == REDIS_REPLY_STRING &&
|
493
|
+
memcmp(reply->str,"hello\x00world",11) == 0)
|
494
|
+
|
495
|
+
test("Binary reply length is correct: ");
|
496
|
+
test_cond(reply->len == 11)
|
497
|
+
freeReplyObject(reply);
|
498
|
+
|
499
|
+
test("Can parse nil replies: ");
|
500
|
+
reply = redisCommand(c,"GET nokey");
|
501
|
+
test_cond(reply->type == REDIS_REPLY_NIL)
|
502
|
+
freeReplyObject(reply);
|
503
|
+
|
504
|
+
/* test 7 */
|
505
|
+
test("Can parse integer replies: ");
|
506
|
+
reply = redisCommand(c,"INCR mycounter");
|
507
|
+
test_cond(reply->type == REDIS_REPLY_INTEGER && reply->integer == 1)
|
508
|
+
freeReplyObject(reply);
|
509
|
+
|
510
|
+
test("Can parse multi bulk replies: ");
|
511
|
+
freeReplyObject(redisCommand(c,"LPUSH mylist foo"));
|
512
|
+
freeReplyObject(redisCommand(c,"LPUSH mylist bar"));
|
513
|
+
reply = redisCommand(c,"LRANGE mylist 0 -1");
|
514
|
+
test_cond(reply->type == REDIS_REPLY_ARRAY &&
|
515
|
+
reply->elements == 2 &&
|
516
|
+
!memcmp(reply->element[0]->str,"bar",3) &&
|
517
|
+
!memcmp(reply->element[1]->str,"foo",3))
|
518
|
+
freeReplyObject(reply);
|
519
|
+
|
520
|
+
/* m/e with multi bulk reply *before* other reply.
|
521
|
+
* specifically test ordering of reply items to parse. */
|
522
|
+
test("Can handle nested multi bulk replies: ");
|
523
|
+
freeReplyObject(redisCommand(c,"MULTI"));
|
524
|
+
freeReplyObject(redisCommand(c,"LRANGE mylist 0 -1"));
|
525
|
+
freeReplyObject(redisCommand(c,"PING"));
|
526
|
+
reply = (redisCommand(c,"EXEC"));
|
527
|
+
test_cond(reply->type == REDIS_REPLY_ARRAY &&
|
528
|
+
reply->elements == 2 &&
|
529
|
+
reply->element[0]->type == REDIS_REPLY_ARRAY &&
|
530
|
+
reply->element[0]->elements == 2 &&
|
531
|
+
!memcmp(reply->element[0]->element[0]->str,"bar",3) &&
|
532
|
+
!memcmp(reply->element[0]->element[1]->str,"foo",3) &&
|
533
|
+
reply->element[1]->type == REDIS_REPLY_STATUS &&
|
534
|
+
strcasecmp(reply->element[1]->str,"pong") == 0);
|
535
|
+
freeReplyObject(reply);
|
536
|
+
|
537
|
+
disconnect(c, 0);
|
538
|
+
}
|
539
|
+
|
540
|
+
static void test_blocking_connection_timeouts(struct config config) {
|
541
|
+
redisContext *c;
|
542
|
+
redisReply *reply;
|
543
|
+
ssize_t s;
|
544
|
+
const char *cmd = "DEBUG SLEEP 3\r\n";
|
545
|
+
struct timeval tv;
|
546
|
+
|
547
|
+
c = connect(config);
|
548
|
+
test("Successfully completes a command when the timeout is not exceeded: ");
|
549
|
+
reply = redisCommand(c,"SET foo fast");
|
550
|
+
freeReplyObject(reply);
|
551
|
+
tv.tv_sec = 0;
|
552
|
+
tv.tv_usec = 10000;
|
553
|
+
redisSetTimeout(c, tv);
|
554
|
+
reply = redisCommand(c, "GET foo");
|
555
|
+
test_cond(reply != NULL && reply->type == REDIS_REPLY_STRING && memcmp(reply->str, "fast", 4) == 0);
|
556
|
+
freeReplyObject(reply);
|
557
|
+
disconnect(c, 0);
|
558
|
+
|
559
|
+
c = connect(config);
|
560
|
+
test("Does not return a reply when the command times out: ");
|
561
|
+
s = write(c->fd, cmd, strlen(cmd));
|
562
|
+
tv.tv_sec = 0;
|
563
|
+
tv.tv_usec = 10000;
|
564
|
+
redisSetTimeout(c, tv);
|
565
|
+
reply = redisCommand(c, "GET foo");
|
566
|
+
test_cond(s > 0 && reply == NULL && c->err == REDIS_ERR_IO && strcmp(c->errstr, "Resource temporarily unavailable") == 0);
|
567
|
+
freeReplyObject(reply);
|
568
|
+
|
569
|
+
test("Reconnect properly reconnects after a timeout: ");
|
570
|
+
redisReconnect(c);
|
571
|
+
reply = redisCommand(c, "PING");
|
572
|
+
test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
|
573
|
+
freeReplyObject(reply);
|
574
|
+
|
575
|
+
test("Reconnect properly uses owned parameters: ");
|
576
|
+
config.tcp.host = "foo";
|
577
|
+
config.unix_sock.path = "foo";
|
578
|
+
redisReconnect(c);
|
579
|
+
reply = redisCommand(c, "PING");
|
580
|
+
test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
|
581
|
+
freeReplyObject(reply);
|
582
|
+
|
583
|
+
disconnect(c, 0);
|
584
|
+
}
|
585
|
+
|
586
|
+
static void test_blocking_io_errors(struct config config) {
|
587
|
+
redisContext *c;
|
588
|
+
redisReply *reply;
|
589
|
+
void *_reply;
|
590
|
+
int major, minor;
|
591
|
+
|
592
|
+
/* Connect to target given by config. */
|
593
|
+
c = connect(config);
|
594
|
+
{
|
595
|
+
/* Find out Redis version to determine the path for the next test */
|
596
|
+
const char *field = "redis_version:";
|
597
|
+
char *p, *eptr;
|
598
|
+
|
599
|
+
reply = redisCommand(c,"INFO");
|
600
|
+
p = strstr(reply->str,field);
|
601
|
+
major = strtol(p+strlen(field),&eptr,10);
|
602
|
+
p = eptr+1; /* char next to the first "." */
|
603
|
+
minor = strtol(p,&eptr,10);
|
604
|
+
freeReplyObject(reply);
|
605
|
+
}
|
606
|
+
|
607
|
+
test("Returns I/O error when the connection is lost: ");
|
608
|
+
reply = redisCommand(c,"QUIT");
|
609
|
+
if (major > 2 || (major == 2 && minor > 0)) {
|
610
|
+
/* > 2.0 returns OK on QUIT and read() should be issued once more
|
611
|
+
* to know the descriptor is at EOF. */
|
612
|
+
test_cond(strcasecmp(reply->str,"OK") == 0 &&
|
613
|
+
redisGetReply(c,&_reply) == REDIS_ERR);
|
614
|
+
freeReplyObject(reply);
|
615
|
+
} else {
|
616
|
+
test_cond(reply == NULL);
|
617
|
+
}
|
618
|
+
|
619
|
+
/* On 2.0, QUIT will cause the connection to be closed immediately and
|
620
|
+
* the read(2) for the reply on QUIT will set the error to EOF.
|
621
|
+
* On >2.0, QUIT will return with OK and another read(2) needed to be
|
622
|
+
* issued to find out the socket was closed by the server. In both
|
623
|
+
* conditions, the error will be set to EOF. */
|
624
|
+
assert(c->err == REDIS_ERR_EOF &&
|
625
|
+
strcmp(c->errstr,"Server closed the connection") == 0);
|
626
|
+
redisFree(c);
|
627
|
+
|
628
|
+
c = connect(config);
|
629
|
+
test("Returns I/O error on socket timeout: ");
|
630
|
+
struct timeval tv = { 0, 1000 };
|
631
|
+
assert(redisSetTimeout(c,tv) == REDIS_OK);
|
632
|
+
test_cond(redisGetReply(c,&_reply) == REDIS_ERR &&
|
633
|
+
c->err == REDIS_ERR_IO && errno == EAGAIN);
|
634
|
+
redisFree(c);
|
635
|
+
}
|
636
|
+
|
637
|
+
static void test_invalid_timeout_errors(struct config config) {
|
638
|
+
redisContext *c;
|
639
|
+
|
640
|
+
test("Set error when an invalid timeout usec value is given to redisConnectWithTimeout: ");
|
641
|
+
|
642
|
+
config.tcp.timeout.tv_sec = 0;
|
643
|
+
config.tcp.timeout.tv_usec = 10000001;
|
644
|
+
|
645
|
+
c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout);
|
646
|
+
|
647
|
+
test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0);
|
648
|
+
redisFree(c);
|
649
|
+
|
650
|
+
test("Set error when an invalid timeout sec value is given to redisConnectWithTimeout: ");
|
651
|
+
|
652
|
+
config.tcp.timeout.tv_sec = (((LONG_MAX) - 999) / 1000) + 1;
|
653
|
+
config.tcp.timeout.tv_usec = 0;
|
654
|
+
|
655
|
+
c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout);
|
656
|
+
|
657
|
+
test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0);
|
658
|
+
redisFree(c);
|
659
|
+
}
|
660
|
+
|
661
|
+
static void test_throughput(struct config config) {
|
662
|
+
redisContext *c = connect(config);
|
663
|
+
redisReply **replies;
|
664
|
+
int i, num;
|
665
|
+
long long t1, t2;
|
666
|
+
|
667
|
+
test("Throughput:\n");
|
668
|
+
for (i = 0; i < 500; i++)
|
669
|
+
freeReplyObject(redisCommand(c,"LPUSH mylist foo"));
|
670
|
+
|
671
|
+
num = 1000;
|
672
|
+
replies = malloc(sizeof(redisReply*)*num);
|
673
|
+
t1 = usec();
|
674
|
+
for (i = 0; i < num; i++) {
|
675
|
+
replies[i] = redisCommand(c,"PING");
|
676
|
+
assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS);
|
677
|
+
}
|
678
|
+
t2 = usec();
|
679
|
+
for (i = 0; i < num; i++) freeReplyObject(replies[i]);
|
680
|
+
free(replies);
|
681
|
+
printf("\t(%dx PING: %.3fs)\n", num, (t2-t1)/1000000.0);
|
682
|
+
|
683
|
+
replies = malloc(sizeof(redisReply*)*num);
|
684
|
+
t1 = usec();
|
685
|
+
for (i = 0; i < num; i++) {
|
686
|
+
replies[i] = redisCommand(c,"LRANGE mylist 0 499");
|
687
|
+
assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY);
|
688
|
+
assert(replies[i] != NULL && replies[i]->elements == 500);
|
689
|
+
}
|
690
|
+
t2 = usec();
|
691
|
+
for (i = 0; i < num; i++) freeReplyObject(replies[i]);
|
692
|
+
free(replies);
|
693
|
+
printf("\t(%dx LRANGE with 500 elements: %.3fs)\n", num, (t2-t1)/1000000.0);
|
694
|
+
|
695
|
+
replies = malloc(sizeof(redisReply*)*num);
|
696
|
+
t1 = usec();
|
697
|
+
for (i = 0; i < num; i++) {
|
698
|
+
replies[i] = redisCommand(c, "INCRBY incrkey %d", 1000000);
|
699
|
+
assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_INTEGER);
|
700
|
+
}
|
701
|
+
t2 = usec();
|
702
|
+
for (i = 0; i < num; i++) freeReplyObject(replies[i]);
|
703
|
+
free(replies);
|
704
|
+
printf("\t(%dx INCRBY: %.3fs)\n", num, (t2-t1)/1000000.0);
|
705
|
+
|
706
|
+
num = 10000;
|
707
|
+
replies = malloc(sizeof(redisReply*)*num);
|
708
|
+
for (i = 0; i < num; i++)
|
709
|
+
redisAppendCommand(c,"PING");
|
710
|
+
t1 = usec();
|
711
|
+
for (i = 0; i < num; i++) {
|
712
|
+
assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);
|
713
|
+
assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS);
|
714
|
+
}
|
715
|
+
t2 = usec();
|
716
|
+
for (i = 0; i < num; i++) freeReplyObject(replies[i]);
|
717
|
+
free(replies);
|
718
|
+
printf("\t(%dx PING (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
|
719
|
+
|
720
|
+
replies = malloc(sizeof(redisReply*)*num);
|
721
|
+
for (i = 0; i < num; i++)
|
722
|
+
redisAppendCommand(c,"LRANGE mylist 0 499");
|
723
|
+
t1 = usec();
|
724
|
+
for (i = 0; i < num; i++) {
|
725
|
+
assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);
|
726
|
+
assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY);
|
727
|
+
assert(replies[i] != NULL && replies[i]->elements == 500);
|
728
|
+
}
|
729
|
+
t2 = usec();
|
730
|
+
for (i = 0; i < num; i++) freeReplyObject(replies[i]);
|
731
|
+
free(replies);
|
732
|
+
printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
|
733
|
+
|
734
|
+
replies = malloc(sizeof(redisReply*)*num);
|
735
|
+
for (i = 0; i < num; i++)
|
736
|
+
redisAppendCommand(c,"INCRBY incrkey %d", 1000000);
|
737
|
+
t1 = usec();
|
738
|
+
for (i = 0; i < num; i++) {
|
739
|
+
assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);
|
740
|
+
assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_INTEGER);
|
741
|
+
}
|
742
|
+
t2 = usec();
|
743
|
+
for (i = 0; i < num; i++) freeReplyObject(replies[i]);
|
744
|
+
free(replies);
|
745
|
+
printf("\t(%dx INCRBY (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
|
746
|
+
|
747
|
+
disconnect(c, 0);
|
748
|
+
}
|
749
|
+
|
750
|
+
// static long __test_callback_flags = 0;
|
751
|
+
// static void __test_callback(redisContext *c, void *privdata) {
|
752
|
+
// ((void)c);
|
753
|
+
// /* Shift to detect execution order */
|
754
|
+
// __test_callback_flags <<= 8;
|
755
|
+
// __test_callback_flags |= (long)privdata;
|
756
|
+
// }
|
757
|
+
//
|
758
|
+
// static void __test_reply_callback(redisContext *c, redisReply *reply, void *privdata) {
|
759
|
+
// ((void)c);
|
760
|
+
// /* Shift to detect execution order */
|
761
|
+
// __test_callback_flags <<= 8;
|
762
|
+
// __test_callback_flags |= (long)privdata;
|
763
|
+
// if (reply) freeReplyObject(reply);
|
764
|
+
// }
|
765
|
+
//
|
766
|
+
// static redisContext *__connect_nonblock() {
|
767
|
+
// /* Reset callback flags */
|
768
|
+
// __test_callback_flags = 0;
|
769
|
+
// return redisConnectNonBlock("127.0.0.1", port, NULL);
|
770
|
+
// }
|
771
|
+
//
|
772
|
+
// static void test_nonblocking_connection() {
|
773
|
+
// redisContext *c;
|
774
|
+
// int wdone = 0;
|
775
|
+
//
|
776
|
+
// test("Calls command callback when command is issued: ");
|
777
|
+
// c = __connect_nonblock();
|
778
|
+
// redisSetCommandCallback(c,__test_callback,(void*)1);
|
779
|
+
// redisCommand(c,"PING");
|
780
|
+
// test_cond(__test_callback_flags == 1);
|
781
|
+
// redisFree(c);
|
782
|
+
//
|
783
|
+
// test("Calls disconnect callback on redisDisconnect: ");
|
784
|
+
// c = __connect_nonblock();
|
785
|
+
// redisSetDisconnectCallback(c,__test_callback,(void*)2);
|
786
|
+
// redisDisconnect(c);
|
787
|
+
// test_cond(__test_callback_flags == 2);
|
788
|
+
// redisFree(c);
|
789
|
+
//
|
790
|
+
// test("Calls disconnect callback and free callback on redisFree: ");
|
791
|
+
// c = __connect_nonblock();
|
792
|
+
// redisSetDisconnectCallback(c,__test_callback,(void*)2);
|
793
|
+
// redisSetFreeCallback(c,__test_callback,(void*)4);
|
794
|
+
// redisFree(c);
|
795
|
+
// test_cond(__test_callback_flags == ((2 << 8) | 4));
|
796
|
+
//
|
797
|
+
// test("redisBufferWrite against empty write buffer: ");
|
798
|
+
// c = __connect_nonblock();
|
799
|
+
// test_cond(redisBufferWrite(c,&wdone) == REDIS_OK && wdone == 1);
|
800
|
+
// redisFree(c);
|
801
|
+
//
|
802
|
+
// test("redisBufferWrite against not yet connected fd: ");
|
803
|
+
// c = __connect_nonblock();
|
804
|
+
// redisCommand(c,"PING");
|
805
|
+
// test_cond(redisBufferWrite(c,NULL) == REDIS_ERR &&
|
806
|
+
// strncmp(c->error,"write:",6) == 0);
|
807
|
+
// redisFree(c);
|
808
|
+
//
|
809
|
+
// test("redisBufferWrite against closed fd: ");
|
810
|
+
// c = __connect_nonblock();
|
811
|
+
// redisCommand(c,"PING");
|
812
|
+
// redisDisconnect(c);
|
813
|
+
// test_cond(redisBufferWrite(c,NULL) == REDIS_ERR &&
|
814
|
+
// strncmp(c->error,"write:",6) == 0);
|
815
|
+
// redisFree(c);
|
816
|
+
//
|
817
|
+
// test("Process callbacks in the right sequence: ");
|
818
|
+
// c = __connect_nonblock();
|
819
|
+
// redisCommandWithCallback(c,__test_reply_callback,(void*)1,"PING");
|
820
|
+
// redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING");
|
821
|
+
// redisCommandWithCallback(c,__test_reply_callback,(void*)3,"PING");
|
822
|
+
//
|
823
|
+
// /* Write output buffer */
|
824
|
+
// wdone = 0;
|
825
|
+
// while(!wdone) {
|
826
|
+
// usleep(500);
|
827
|
+
// redisBufferWrite(c,&wdone);
|
828
|
+
// }
|
829
|
+
//
|
830
|
+
// /* Read until at least one callback is executed (the 3 replies will
|
831
|
+
// * arrive in a single packet, causing all callbacks to be executed in
|
832
|
+
// * a single pass). */
|
833
|
+
// while(__test_callback_flags == 0) {
|
834
|
+
// assert(redisBufferRead(c) == REDIS_OK);
|
835
|
+
// redisProcessCallbacks(c);
|
836
|
+
// }
|
837
|
+
// test_cond(__test_callback_flags == 0x010203);
|
838
|
+
// redisFree(c);
|
839
|
+
//
|
840
|
+
// test("redisDisconnect executes pending callbacks with NULL reply: ");
|
841
|
+
// c = __connect_nonblock();
|
842
|
+
// redisSetDisconnectCallback(c,__test_callback,(void*)1);
|
843
|
+
// redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING");
|
844
|
+
// redisDisconnect(c);
|
845
|
+
// test_cond(__test_callback_flags == 0x0201);
|
846
|
+
// redisFree(c);
|
847
|
+
// }
|
848
|
+
|
849
|
+
int main(int argc, char **argv) {
|
850
|
+
struct config cfg = {
|
851
|
+
.tcp = {
|
852
|
+
.host = "127.0.0.1",
|
853
|
+
.port = 6379
|
854
|
+
},
|
855
|
+
.unix_sock = {
|
856
|
+
.path = "/tmp/redis.sock"
|
857
|
+
}
|
858
|
+
};
|
859
|
+
int throughput = 1;
|
860
|
+
int test_inherit_fd = 1;
|
861
|
+
|
862
|
+
/* Ignore broken pipe signal (for I/O error tests). */
|
863
|
+
signal(SIGPIPE, SIG_IGN);
|
864
|
+
|
865
|
+
/* Parse command line options. */
|
866
|
+
argv++; argc--;
|
867
|
+
while (argc) {
|
868
|
+
if (argc >= 2 && !strcmp(argv[0],"-h")) {
|
869
|
+
argv++; argc--;
|
870
|
+
cfg.tcp.host = argv[0];
|
871
|
+
} else if (argc >= 2 && !strcmp(argv[0],"-p")) {
|
872
|
+
argv++; argc--;
|
873
|
+
cfg.tcp.port = atoi(argv[0]);
|
874
|
+
} else if (argc >= 2 && !strcmp(argv[0],"-s")) {
|
875
|
+
argv++; argc--;
|
876
|
+
cfg.unix_sock.path = argv[0];
|
877
|
+
} else if (argc >= 1 && !strcmp(argv[0],"--skip-throughput")) {
|
878
|
+
throughput = 0;
|
879
|
+
} else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) {
|
880
|
+
test_inherit_fd = 0;
|
881
|
+
} else {
|
882
|
+
fprintf(stderr, "Invalid argument: %s\n", argv[0]);
|
883
|
+
exit(1);
|
884
|
+
}
|
885
|
+
argv++; argc--;
|
886
|
+
}
|
887
|
+
|
888
|
+
test_format_commands();
|
889
|
+
test_reply_reader();
|
890
|
+
test_blocking_connection_errors();
|
891
|
+
test_free_null();
|
892
|
+
|
893
|
+
printf("\nTesting against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port);
|
894
|
+
cfg.type = CONN_TCP;
|
895
|
+
test_blocking_connection(cfg);
|
896
|
+
test_blocking_connection_timeouts(cfg);
|
897
|
+
test_blocking_io_errors(cfg);
|
898
|
+
test_invalid_timeout_errors(cfg);
|
899
|
+
test_append_formatted_commands(cfg);
|
900
|
+
if (throughput) test_throughput(cfg);
|
901
|
+
|
902
|
+
printf("\nTesting against Unix socket connection (%s):\n", cfg.unix_sock.path);
|
903
|
+
cfg.type = CONN_UNIX;
|
904
|
+
test_blocking_connection(cfg);
|
905
|
+
test_blocking_connection_timeouts(cfg);
|
906
|
+
test_blocking_io_errors(cfg);
|
907
|
+
if (throughput) test_throughput(cfg);
|
908
|
+
|
909
|
+
if (test_inherit_fd) {
|
910
|
+
printf("\nTesting against inherited fd (%s):\n", cfg.unix_sock.path);
|
911
|
+
cfg.type = CONN_FD;
|
912
|
+
test_blocking_connection(cfg);
|
913
|
+
}
|
914
|
+
|
915
|
+
|
916
|
+
if (fails) {
|
917
|
+
printf("*** %d TESTS FAILED ***\n", fails);
|
918
|
+
return 1;
|
919
|
+
}
|
920
|
+
|
921
|
+
printf("ALL TESTS PASSED\n");
|
922
|
+
return 0;
|
923
|
+
}
|