hiredis 0.4.1 → 0.4.2.pre

Sign up to get free protection for your applications and to get access to all the features.
@@ -37,7 +37,7 @@
37
37
 
38
38
  #define HIREDIS_MAJOR 0
39
39
  #define HIREDIS_MINOR 10
40
- #define HIREDIS_PATCH 0
40
+ #define HIREDIS_PATCH 1
41
41
 
42
42
  #define REDIS_ERR -1
43
43
  #define REDIS_OK 0
@@ -123,7 +123,7 @@ typedef struct redisReader {
123
123
  size_t pos; /* Buffer cursor */
124
124
  size_t len; /* Buffer length */
125
125
 
126
- redisReadTask rstack[3];
126
+ redisReadTask rstack[4];
127
127
  int ridx; /* Index of current read task */
128
128
  void *reply; /* Temporary reply pointer */
129
129
 
@@ -62,16 +62,24 @@ static void __redisSetErrorFromErrno(redisContext *c, int type, const char *pref
62
62
  __redisSetError(c,type,buf);
63
63
  }
64
64
 
65
+ static int redisSetReuseAddr(redisContext *c, int fd) {
66
+ int on = 1;
67
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
68
+ __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
69
+ close(fd);
70
+ return REDIS_ERR;
71
+ }
72
+ return REDIS_OK;
73
+ }
74
+
65
75
  static int redisCreateSocket(redisContext *c, int type) {
66
- int s, on = 1;
76
+ int s;
67
77
  if ((s = socket(type, SOCK_STREAM, 0)) == -1) {
68
78
  __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
69
79
  return REDIS_ERR;
70
80
  }
71
81
  if (type == AF_INET) {
72
- if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
73
- __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
74
- close(s);
82
+ if (redisSetReuseAddr(c,s) == REDIS_ERR) {
75
83
  return REDIS_ERR;
76
84
  }
77
85
  }
@@ -117,8 +125,6 @@ static int redisContextWaitReady(redisContext *c, int fd, const struct timeval *
117
125
  struct timeval to;
118
126
  struct timeval *toptr = NULL;
119
127
  fd_set wfd;
120
- int err;
121
- socklen_t errlen;
122
128
 
123
129
  /* Only use timeout when not NULL. */
124
130
  if (timeout != NULL) {
@@ -143,20 +149,8 @@ static int redisContextWaitReady(redisContext *c, int fd, const struct timeval *
143
149
  return REDIS_ERR;
144
150
  }
145
151
 
146
- err = 0;
147
- errlen = sizeof(err);
148
- if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) {
149
- __redisSetErrorFromErrno(c,REDIS_ERR_IO,"getsockopt(SO_ERROR)");
150
- close(fd);
152
+ if (redisCheckSocketError(c, fd) != REDIS_OK)
151
153
  return REDIS_ERR;
152
- }
153
-
154
- if (err) {
155
- errno = err;
156
- __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
157
- close(fd);
158
- return REDIS_ERR;
159
- }
160
154
 
161
155
  return REDIS_OK;
162
156
  }
@@ -166,6 +160,26 @@ static int redisContextWaitReady(redisContext *c, int fd, const struct timeval *
166
160
  return REDIS_ERR;
167
161
  }
168
162
 
163
+ int redisCheckSocketError(redisContext *c, int fd) {
164
+ int err = 0;
165
+ socklen_t errlen = sizeof(err);
166
+
167
+ if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) {
168
+ __redisSetErrorFromErrno(c,REDIS_ERR_IO,"getsockopt(SO_ERROR)");
169
+ close(fd);
170
+ return REDIS_ERR;
171
+ }
172
+
173
+ if (err) {
174
+ errno = err;
175
+ __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
176
+ close(fd);
177
+ return REDIS_ERR;
178
+ }
179
+
180
+ return REDIS_OK;
181
+ }
182
+
169
183
  int redisContextSetTimeout(redisContext *c, struct timeval tv) {
170
184
  if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv)) == -1) {
171
185
  __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)");
@@ -179,50 +193,59 @@ int redisContextSetTimeout(redisContext *c, struct timeval tv) {
179
193
  }
180
194
 
