hiredis 0.4.1 → 0.4.2.pre

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.
@@ -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
  }