/hiredis/test.c
C | 806 lines | 569 code | 110 blank | 127 comment | 198 complexity | 966520bb1125045a2f47a3b4b00e1c79 MD5 | raw file
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 16enum connection_type { 17 CONN_TCP, 18 CONN_UNIX, 19 CONN_FD 20}; 21 22struct 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; 34}; 35 36/* The following lines make up our testing "framework" :) */ 37static 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 41static 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 54static 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 76static 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 94static 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.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.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 125static 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 229static void test_append_formatted_commands(struct config config) { 230 redisContext *c; 231 redisReply *reply; 232 char *cmd; 233 int len; 234 235 c = connect(config); 236 237 test("Append format command: "); 238 239 len = redisFormatCommand(&cmd, "SET foo bar"); 240 241 test_cond(redisAppendFormattedCommand(c, cmd, len) == REDIS_OK); 242 243 assert(redisGetReply(c, (void*)&reply) == REDIS_OK); 244 245 free(cmd); 246 freeReplyObject(reply); 247 248 disconnect(c, 0); 249} 250 251static void test_reply_reader(void) { 252 redisReader *reader; 253 void *reply; 254 int ret; 255 int i; 256 257 test("Error handling in reply parser: "); 258 reader = redisReaderCreate(); 259 redisReaderFeed(reader,(char*)"@foo\r\n",6); 260 ret = redisReaderGetReply(reader,NULL); 261 test_cond(ret == REDIS_ERR && 262 strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0); 263 redisReaderFree(reader); 264 265 /* when the reply already contains multiple items, they must be free'd 266 * on an error. valgrind will bark when this doesn't happen. */ 267 test("Memory cleanup in reply parser: "); 268 reader = redisReaderCreate(); 269 redisReaderFeed(reader,(char*)"*2\r\n",4); 270 redisReaderFeed(reader,(char*)"$5\r\nhello\r\n",11); 271 redisReaderFeed(reader,(char*)"@foo\r\n",6); 272 ret = redisReaderGetReply(reader,NULL); 273 test_cond(ret == REDIS_ERR && 274 strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0); 275 redisReaderFree(reader); 276 277 test("Set error on nested multi bulks with depth > 7: "); 278 reader = redisReaderCreate(); 279 280 for (i = 0; i < 9; i++) { 281 redisReaderFeed(reader,(char*)"*1\r\n",4); 282 } 283 284 ret = redisReaderGetReply(reader,NULL); 285 test_cond(ret == REDIS_ERR && 286 strncasecmp(reader->errstr,"No support for",14) == 0); 287 redisReaderFree(reader); 288 289 test("Works with NULL functions for reply: "); 290 reader = redisReaderCreate(); 291 reader->fn = NULL; 292 redisReaderFeed(reader,(char*)"+OK\r\n",5); 293 ret = redisReaderGetReply(reader,&reply); 294 test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS); 295 redisReaderFree(reader); 296 297 test("Works when a single newline (\\r\\n) covers two calls to feed: "); 298 reader = redisReaderCreate(); 299 reader->fn = NULL; 300 redisReaderFeed(reader,(char*)"+OK\r",4); 301 ret = redisReaderGetReply(reader,&reply); 302 assert(ret == REDIS_OK && reply == NULL); 303 redisReaderFeed(reader,(char*)"\n",1); 304 ret = redisReaderGetReply(reader,&reply); 305 test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS); 306 redisReaderFree(reader); 307 308 test("Don't reset state after protocol error: "); 309 reader = redisReaderCreate(); 310 reader->fn = NULL; 311 redisReaderFeed(reader,(char*)"x",1); 312 ret = redisReaderGetReply(reader,&reply); 313 assert(ret == REDIS_ERR); 314 ret = redisReaderGetReply(reader,&reply); 315 test_cond(ret == REDIS_ERR && reply == NULL); 316 redisReaderFree(reader); 317 318 /* Regression test for issue #45 on GitHub. */ 319 test("Don't do empty allocation for empty multi bulk: "); 320 reader = redisReaderCreate(); 321 redisReaderFeed(reader,(char*)"*0\r\n",4); 322 ret = redisReaderGetReply(reader,&reply); 323 test_cond(ret == REDIS_OK && 324 ((redisReply*)reply)->type == REDIS_REPLY_ARRAY && 325 ((redisReply*)reply)->elements == 0); 326 freeReplyObject(reply); 327 redisReaderFree(reader); 328} 329 330static void test_free_null(void) { 331 void *redisContext = NULL; 332 void *reply = NULL; 333 334 test("Don't fail when redisFree is passed a NULL value: "); 335 redisFree(redisContext); 336 test_cond(redisContext == NULL); 337 338 test("Don't fail when freeReplyObject is passed a NULL value: "); 339 freeReplyObject(reply); 340 test_cond(reply == NULL); 341} 342 343static void test_blocking_connection_errors(void) { 344 redisContext *c; 345 346 test("Returns error when host cannot be resolved: "); 347 c = redisConnect((char*)"idontexist.test", 6379); 348 test_cond(c->err == REDIS_ERR_OTHER && 349 (strcmp(c->errstr,"Name or service not known") == 0 || 350 strcmp(c->errstr,"Can't resolve: idontexist.test") == 0 || 351 strcmp(c->errstr,"nodename nor servname provided, or not known") == 0 || 352 strcmp(c->errstr,"No address associated with hostname") == 0 || 353 strcmp(c->errstr,"Temporary failure in name resolution") == 0 || 354 strcmp(c->errstr,"no address associated with name") == 0)); 355 redisFree(c); 356 357 test("Returns error when the port is not open: "); 358 c = redisConnect((char*)"localhost", 1); 359 test_cond(c->err == REDIS_ERR_IO && 360 strcmp(c->errstr,"Connection refused") == 0); 361 redisFree(c); 362 363 test("Returns error when the unix socket path doesn't accept connections: "); 364 c = redisConnectUnix((char*)"/tmp/idontexist.sock"); 365 test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */ 366 redisFree(c); 367} 368 369static void test_blocking_connection(struct config config) { 370 redisContext *c; 371 redisReply *reply; 372 373 c = connect(config); 374 375 test("Is able to deliver commands: "); 376 reply = redisCommand(c,"PING"); 377 test_cond(reply->type == REDIS_REPLY_STATUS && 378 strcasecmp(reply->str,"pong") == 0) 379 freeReplyObject(reply); 380 381 test("Is a able to send commands verbatim: "); 382 reply = redisCommand(c,"SET foo bar"); 383 test_cond (reply->type == REDIS_REPLY_STATUS && 384 strcasecmp(reply->str,"ok") == 0) 385 freeReplyObject(reply); 386 387 test("%%s String interpolation works: "); 388 reply = redisCommand(c,"SET %s %s","foo","hello world"); 389 freeReplyObject(reply); 390 reply = redisCommand(c,"GET foo"); 391 test_cond(reply->type == REDIS_REPLY_STRING && 392 strcmp(reply->str,"hello world") == 0); 393 freeReplyObject(reply); 394 395 test("%%b String interpolation works: "); 396 reply = redisCommand(c,"SET %b %b","foo",(size_t)3,"hello\x00world",(size_t)11); 397 freeReplyObject(reply); 398 reply = redisCommand(c,"GET foo"); 399 test_cond(reply->type == REDIS_REPLY_STRING && 400 memcmp(reply->str,"hello\x00world",11) == 0) 401 402 test("Binary reply length is correct: "); 403 test_cond(reply->len == 11) 404 freeReplyObject(reply); 405 406 test("Can parse nil replies: "); 407 reply = redisCommand(c,"GET nokey"); 408 test_cond(reply->type == REDIS_REPLY_NIL) 409 freeReplyObject(reply); 410 411 /* test 7 */ 412 test("Can parse integer replies: "); 413 reply = redisCommand(c,"INCR mycounter"); 414 test_cond(reply->type == REDIS_REPLY_INTEGER && reply->integer == 1) 415 freeReplyObject(reply); 416 417 test("Can parse multi bulk replies: "); 418 freeReplyObject(redisCommand(c,"LPUSH mylist foo")); 419 freeReplyObject(redisCommand(c,"LPUSH mylist bar")); 420 reply = redisCommand(c,"LRANGE mylist 0 -1"); 421 test_cond(reply->type == REDIS_REPLY_ARRAY && 422 reply->elements == 2 && 423 !memcmp(reply->element[0]->str,"bar",3) && 424 !memcmp(reply->element[1]->str,"foo",3)) 425 freeReplyObject(reply); 426 427 /* m/e with multi bulk reply *before* other reply. 428 * specifically test ordering of reply items to parse. */ 429 test("Can handle nested multi bulk replies: "); 430 freeReplyObject(redisCommand(c,"MULTI")); 431 freeReplyObject(redisCommand(c,"LRANGE mylist 0 -1")); 432 freeReplyObject(redisCommand(c,"PING")); 433 reply = (redisCommand(c,"EXEC")); 434 test_cond(reply->type == REDIS_REPLY_ARRAY && 435 reply->elements == 2 && 436 reply->element[0]->type == REDIS_REPLY_ARRAY && 437 reply->element[0]->elements == 2 && 438 !memcmp(reply->element[0]->element[0]->str,"bar",3) && 439 !memcmp(reply->element[0]->element[1]->str,"foo",3) && 440 reply->element[1]->type == REDIS_REPLY_STATUS && 441 strcasecmp(reply->element[1]->str,"pong") == 0); 442 freeReplyObject(reply); 443 444 disconnect(c, 0); 445} 446 447static void test_blocking_connection_timeouts(struct config config) { 448 redisContext *c; 449 redisReply *reply; 450 ssize_t s; 451 const char *cmd = "DEBUG SLEEP 3\r\n"; 452 struct timeval tv; 453 454 c = connect(config); 455 test("Successfully completes a command when the timeout is not exceeded: "); 456 reply = redisCommand(c,"SET foo fast"); 457 freeReplyObject(reply); 458 tv.tv_sec = 0; 459 tv.tv_usec = 10000; 460 redisSetTimeout(c, tv); 461 reply = redisCommand(c, "GET foo"); 462 test_cond(reply != NULL && reply->type == REDIS_REPLY_STRING && memcmp(reply->str, "fast", 4) == 0); 463 freeReplyObject(reply); 464 disconnect(c, 0); 465 466 c = connect(config); 467 test("Does not return a reply when the command times out: "); 468 s = write(c->fd, cmd, strlen(cmd)); 469 tv.tv_sec = 0; 470 tv.tv_usec = 10000; 471 redisSetTimeout(c, tv); 472 reply = redisCommand(c, "GET foo"); 473 test_cond(s > 0 && reply == NULL && c->err == REDIS_ERR_IO && strcmp(c->errstr, "Resource temporarily unavailable") == 0); 474 freeReplyObject(reply); 475 476 test("Reconnect properly reconnects after a timeout: "); 477 redisReconnect(c); 478 reply = redisCommand(c, "PING"); 479 test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0); 480 freeReplyObject(reply); 481 482 test("Reconnect properly uses owned parameters: "); 483 config.tcp.host = "foo"; 484 config.unix.path = "foo"; 485 redisReconnect(c); 486 reply = redisCommand(c, "PING"); 487 test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0); 488 freeReplyObject(reply); 489 490 disconnect(c, 0); 491} 492 493static void test_blocking_io_errors(struct config config) { 494 redisContext *c; 495 redisReply *reply; 496 void *_reply; 497 int major, minor; 498 499 /* Connect to target given by config. */ 500 c = connect(config); 501 { 502 /* Find out Redis version to determine the path for the next test */ 503 const char *field = "redis_version:"; 504 char *p, *eptr; 505 506 reply = redisCommand(c,"INFO"); 507 p = strstr(reply->str,field); 508 major = strtol(p+strlen(field),&eptr,10); 509 p = eptr+1; /* char next to the first "." */ 510 minor = strtol(p,&eptr,10); 511 freeReplyObject(reply); 512 } 513 514 test("Returns I/O error when the connection is lost: "); 515 reply = redisCommand(c,"QUIT"); 516 if (major > 2 || (major == 2 && minor > 0)) { 517 /* > 2.0 returns OK on QUIT and read() should be issued once more 518 * to know the descriptor is at EOF. */ 519 test_cond(strcasecmp(reply->str,"OK") == 0 && 520 redisGetReply(c,&_reply) == REDIS_ERR); 521 freeReplyObject(reply); 522 } else { 523 test_cond(reply == NULL); 524 } 525 526 /* On 2.0, QUIT will cause the connection to be closed immediately and 527 * the read(2) for the reply on QUIT will set the error to EOF. 528 * On >2.0, QUIT will return with OK and another read(2) needed to be 529 * issued to find out the socket was closed by the server. In both 530 * conditions, the error will be set to EOF. */ 531 assert(c->err == REDIS_ERR_EOF && 532 strcmp(c->errstr,"Server closed the connection") == 0); 533 redisFree(c); 534 535 c = connect(config); 536 test("Returns I/O error on socket timeout: "); 537 struct timeval tv = { 0, 1000 }; 538 assert(redisSetTimeout(c,tv) == REDIS_OK); 539 test_cond(redisGetReply(c,&_reply) == REDIS_ERR && 540 c->err == REDIS_ERR_IO && errno == EAGAIN); 541 redisFree(c); 542} 543 544static void test_invalid_timeout_errors(struct config config) { 545 redisContext *c; 546 547 test("Set error when an invalid timeout usec value is given to redisConnectWithTimeout: "); 548 549 config.tcp.timeout.tv_sec = 0; 550 config.tcp.timeout.tv_usec = 10000001; 551 552 c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout); 553 554 test_cond(c->err == REDIS_ERR_IO); 555 redisFree(c); 556 557 test("Set error when an invalid timeout sec value is given to redisConnectWithTimeout: "); 558 559 config.tcp.timeout.tv_sec = (((LONG_MAX) - 999) / 1000) + 1; 560 config.tcp.timeout.tv_usec = 0; 561 562 c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout); 563 564 test_cond(c->err == REDIS_ERR_IO); 565 redisFree(c); 566} 567 568static void test_throughput(struct config config) { 569 redisContext *c = connect(config); 570 redisReply **replies; 571 int i, num; 572 long long t1, t2; 573 574 test("Throughput:\n"); 575 for (i = 0; i < 500; i++) 576 freeReplyObject(redisCommand(c,"LPUSH mylist foo")); 577 578 num = 1000; 579 replies = malloc(sizeof(redisReply*)*num); 580 t1 = usec(); 581 for (i = 0; i < num; i++) { 582 replies[i] = redisCommand(c,"PING"); 583 assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS); 584 } 585 t2 = usec(); 586 for (i = 0; i < num; i++) freeReplyObject(replies[i]); 587 free(replies); 588 printf("\t(%dx PING: %.3fs)\n", num, (t2-t1)/1000000.0); 589 590 replies = malloc(sizeof(redisReply*)*num); 591 t1 = usec(); 592 for (i = 0; i < num; i++) { 593 replies[i] = redisCommand(c,"LRANGE mylist 0 499"); 594 assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY); 595 assert(replies[i] != NULL && replies[i]->elements == 500); 596 } 597 t2 = usec(); 598 for (i = 0; i < num; i++) freeReplyObject(replies[i]); 599 free(replies); 600 printf("\t(%dx LRANGE with 500 elements: %.3fs)\n", num, (t2-t1)/1000000.0); 601 602 num = 10000; 603 replies = malloc(sizeof(redisReply*)*num); 604 for (i = 0; i < num; i++) 605 redisAppendCommand(c,"PING"); 606 t1 = usec(); 607 for (i = 0; i < num; i++) { 608 assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK); 609 assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS); 610 } 611 t2 = usec(); 612 for (i = 0; i < num; i++) freeReplyObject(replies[i]); 613 free(replies); 614 printf("\t(%dx PING (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); 615 616 replies = malloc(sizeof(redisReply*)*num); 617 for (i = 0; i < num; i++) 618 redisAppendCommand(c,"LRANGE mylist 0 499"); 619 t1 = usec(); 620 for (i = 0; i < num; i++) { 621 assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK); 622 assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY); 623 assert(replies[i] != NULL && replies[i]->elements == 500); 624 } 625 t2 = usec(); 626 for (i = 0; i < num; i++) freeReplyObject(replies[i]); 627 free(replies); 628 printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); 629 630 disconnect(c, 0); 631} 632 633// static long __test_callback_flags = 0; 634// static void __test_callback(redisContext *c, void *privdata) { 635// ((void)c); 636// /* Shift to detect execution order */ 637// __test_callback_flags <<= 8; 638// __test_callback_flags |= (long)privdata; 639// } 640// 641// static void __test_reply_callback(redisContext *c, redisReply *reply, void *privdata) { 642// ((void)c); 643// /* Shift to detect execution order */ 644// __test_callback_flags <<= 8; 645// __test_callback_flags |= (long)privdata; 646// if (reply) freeReplyObject(reply); 647// } 648// 649// static redisContext *__connect_nonblock() { 650// /* Reset callback flags */ 651// __test_callback_flags = 0; 652// return redisConnectNonBlock("127.0.0.1", port, NULL); 653// } 654// 655// static void test_nonblocking_connection() { 656// redisContext *c; 657// int wdone = 0; 658// 659// test("Calls command callback when command is issued: "); 660// c = __connect_nonblock(); 661// redisSetCommandCallback(c,__test_callback,(void*)1); 662// redisCommand(c,"PING"); 663// test_cond(__test_callback_flags == 1); 664// redisFree(c); 665// 666// test("Calls disconnect callback on redisDisconnect: "); 667// c = __connect_nonblock(); 668// redisSetDisconnectCallback(c,__test_callback,(void*)2); 669// redisDisconnect(c); 670// test_cond(__test_callback_flags == 2); 671// redisFree(c); 672// 673// test("Calls disconnect callback and free callback on redisFree: "); 674// c = __connect_nonblock(); 675// redisSetDisconnectCallback(c,__test_callback,(void*)2); 676// redisSetFreeCallback(c,__test_callback,(void*)4); 677// redisFree(c); 678// test_cond(__test_callback_flags == ((2 << 8) | 4)); 679// 680// test("redisBufferWrite against empty write buffer: "); 681// c = __connect_nonblock(); 682// test_cond(redisBufferWrite(c,&wdone) == REDIS_OK && wdone == 1); 683// redisFree(c); 684// 685// test("redisBufferWrite against not yet connected fd: "); 686// c = __connect_nonblock(); 687// redisCommand(c,"PING"); 688// test_cond(redisBufferWrite(c,NULL) == REDIS_ERR && 689// strncmp(c->error,"write:",6) == 0); 690// redisFree(c); 691// 692// test("redisBufferWrite against closed fd: "); 693// c = __connect_nonblock(); 694// redisCommand(c,"PING"); 695// redisDisconnect(c); 696// test_cond(redisBufferWrite(c,NULL) == REDIS_ERR && 697// strncmp(c->error,"write:",6) == 0); 698// redisFree(c); 699// 700// test("Process callbacks in the right sequence: "); 701// c = __connect_nonblock(); 702// redisCommandWithCallback(c,__test_reply_callback,(void*)1,"PING"); 703// redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING"); 704// redisCommandWithCallback(c,__test_reply_callback,(void*)3,"PING"); 705// 706// /* Write output buffer */ 707// wdone = 0; 708// while(!wdone) { 709// usleep(500); 710// redisBufferWrite(c,&wdone); 711// } 712// 713// /* Read until at least one callback is executed (the 3 replies will 714// * arrive in a single packet, causing all callbacks to be executed in 715// * a single pass). */ 716// while(__test_callback_flags == 0) { 717// assert(redisBufferRead(c) == REDIS_OK); 718// redisProcessCallbacks(c); 719// } 720// test_cond(__test_callback_flags == 0x010203); 721// redisFree(c); 722// 723// test("redisDisconnect executes pending callbacks with NULL reply: "); 724// c = __connect_nonblock(); 725// redisSetDisconnectCallback(c,__test_callback,(void*)1); 726// redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING"); 727// redisDisconnect(c); 728// test_cond(__test_callback_flags == 0x0201); 729// redisFree(c); 730// } 731 732int main(int argc, char **argv) { 733 struct config cfg = { 734 .tcp = { 735 .host = "127.0.0.1", 736 .port = 6379 737 }, 738 .unix = { 739 .path = "/tmp/redis.sock" 740 } 741 }; 742 int throughput = 1; 743 int test_inherit_fd = 1; 744 745 /* Ignore broken pipe signal (for I/O error tests). */ 746 signal(SIGPIPE, SIG_IGN); 747 748 /* Parse command line options. */ 749 argv++; argc--; 750 while (argc) { 751 if (argc >= 2 && !strcmp(argv[0],"-h")) { 752 argv++; argc--; 753 cfg.tcp.host = argv[0]; 754 } else if (argc >= 2 && !strcmp(argv[0],"-p")) { 755 argv++; argc--; 756 cfg.tcp.port = atoi(argv[0]); 757 } else if (argc >= 2 && !strcmp(argv[0],"-s")) { 758 argv++; argc--; 759 cfg.unix.path = argv[0]; 760 } else if (argc >= 1 && !strcmp(argv[0],"--skip-throughput")) { 761 throughput = 0; 762 } else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) { 763 test_inherit_fd = 0; 764 } else { 765 fprintf(stderr, "Invalid argument: %s\n", argv[0]); 766 exit(1); 767 } 768 argv++; argc--; 769 } 770 771 test_format_commands(); 772 test_reply_reader(); 773 test_blocking_connection_errors(); 774 test_free_null(); 775 776 printf("\nTesting against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port); 777 cfg.type = CONN_TCP; 778 test_blocking_connection(cfg); 779 test_blocking_connection_timeouts(cfg); 780 test_blocking_io_errors(cfg); 781 test_invalid_timeout_errors(cfg); 782 test_append_formatted_commands(cfg); 783 if (throughput) test_throughput(cfg); 784 785 printf("\nTesting against Unix socket connection (%s):\n", cfg.unix.path); 786 cfg.type = CONN_UNIX; 787 test_blocking_connection(cfg); 788 test_blocking_connection_timeouts(cfg); 789 test_blocking_io_errors(cfg); 790 if (throughput) test_throughput(cfg); 791 792 if (test_inherit_fd) { 793 printf("\nTesting against inherited fd (%s):\n", cfg.unix.path); 794 cfg.type = CONN_FD; 795 test_blocking_connection(cfg); 796 } 797 798 799 if (fails) { 800 printf("*** %d TESTS FAILED ***\n", fails); 801 return 1; 802 } 803 804 printf("ALL TESTS PASSED\n"); 805 return 0; 806}