181
195
  int redisContextConnectTcp(redisContext *c, const char *addr, int port, struct timeval *timeout) {
182
- int s;
196
+ int s, rv;
197
+ char _port[6]; /* strlen("65535"); */
198
+ struct addrinfo hints, *servinfo, *p;
183
199
  int blocking = (c->flags & REDIS_BLOCK);
184
- struct sockaddr_in sa;
185
200
 
186
- if ((s = redisCreateSocket(c,AF_INET)) < 0)
187
- return REDIS_ERR;
188
- if (redisSetBlocking(c,s,0) != REDIS_OK)
189
- return REDIS_ERR;
201
+ snprintf(_port, 6, "%d", port);
202
+ memset(&hints,0,sizeof(hints));
203
+ hints.ai_family = AF_INET;
204
+ hints.ai_socktype = SOCK_STREAM;
190
205
 
191
- sa.sin_family = AF_INET;
192
- sa.sin_port = htons(port);
193
- if (inet_aton(addr, &sa.sin_addr) == 0) {
194
- struct hostent *he;
195
-
196
- he = gethostbyname(addr);
197
- if (he == NULL) {
198
- char buf[128];
199
- snprintf(buf,sizeof(buf),"Can't resolve: %s", addr);
200
- __redisSetError(c,REDIS_ERR_OTHER,buf);
201
- close(s);
202
- return REDIS_ERR;
203
- }
204
- memcpy(&sa.sin_addr, he->h_addr, sizeof(struct in_addr));
206
+ if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) {
207
+ __redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv));
208
+ return REDIS_ERR;
205
209
  }
206
-
207
- if (connect(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
208
- if (errno == EINPROGRESS && !blocking) {
209
- /* This is ok. */
210
- } else {
211
- if (redisContextWaitReady(c,s,timeout) != REDIS_OK)
212
- return REDIS_ERR;
210
+ for (p = servinfo; p != NULL; p = p->ai_next) {
211
+ if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1)
212
+ continue;
213
+
214
+ if (redisSetBlocking(c,s,0) != REDIS_OK)
215
+ goto error;
216
+ if (connect(s,p->ai_addr,p->ai_addrlen) == -1) {
217
+ if (errno == EHOSTUNREACH) {
218
+ close(s);
219
+ continue;
220
+ } else if (errno == EINPROGRESS && !blocking) {
221
+ /* This is ok. */
222
+ } else {
223
+ if (redisContextWaitReady(c,s,timeout) != REDIS_OK)
224
+ goto error;
225
+ }
213
226
  }
227
+ if (blocking && redisSetBlocking(c,s,1) != REDIS_OK)
228
+ goto error;
229
+ if (redisSetTcpNoDelay(c,s) != REDIS_OK)
230
+ goto error;
231
+
232
+ c->fd = s;
233
+ c->flags |= REDIS_CONNECTED;
234
+ rv = REDIS_OK;
235
+ goto end;
236
+ }
237
+ if (p == NULL) {
238
+ char buf[128];
239
+ snprintf(buf,sizeof(buf),"Can't create socket: %s",strerror(errno));
240
+ __redisSetError(c,REDIS_ERR_OTHER,buf);
241
+ goto error;
214
242
  }
215
243
 
216
- /* Reset socket to be blocking after connect(2). */
217
- if (blocking && redisSetBlocking(c,s,1) != REDIS_OK)
218
- return REDIS_ERR;
219
-
220
- if (redisSetTcpNoDelay(c,s) != REDIS_OK)
221
- return REDIS_ERR;
222
-
223
- c->fd = s;
224
- c->flags |= REDIS_CONNECTED;
225
- return REDIS_OK;
244
+ error:
245
+ rv = REDIS_ERR;
246
+ end:
247
+ freeaddrinfo(servinfo);
248
+ return rv; // Need to return REDIS_OK if alright
226
249
  }
227
250
 
228
251
  int redisContextConnectUnix(redisContext *c, const char *path, struct timeval *timeout) {
@@ -39,6 +39,7 @@
39
39
  #define AF_LOCAL AF_UNIX
40
40
  #endif
41
41
 
42
+ int redisCheckSocketError(redisContext *c, int fd);
42
43
  int redisContextSetTimeout(redisContext *c, struct timeval tv);
43
44
  int redisContextConnectTcp(redisContext *c, const char *addr, int port, struct timeval *timeout);
44
45
  int redisContextConnectUnix(redisContext *c, const char *path, struct timeval *timeout);
@@ -1,3 +1,4 @@
1
+ #include "fmacros.h"
1
2
  #include <stdio.h>
2
3
  #include <stdlib.h>
3
4
  #include <string.h>
@@ -10,10 +11,28 @@
10
11
 
11
12
  #include "hiredis.h"
12
13
 
14
+ enum connection_type {
15
+ CONN_TCP,
16
+ CONN_UNIX
17
+ };
18
+
19
+ struct config {
20
+ enum connection_type type;
21
+
22
+ struct {
23
+ const char *host;
24
+ int port;
25
+ } tcp;
26
+
27
+ struct {
28
+ const char *path;
29
+ } unix;
30
+ };
31
+
13
32
  /* The following lines make up our testing "framework" :) */
14
33
  static int tests = 0, fails = 0;
15
34
  #define test(_s) { printf("#%02d ", ++tests); printf(_s); }
16
- #define test_cond(_c) if(_c) printf("PASSED\n"); else {printf("FAILED\n"); fails++;}
35
+ #define test_cond(_c) if(_c) printf("\033[0;32mPASSED\033[0;0m\n"); else {printf("\033[0;31mFAILED\033[0;0m\n"); fails++;}
17
36
 
18
37
  static long long usec(void) {
19
38
  struct timeval tv;
@@ -21,15 +40,60 @@ static long long usec(void) {
21
40
  return (((long long)tv.tv_sec)*1000000)+tv.tv_usec;
22
41
  }
23
42
 
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);
43
+ static redisContext *select_database(redisContext *c) {
44
+ redisReply *reply;
45
+
46
+ /* Switch to DB 9 for testing, now that we know we can chat. */
47
+ reply = redisCommand(c,"SELECT 9");
48
+ assert(reply != NULL);
49
+ freeReplyObject(reply);
50
+
51
+ /* Make sure the DB is emtpy */
52
+ reply = redisCommand(c,"DBSIZE");
53
+ assert(reply != NULL);
54
+ if (reply->type == REDIS_REPLY_INTEGER && reply->integer == 0) {
55
+ /* Awesome, DB 9 is empty and we can continue. */
56
+ freeReplyObject(reply);
57
+ } else {
58
+ printf("Database #9 is not empty, test can not continue\n");
31
59
  exit(1);
32
60
  }
61
+
62
+ return c;
63
+ }
64
+
65
+ static void disconnect(redisContext *c) {
66
+ redisReply *reply;
67
+
68
+ /* Make sure we're on DB 9. */
69
+ reply = redisCommand(c,"SELECT 9");
70
+ assert(reply != NULL);
71
+ freeReplyObject(reply);
72
+ reply = redisCommand(c,"FLUSHDB");
73
+ assert(reply != NULL);
74
+ freeReplyObject(reply);
75
+
76
+ /* Free the context as well. */
77
+ redisFree(c);
78
+ }
79
+
80
+ static redisContext *connect(struct config config) {
81
+ redisContext *c = NULL;
82
+
83
+ if (config.type == CONN_TCP) {
84
+ c = redisConnect(config.tcp.host, config.tcp.port);
85
+ } else if (config.type == CONN_UNIX) {
86
+ c = redisConnectUnix(config.unix.path);
87
+ } else {
88
+ assert(NULL);
89
+ }
90
+
91
+ if (c->err) {
92
+ printf("Connection error: %s\n", c->errstr);
93
+ exit(1);
94
+ }
95
+
96
+ return select_database(c);
33
97
  }
34
98
 
35
99
  static void test_format_commands(void) {
@@ -78,29 +142,43 @@ static void test_format_commands(void) {
78
142
  len == 4+4+(3+2)+4+(1+2)+4+(1+2));
79
143
  free(cmd);
80
144
 
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: ");
145
+ /* Vararg width depends on the type. These tests make sure that the
146
+ * width is correctly determined using the format and subsequent varargs
147
+ * can correctly be interpolated. */
148
+ #define INTEGER_WIDTH_TEST(fmt, type) do { \
149
+ type value = 123; \
150
+ test("Format command with printf-delegation (" #type "): "); \
151
+ len = redisFormatCommand(&cmd,"key:%08" fmt " str:%s", value, "hello"); \
152
+ test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:00000123\r\n$9\r\nstr:hello\r\n",len) == 0 && \
153
+ len == 4+5+(12+2)+4+(9+2)); \
154
+ free(cmd); \
155
+ } while(0)
156
+
157
+ #define FLOAT_WIDTH_TEST(type) do { \
158
+ type value = 123.0; \
159
+ test("Format command with printf-delegation (" #type "): "); \
160
+ len = redisFormatCommand(&cmd,"key:%08.3f str:%s", value, "hello"); \
161
+ test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:0123.000\r\n$9\r\nstr:hello\r\n",len) == 0 && \
162
+ len == 4+5+(12+2)+4+(9+2)); \
163
+ free(cmd); \
164
+ } while(0)
165
+
166
+ INTEGER_WIDTH_TEST("d", int);
167
+ INTEGER_WIDTH_TEST("hhd", char);
168
+ INTEGER_WIDTH_TEST("hd", short);
169
+ INTEGER_WIDTH_TEST("ld", long);
170
+ INTEGER_WIDTH_TEST("lld", long long);
171
+ INTEGER_WIDTH_TEST("u", unsigned int);
172
+ INTEGER_WIDTH_TEST("hhu", unsigned char);
173
+ INTEGER_WIDTH_TEST("hu", unsigned short);
174
+ INTEGER_WIDTH_TEST("lu", unsigned long);
175
+ INTEGER_WIDTH_TEST("llu", unsigned long long);
176
+ FLOAT_WIDTH_TEST(float);
177
+ FLOAT_WIDTH_TEST(double);
178
+
179
+ test("Format command with invalid printf format: ");
100
180
  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);
181
+ test_cond(len == -1);
104
182
 
105
183
  const char *argv[3];
106
184
  argv[0] = "SET";
@@ -122,10 +200,85 @@ static void test_format_commands(void) {
122
200
  free(cmd);
123
201
  }
124
202
 
125
- static void test_blocking_connection(void) {
203
+ static void test_reply_reader(void) {
204
+ redisReader *reader;
205
+ void *reply;
206
+ int ret;
207
+
208
+ test("Error handling in reply parser: ");
209
+ reader = redisReaderCreate();
210
+ redisReaderFeed(reader,(char*)"@foo\r\n",6);
211
+ ret = redisReaderGetReply(reader,NULL);
212
+ test_cond(ret == REDIS_ERR &&
213
+ strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0);
214
+ redisReaderFree(reader);
215
+
216
+ /* when the reply already contains multiple items, they must be free'd
217
+ * on an error. valgrind will bark when this doesn't happen. */
218
+ test("Memory cleanup in reply parser: ");
219
+ reader = redisReaderCreate();
220
+ redisReaderFeed(reader,(char*)"*2\r\n",4);
221
+ redisReaderFeed(reader,(char*)"$5\r\nhello\r\n",11);
222
+ redisReaderFeed(reader,(char*)"@foo\r\n",6);
223
+ ret = redisReaderGetReply(reader,NULL);
224
+ test_cond(ret == REDIS_ERR &&
225
+ strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0);
226
+ redisReaderFree(reader);
227
+
228
+ test("Set error on nested multi bulks with depth > 2: ");
229
+ reader = redisReaderCreate();
230
+ redisReaderFeed(reader,(char*)"*1\r\n",4);
231
+ redisReaderFeed(reader,(char*)"*1\r\n",4);
232
+ redisReaderFeed(reader,(char*)"*1\r\n",4);
233
+ redisReaderFeed(reader,(char*)"*1\r\n",4);
234
+ ret = redisReaderGetReply(reader,NULL);
235
+ test_cond(ret == REDIS_ERR &&
236
+ strncasecmp(reader->errstr,"No support for",14) == 0);
237
+ redisReaderFree(reader);
238
+
239
+ test("Works with NULL functions for reply: ");
240
+ reader = redisReaderCreate();
241
+ reader->fn = NULL;
242
+ redisReaderFeed(reader,(char*)"+OK\r\n",5);
243
+ ret = redisReaderGetReply(reader,&reply);
244
+ test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS);
245
+ redisReaderFree(reader);
246
+
247
+ test("Works when a single newline (\\r\\n) covers two calls to feed: ");
248
+ reader = redisReaderCreate();
249
+ reader->fn = NULL;
250
+ redisReaderFeed(reader,(char*)"+OK\r",4);
251
+ ret = redisReaderGetReply(reader,&reply);
252
+ assert(ret == REDIS_OK && reply == NULL);
253
+ redisReaderFeed(reader,(char*)"\n",1);
254
+ ret = redisReaderGetReply(reader,&reply);
255
+ test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS);
256
+ redisReaderFree(reader);
257
+
258
+ test("Don't reset state after protocol error: ");
259
+ reader = redisReaderCreate();
260
+ reader->fn = NULL;
261
+ redisReaderFeed(reader,(char*)"x",1);
262
+ ret = redisReaderGetReply(reader,&reply);
263
+ assert(ret == REDIS_ERR);
264
+ ret = redisReaderGetReply(reader,&reply);
265
+ test_cond(ret == REDIS_ERR && reply == NULL);
266
+ redisReaderFree(reader);
267
+
268
+ /* Regression test for issue #45 on GitHub. */
269
+ test("Don't do empty allocation for empty multi bulk: ");
270
+ reader = redisReaderCreate();
271
+ redisReaderFeed(reader,(char*)"*0\r\n",4);
272
+ ret = redisReaderGetReply(reader,&reply);
273
+ test_cond(ret == REDIS_OK &&
274
+ ((redisReply*)reply)->type == REDIS_REPLY_ARRAY &&
275
+ ((redisReply*)reply)->elements == 0);
276
+ freeReplyObject(reply);
277
+ redisReaderFree(reader);
278
+ }
279
+
280
+ static void test_blocking_connection_errors(void) {
126
281
  redisContext *c;
127
- redisReply *reply;
128
- int major, minor;
129
282
 
130
283
  test("Returns error when host cannot be resolved: ");
131
284
  c = redisConnect((char*)"idontexist.local", 6379);
@@ -134,30 +287,29 @@ static void test_blocking_connection(void) {
134
287
  redisFree(c);
135
288
 
136
289
  test("Returns error when the port is not open: ");
137
- c = redisConnect((char*)"localhost", 56380);
290
+ c = redisConnect((char*)"localhost", 1);
138
291
  test_cond(c->err == REDIS_ERR_IO &&
139
292
  strcmp(c->errstr,"Connection refused") == 0);
140
293
  redisFree(c);
141
294
 
142
- __connect(&c);
295
+ test("Returns error when the unix socket path doesn't accept connections: ");
296
+ c = redisConnectUnix((char*)"/tmp/idontexist.sock");
297
+ test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */
298
+ redisFree(c);
299
+ }
300
+
301
+ static void test_blocking_connection(struct config config) {
302
+ redisContext *c;
303
+ redisReply *reply;
304
+
305
+ c = connect(config);
306
+
143
307
  test("Is able to deliver commands: ");
144
308
  reply = redisCommand(c,"PING");
145
309
  test_cond(reply->type == REDIS_REPLY_STATUS &&
146
310
  strcasecmp(reply->str,"pong") == 0)
147
311
  freeReplyObject(reply);
148
312
 
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
313
  test("Is a able to send commands verbatim: ");
162
314
  reply = redisCommand(c,"SET foo bar");
163
315
  test_cond (reply->type == REDIS_REPLY_STATUS &&
@@ -221,6 +373,17 @@ static void test_blocking_connection(void) {
221
373
  strcasecmp(reply->element[1]->str,"pong") == 0);
222
374
  freeReplyObject(reply);
223
375
 
376
+ disconnect(c);
377
+ }
378
+
379
+ static void test_blocking_io_errors(struct config config) {
380
+ redisContext *c;
381
+ redisReply *reply;
382
+ void *_reply;
383
+ int major, minor;
384
+
385
+ /* Connect to target given by config. */
386
+ c = connect(config);
224
387
  {
225
388
  /* Find out Redis version to determine the path for the next test */
226
389
  const char *field = "redis_version:";
@@ -240,7 +403,7 @@ static void test_blocking_connection(void) {
240
403
  /* > 2.0 returns OK on QUIT and read() should be issued once more
241
404
  * to know the descriptor is at EOF. */
242
405
  test_cond(strcasecmp(reply->str,"OK") == 0 &&
243
- redisGetReply(c,(void**)&reply) == REDIS_ERR);
406
+ redisGetReply(c,&_reply) == REDIS_ERR);
244
407
  freeReplyObject(reply);
245
408
  } else {
246
409
  test_cond(reply == NULL);
@@ -255,87 +418,20 @@ static void test_blocking_connection(void) {
255
418
  strcmp(c->errstr,"Server closed the connection") == 0);
256
419
  redisFree(c);
257
420
 
258
- __connect(&c);
421
+ c = connect(config);
259
422
  test("Returns I/O error on socket timeout: ");
260
423
  struct timeval tv = { 0, 1000 };
261
424
  assert(redisSetTimeout(c,tv) == REDIS_OK);
262
- test_cond(redisGetReply(c,(void**)&reply) == REDIS_ERR &&
425
+ test_cond(redisGetReply(c,&_reply) == REDIS_ERR &&
263
426
  c->err == REDIS_ERR_IO && errno == EAGAIN);
264
427
  redisFree(c);
265
-
266
- /* Context should be connected */
267
- __connect(&c);
268
- }
269
-
270
- static void test_reply_reader(void) {
271
- redisReader *reader;
272
- void *reply;
273
- int ret;
274
-
275
- test("Error handling in reply parser: ");
276
- reader = redisReaderCreate();
277
- redisReaderFeed(reader,(char*)"@foo\r\n",6);
278
- ret = redisReaderGetReply(reader,NULL);
279
- test_cond(ret == REDIS_ERR &&
280
- strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0);
281
- redisReaderFree(reader);
282
-
283
- /* when the reply already contains multiple items, they must be free'd
284
- * on an error. valgrind will bark when this doesn't happen. */
285
- test("Memory cleanup in reply parser: ");
286
- reader = redisReaderCreate();
287
- redisReaderFeed(reader,(char*)"*2\r\n",4);
288
- redisReaderFeed(reader,(char*)"$5\r\nhello\r\n",11);
289
- redisReaderFeed(reader,(char*)"@foo\r\n",6);
290
- ret = redisReaderGetReply(reader,NULL);
291
- test_cond(ret == REDIS_ERR &&
292
- strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0);
293
- redisReaderFree(reader);
294
-
295
- test("Set error on nested multi bulks with depth > 1: ");
296
- reader = redisReaderCreate();
297
- redisReaderFeed(reader,(char*)"*1\r\n",4);
298
- redisReaderFeed(reader,(char*)"*1\r\n",4);
299
- redisReaderFeed(reader,(char*)"*1\r\n",4);
300
- ret = redisReaderGetReply(reader,NULL);
301
- test_cond(ret == REDIS_ERR &&
302
- strncasecmp(reader->errstr,"No support for",14) == 0);
303
- redisReaderFree(reader);
304
-
305
- test("Works with NULL functions for reply: ");
306
- reader = redisReaderCreate();
307
- reader->fn = NULL;
308
- redisReaderFeed(reader,(char*)"+OK\r\n",5);
309
- ret = redisReaderGetReply(reader,&reply);
310
- test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS);
311
- redisReaderFree(reader);
312
-
313
- test("Works when a single newline (\\r\\n) covers two calls to feed: ");
314
- reader = redisReaderCreate();
315
- reader->fn = NULL;
316
- redisReaderFeed(reader,(char*)"+OK\r",4);
317
- ret = redisReaderGetReply(reader,&reply);
318
- assert(ret == REDIS_OK && reply == NULL);
319
- redisReaderFeed(reader,(char*)"\n",1);
320
- ret = redisReaderGetReply(reader,&reply);
321
- test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS);
322
- redisReaderFree(reader);
323
-
324
- test("Don't reset state after protocol error: ");
325
- reader = redisReaderCreate();
326
- reader->fn = NULL;
327
- redisReaderFeed(reader,(char*)"x",1);
328
- ret = redisReaderGetReply(reader,&reply);
329
- assert(ret == REDIS_ERR);
330
- ret = redisReaderGetReply(reader,&reply);
331
- test_cond(ret == REDIS_ERR && reply == NULL);
332
428
  }
333
429
 
334
- static void test_throughput(void) {
430
+ static void test_throughput(struct config config) {
431
+ redisContext *c = connect(config);
432
+ redisReply **replies;
335
433
  int i, num;
336
434
  long long t1, t2;
337
- redisContext *c = blocking_context;
338
- redisReply **replies;
339
435
 
340
436
  test("Throughput:\n");
341
437
  for (i = 0; i < 500; i++)
@@ -392,18 +488,8 @@ static void test_throughput(void) {
392
488
  for (i = 0; i < num; i++) freeReplyObject(replies[i]);
393
489
  free(replies);
394
490
  printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
395
- }
396
-
397
- static void cleanup(void) {
398
- redisContext *c = blocking_context;
399
- redisReply *reply;
400
491
 
401
- /* Make sure we're on DB 9 */
402
- reply = redisCommand(c,"SELECT 9");
403
- assert(reply != NULL); freeReplyObject(reply);
404
- reply = redisCommand(c,"FLUSHDB");
405
- assert(reply != NULL); freeReplyObject(reply);
406
- redisFree(c);
492
+ disconnect(c);
407
493
  }
408
494
 
409
495
  // static long __test_callback_flags = 0;
@@ -425,7 +511,7 @@ static void cleanup(void) {
425
511
  // static redisContext *__connect_nonblock() {
426
512
  // /* Reset callback flags */
427
513
  // __test_callback_flags = 0;
428
- // return redisConnectNonBlock("127.0.0.1", 6379, NULL);
514
+ // return redisConnectNonBlock("127.0.0.1", port, NULL);
429
515
  // }
430
516
  //
431
517
  // static void test_nonblocking_connection() {
@@ -506,23 +592,62 @@ static void cleanup(void) {
506
592
  // }
507
593
 
508
594
  int main(int argc, char **argv) {
509
- if (argc > 1) {
510
- if (strcmp(argv[1],"-s") == 0)
511
- use_unix = 1;
595
+ struct config cfg = {
596
+ .tcp = {
597
+ .host = "127.0.0.1",
598
+ .port = 6379
599
+ },
600
+ .unix = {
601
+ .path = "/tmp/redis.sock"
602
+ }
603
+ };
604
+ int throughput = 1;
605
+
606
+ /* Ignore broken pipe signal (for I/O error tests). */
607
+ signal(SIGPIPE, SIG_IGN);
608
+
609
+ /* Parse command line options. */
610
+ argv++; argc--;
611
+ while (argc) {
612
+ if (argc >= 2 && !strcmp(argv[0],"-h")) {
613
+ argv++; argc--;
614
+ cfg.tcp.host = argv[0];
615
+ } else if (argc >= 2 && !strcmp(argv[0],"-p")) {
616
+ argv++; argc--;
617
+ cfg.tcp.port = atoi(argv[0]);
618
+ } else if (argc >= 2 && !strcmp(argv[0],"-s")) {
619
+ argv++; argc--;
620
+ cfg.unix.path = argv[0];
621
+ } else if (argc >= 1 && !strcmp(argv[0],"--skip-throughput")) {
622
+ throughput = 0;
623
+ } else {
624
+ fprintf(stderr, "Invalid argument: %s\n", argv[0]);
625
+ exit(1);
626
+ }
627
+ argv++; argc--;
512
628
  }
513
629
 
514
- signal(SIGPIPE, SIG_IGN);
515
630
  test_format_commands();
516
- test_blocking_connection();
517
631
  test_reply_reader();
518
- // test_nonblocking_connection();
519
- test_throughput();
520
- cleanup();
632
+ test_blocking_connection_errors();
521
633
 
522
- if (fails == 0) {
523
- printf("ALL TESTS PASSED\n");
524
- } else {
634
+ printf("\nTesting against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port);
635
+ cfg.type = CONN_TCP;
636
+ test_blocking_connection(cfg);
637
+ test_blocking_io_errors(cfg);
638
+ if (throughput) test_throughput(cfg);
639
+
640
+ printf("\nTesting against Unix socket connection (%s):\n", cfg.unix.path);
641
+ cfg.type = CONN_UNIX;
642
+ test_blocking_connection(cfg);
643
+ test_blocking_io_errors(cfg);
644
+ if (throughput) test_throughput(cfg);
645
+
646
+ if (fails) {
525
647
  printf("*** %d TESTS FAILED ***\n", fails);
648
+ return 1;
526
649
  }
650
+
651
+ printf("ALL TESTS PASSED\n");
527
652
  return 0;
528
653
  }