From: antirez Date: Thu, 26 Aug 2010 10:04:24 +0000 (+0200) Subject: Merge remote branch 'pietern/intset-split' X-Git-Url: https://git.saurik.com/redis.git/commitdiff_plain/acc75bfd4f1607625876d74d6447efaaf505db59?hp=f9d5c4e33c8b03d20bd9e4ec145792c000a7210f Merge remote branch 'pietern/intset-split' --- diff --git a/src/db.c b/src/db.c index 0dec95b1..6d287d72 100644 --- a/src/db.c +++ b/src/db.c @@ -514,15 +514,14 @@ void expireatCommand(redisClient *c) { } void ttlCommand(redisClient *c) { - time_t expire; - int ttl = -1; + time_t expire, ttl = -1; expire = getExpire(c->db,c->argv[1]); if (expire != -1) { - ttl = (int) (expire-time(NULL)); + ttl = (expire-time(NULL)); if (ttl < 0) ttl = -1; } - addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",ttl)); + addReplyLongLong(c,(long long)ttl); } void persistCommand(redisClient *c) { diff --git a/src/networking.c b/src/networking.c index e5a66984..a39be7c4 100644 --- a/src/networking.c +++ b/src/networking.c @@ -255,7 +255,8 @@ void freeClient(redisClient *c) { server.vm_blocked_clients--; } listRelease(c->io_keys); - /* Master/slave cleanup */ + /* Master/slave cleanup. + * Case 1: we lost the connection with a slave. */ if (c->flags & REDIS_SLAVE) { if (c->replstate == REDIS_REPL_SEND_BULK && c->repldbfd != -1) close(c->repldbfd); @@ -264,9 +265,20 @@ void freeClient(redisClient *c) { redisAssert(ln != NULL); listDelNode(l,ln); } + + /* Case 2: we lost the connection with the master. */ if (c->flags & REDIS_MASTER) { server.master = NULL; server.replstate = REDIS_REPL_CONNECT; + /* Since we lost the connection with the master, we should also + * close the connection with all our slaves if we have any, so + * when we'll resync with the master the other slaves will sync again + * with us as well. Note that also when the slave is not connected + * to the master it will keep refusing connections by other slaves. */ + while (listLength(server.slaves)) { + ln = listFirst(server.slaves); + freeClient((redisClient*)ln->value); + } } /* Release memory */ zfree(c->argv); @@ -466,6 +478,7 @@ void closeTimedoutClients(void) { if (server.maxidletime && !(c->flags & REDIS_SLAVE) && /* no timeout for slaves */ !(c->flags & REDIS_MASTER) && /* no timeout for masters */ + !(c->flags & REDIS_BLOCKED) && /* no timeout for BLPOP */ dictSize(c->pubsub_channels) == 0 && /* no timeout for pubsub */ listLength(c->pubsub_patterns) == 0 && (now - c->lastinteraction > server.maxidletime)) diff --git a/src/object.c b/src/object.c index b16123eb..dae7f97b 100644 --- a/src/object.c +++ b/src/object.c @@ -377,6 +377,8 @@ int getLongLongFromObject(robj *o, long long *target) { value = strtoll(o->ptr, &eptr, 10); if (errno == ERANGE) return REDIS_ERR; if (eptr[0] != '\0') return REDIS_ERR; + if (errno == ERANGE && (value == LLONG_MIN || value == LLONG_MAX)) + return REDIS_ERR; } else if (o->encoding == REDIS_ENCODING_INT) { value = (long)o->ptr; } else { @@ -394,7 +396,7 @@ int getLongLongFromObjectOrReply(redisClient *c, robj *o, long long *target, con if (msg != NULL) { addReplySds(c, sdscatprintf(sdsempty(), "-ERR %s\r\n", msg)); } else { - addReplySds(c, sdsnew("-ERR value is not an integer\r\n")); + addReplySds(c, sdsnew("-ERR value is not an integer or out of range\r\n")); } return REDIS_ERR; } diff --git a/src/redis-cli.c b/src/redis-cli.c index b4a10890..8b7d0777 100644 --- a/src/redis-cli.c +++ b/src/redis-cli.c @@ -36,6 +36,8 @@ #include #include #include +#include +#include #include "anet.h" #include "sds.h" @@ -54,12 +56,13 @@ static struct config { int hostport; long repeat; int dbnum; - int argn_from_stdin; int interactive; int shutdown; int monitor_mode; int pubsub_mode; - int raw_output; + int raw_output; /* output mode per command */ + int tty; /* flag for default output format */ + char mb_sep; char *auth; char *historyfile; } config; @@ -67,11 +70,14 @@ static struct config { static int cliReadReply(int fd); static void usage(); -static int cliConnect(void) { +/* Connect to the client. If force is not zero the connection is performed + * even if there is already a connected socket. */ +static int cliConnect(int force) { char err[ANET_ERR_LEN]; static int fd = ANET_ERR; - if (fd == ANET_ERR) { + if (fd == ANET_ERR || force) { + if (force) close(fd); fd = anetTcpConnect(err,config.hostip,config.hostport); if (fd == ANET_ERR) { fprintf(stderr, "Could not connect to Redis at %s:%d: %s", config.hostip, config.hostport, err); @@ -107,7 +113,7 @@ static int cliReadSingleLineReply(int fd, int quiet) { if (reply == NULL) return 1; if (!quiet) - printf("%s\n", reply); + printf("%s", reply); sdsfree(reply); return 0; } @@ -134,7 +140,7 @@ static void printStringRepr(char *s, int len) { } s++; } - printf("\"\n"); + printf("\""); } static int cliReadBulkReply(int fd) { @@ -152,7 +158,7 @@ static int cliReadBulkReply(int fd) { reply = zmalloc(bulklen); anetRead(fd,reply,bulklen); anetRead(fd,crlf,2); - if (config.raw_output || !isatty(fileno(stdout))) { + if (config.raw_output || !config.tty) { if (bulklen && fwrite(reply,bulklen,1,stdout) == 0) { zfree(reply); return 1; @@ -169,6 +175,7 @@ static int cliReadBulkReply(int fd) { static int cliReadMultiBulkReply(int fd) { sds replylen = cliReadLine(fd); int elements, c = 1; + int retval = 0; if (replylen == NULL) return 1; elements = atoi(replylen); @@ -181,36 +188,45 @@ static int cliReadMultiBulkReply(int fd) { printf("(empty list or set)\n"); } while(elements--) { - printf("%d. ", c); - if (cliReadReply(fd)) return 1; + if (config.tty) printf("%d. ", c); + if (cliReadReply(fd)) retval = 1; + if (elements) printf("%c",config.mb_sep); c++; } - return 0; + return retval; } static int cliReadReply(int fd) { char type; + int nread; - if (anetRead(fd,&type,1) <= 0) { + if ((nread = anetRead(fd,&type,1)) <= 0) { if (config.shutdown) return 0; - exit(1); + if (config.interactive && + (nread == 0 || (nread == -1 && errno == ECONNRESET))) + { + return ECONNRESET; + } else { + printf("I/O error while reading from socket: %s",strerror(errno)); + exit(1); + } } switch(type) { case '-': - printf("(error) "); + if (config.tty) printf("(error) "); cliReadSingleLineReply(fd,0); return 1; case '+': return cliReadSingleLineReply(fd,0); case ':': - printf("(integer) "); + if (config.tty) printf("(integer) "); return cliReadSingleLineReply(fd,0); case '$': return cliReadBulkReply(fd); case '*': return cliReadMultiBulkReply(fd); default: - printf("protocol error, got '%c' as reply type byte\n", type); + printf("protocol error, got '%c' as reply type byte", type); return 1; } } @@ -245,7 +261,7 @@ static int cliSendCommand(int argc, char **argv, int repeat) { if (!strcasecmp(command,"monitor")) config.monitor_mode = 1; if (!strcasecmp(command,"subscribe") || !strcasecmp(command,"psubscribe")) config.pubsub_mode = 1; - if ((fd = cliConnect()) == -1) return 1; + if ((fd = cliConnect(0)) == -1) return 1; /* Select db number */ retval = selectDb(fd); @@ -273,14 +289,13 @@ static int cliSendCommand(int argc, char **argv, int repeat) { printf("Reading messages... (press Ctrl-c to quit)\n"); while (1) { cliReadReply(fd); - printf("\n"); + printf("\n\n"); } } retval = cliReadReply(fd); - if (retval) { - return retval; - } + if (!config.raw_output && config.tty) printf("\n"); + if (retval) return retval; } return 0; } @@ -314,9 +329,16 @@ static int parseOptions(int argc, char **argv) { config.auth = argv[i+1]; i++; } else if (!strcmp(argv[i],"-i")) { - config.interactive = 1; + fprintf(stderr, +"Starting interactive mode using -i is deprecated. Interactive mode is started\n" +"by default when redis-cli is executed without a command to execute.\n" + ); } else if (!strcmp(argv[i],"-c")) { - config.argn_from_stdin = 1; + fprintf(stderr, +"Reading last argument from standard input using -c is deprecated.\n" +"When standard input is connected to a pipe or regular file, it is\n" +"automatically used as last argument.\n" + ); } else if (!strcmp(argv[i],"-v")) { printf("redis-cli shipped with Redis verison %s\n", REDIS_VERSION); exit(0); @@ -346,7 +368,7 @@ static sds readArgFromStdin(void) { static void usage() { fprintf(stderr, "usage: redis-cli [-iv] [-h host] [-p port] [-a authpw] [-r repeat_times] [-n db_num] cmd arg1 arg2 arg3 ... argN\n"); - fprintf(stderr, "usage: echo \"argN\" | redis-cli -c [-h host] [-p port] [-a authpw] [-r repeat_times] [-n db_num] cmd arg1 arg2 ... arg(N-1)\n"); + fprintf(stderr, "usage: echo \"argN\" | redis-cli [-h host] [-p port] [-a authpw] [-r repeat_times] [-n db_num] cmd arg1 arg2 ... arg(N-1)\n"); fprintf(stderr, "\nIf a pipe from standard input is detected this data is used as last argument.\n\n"); fprintf(stderr, "example: cat /etc/passwd | redis-cli set my_passwd\n"); fprintf(stderr, "example: redis-cli get my_passwd\n"); @@ -372,17 +394,33 @@ static void repl() { char *line; sds *argv; + config.interactive = 1; while((line = linenoise("redis> ")) != NULL) { if (line[0] != '\0') { argv = sdssplitargs(line,&argc); linenoiseHistoryAdd(line); if (config.historyfile) linenoiseHistorySave(config.historyfile); - if (argc > 0) { + if (argv == NULL) { + printf("Invalid argument(s)\n"); + continue; + } else if (argc > 0) { if (strcasecmp(argv[0],"quit") == 0 || strcasecmp(argv[0],"exit") == 0) - exit(0); - else - cliSendCommand(argc, argv, 1); + { + exit(0); + } else { + int err; + + if ((err = cliSendCommand(argc, argv, 1)) != 0) { + if (err == ECONNRESET) { + printf("Reconnecting... "); + fflush(stdout); + if (cliConnect(1) == -1) exit(1); + printf("OK\n"); + cliSendCommand(argc,argv,1); + } + } + } } /* Free the argument vector */ for (j = 0; j < argc; j++) @@ -395,22 +433,37 @@ static void repl() { exit(0); } +static int noninteractive(int argc, char **argv) { + int retval = 0; + struct stat s; + fstat(fileno(stdin), &s); + if (S_ISFIFO(s.st_mode) || S_ISREG(s.st_mode)) { /* pipe, regular file */ + argv = zrealloc(argv, (argc+1)*sizeof(char*)); + argv[argc] = readArgFromStdin(); + retval = cliSendCommand(argc+1, argv, config.repeat); + } else { + /* stdin is probably a tty, can be tested with S_ISCHR(s.st_mode) */ + retval = cliSendCommand(argc, argv, config.repeat); + } + return retval; +} + int main(int argc, char **argv) { int firstarg; - char **argvcopy; config.hostip = "127.0.0.1"; config.hostport = 6379; config.repeat = 1; config.dbnum = 0; - config.argn_from_stdin = 0; - config.shutdown = 0; config.interactive = 0; + config.shutdown = 0; config.monitor_mode = 0; config.pubsub_mode = 0; config.raw_output = 0; config.auth = NULL; config.historyfile = NULL; + config.tty = isatty(fileno(stdout)) || (getenv("FAKETTY") != NULL); + config.mb_sep = '\n'; if (getenv("HOME") != NULL) { config.historyfile = malloc(256); @@ -430,13 +483,8 @@ int main(int argc, char **argv) { cliSendCommand(2, convertToSds(2, authargv), 1); } - if (argc == 0 || config.interactive == 1) repl(); - - argvcopy = convertToSds(argc+1, argv); - if (config.argn_from_stdin) { - sds lastarg = readArgFromStdin(); - argvcopy[argc] = lastarg; - argc++; - } - return cliSendCommand(argc, argvcopy, config.repeat); + /* Start interactive mode when no command is provided */ + if (argc == 0) repl(); + /* Otherwise, we have some arguments to execute */ + return noninteractive(argc,convertToSds(argc,argv)); } diff --git a/src/redis.c b/src/redis.c index 7b2ed42e..9fbd52f2 100644 --- a/src/redis.c +++ b/src/redis.c @@ -913,9 +913,14 @@ int processCommand(redisClient *c) { resetClient(c); return 1; } else { - int bulklen = atoi(((char*)c->argv[0]->ptr)+1); + char *eptr; + long bulklen = strtol(((char*)c->argv[0]->ptr)+1,&eptr,10); + int perr = eptr[0] != '\0'; + decrRefCount(c->argv[0]); - if (bulklen < 0 || bulklen > 1024*1024*1024) { + if (perr || bulklen == LONG_MIN || bulklen == LONG_MAX || + bulklen < 0 || bulklen > 1024*1024*1024) + { c->argc--; addReplySds(c,sdsnew("-ERR invalid bulk write count\r\n")); resetClient(c); @@ -985,10 +990,14 @@ int processCommand(redisClient *c) { return 1; } else if (cmd->flags & REDIS_CMD_BULK && c->bulklen == -1) { /* This is a bulk command, we have to read the last argument yet. */ - int bulklen = atoi(c->argv[c->argc-1]->ptr); + char *eptr; + long bulklen = strtol(c->argv[c->argc-1]->ptr,&eptr,10); + int perr = eptr[0] != '\0'; decrRefCount(c->argv[c->argc-1]); - if (bulklen < 0 || bulklen > 1024*1024*1024) { + if (perr || bulklen == LONG_MAX || bulklen == LONG_MIN || + bulklen < 0 || bulklen > 1024*1024*1024) + { c->argc--; addReplySds(c,sdsnew("-ERR invalid bulk write count\r\n")); resetClient(c); @@ -1077,11 +1086,7 @@ int prepareForShutdown() { if (server.vm_enabled) unlink(server.vm_swap_file); } else { /* Snapshotting. Perform a SYNC SAVE and exit */ - if (rdbSave(server.dbfilename) == REDIS_OK) { - if (server.daemonize) - unlink(server.pidfile); - redisLog(REDIS_WARNING,"%zu bytes used at exit",zmalloc_used_memory()); - } else { + if (rdbSave(server.dbfilename) != REDIS_OK) { /* Ooops.. error saving! The best we can do is to continue * operating. Note that if there was a background saving process, * in the next cron() Redis will be notified that the background @@ -1091,6 +1096,7 @@ int prepareForShutdown() { return REDIS_ERR; } } + if (server.daemonize) unlink(server.pidfile); redisLog(REDIS_WARNING,"Server exit now, bye bye..."); return REDIS_OK; } @@ -1363,9 +1369,17 @@ void linuxOvercommitMemoryWarning(void) { } #endif /* __linux__ */ +void createPidFile(void) { + /* Try to write the pid file in a best-effort way. */ + FILE *fp = fopen(server.pidfile,"w"); + if (fp) { + fprintf(fp,"%d\n",getpid()); + fclose(fp); + } +} + void daemonize(void) { int fd; - FILE *fp; if (fork() != 0) exit(0); /* parent exits */ setsid(); /* create a new session */ @@ -1379,12 +1393,6 @@ void daemonize(void) { dup2(fd, STDERR_FILENO); if (fd > STDERR_FILENO) close(fd); } - /* Try to write the pid file */ - fp = fopen(server.pidfile,"w"); - if (fp) { - fprintf(fp,"%d\n",getpid()); - fclose(fp); - } } void version() { @@ -1417,6 +1425,7 @@ int main(int argc, char **argv) { } if (server.daemonize) daemonize(); initServer(); + if (server.daemonize) createPidFile(); redisLog(REDIS_NOTICE,"Server started, Redis version " REDIS_VERSION); #ifdef __linux__ linuxOvercommitMemoryWarning(); @@ -1493,6 +1502,7 @@ void segvHandler(int sig, siginfo_t *info, void *secret) { redisLog(REDIS_WARNING,"%s", messages[i]); /* free(messages); Don't call free() with possibly corrupted memory. */ + if (server.daemonize) unlink(server.pidfile); _exit(0); } diff --git a/src/redis.h b/src/redis.h index 0b78320c..6156a6ca 100644 --- a/src/redis.h +++ b/src/redis.h @@ -286,7 +286,7 @@ typedef struct redisClient { sds querybuf; robj **argv, **mbargv; int argc, mbargc; - int bulklen; /* bulk read len. -1 if not in bulk read mode */ + long bulklen; /* bulk read len. -1 if not in bulk read mode */ int multibulk; /* multi bulk command format active */ list *reply; int sentlen; diff --git a/src/replication.c b/src/replication.c index 5387db91..363ce54a 100644 --- a/src/replication.c +++ b/src/replication.c @@ -176,6 +176,13 @@ void syncCommand(redisClient *c) { /* ignore SYNC if aleady slave or in monitor mode */ if (c->flags & REDIS_SLAVE) return; + /* Refuse SYNC requests if we are a slave but the link with our master + * is not ok... */ + if (server.masterhost && server.replstate != REDIS_REPL_CONNECTED) { + addReplySds(c,sdsnew("-ERR Can't SYNC while not connected with my master\r\n")); + return; + } + /* SYNC can't be issued when the server has pending data to send to * the client about already issued commands. We need a fresh reply * buffer registering the differences between the BGSAVE and the current @@ -392,7 +399,12 @@ int syncWithMaster(void) { strerror(errno)); return REDIS_ERR; } - if (buf[0] != '$') { + if (buf[0] == '-') { + close(fd); + redisLog(REDIS_WARNING,"MASTER aborted replication with an error: %s", + buf+1); + return REDIS_ERR; + } else if (buf[0] != '$') { close(fd); redisLog(REDIS_WARNING,"Bad protocol from MASTER, the first byte is not '$', are you sure the host and port are right?"); return REDIS_ERR; @@ -416,9 +428,9 @@ int syncWithMaster(void) { int nread, nwritten; nread = read(fd,buf,(dumpsize < 1024)?dumpsize:1024); - if (nread == -1) { + if (nread <= 0) { redisLog(REDIS_WARNING,"I/O error trying to sync with MASTER: %s", - strerror(errno)); + (nread == -1) ? strerror(errno) : "connection lost"); close(fd); close(dfd); return REDIS_ERR; diff --git a/src/sds.c b/src/sds.c index 4878f8a6..d7d23c45 100644 --- a/src/sds.c +++ b/src/sds.c @@ -407,7 +407,7 @@ sds *sdssplitargs(char *line, int *argc) { if (*p) { /* get a token */ int inq=0; /* set to 1 if we are in "quotes" */ - int done = 0; + int done=0; if (current == NULL) current = sdsempty(); while(!done) { @@ -426,7 +426,12 @@ sds *sdssplitargs(char *line, int *argc) { } current = sdscatlen(current,&c,1); } else if (*p == '"') { - done = 1; + /* closing quote must be followed by a space */ + if (*(p+1) && !isspace(*(p+1))) goto err; + done=1; + } else if (!*p) { + /* unterminated quotes */ + goto err; } else { current = sdscatlen(current,p,1); } @@ -458,4 +463,11 @@ sds *sdssplitargs(char *line, int *argc) { return vector; } } + +err: + while(*argc--) + sdsfree(vector[*argc]); + zfree(vector); + if (current) sdsfree(current); + return NULL; } diff --git a/tests/integration/redis-cli.tcl b/tests/integration/redis-cli.tcl new file mode 100644 index 00000000..40e4222e --- /dev/null +++ b/tests/integration/redis-cli.tcl @@ -0,0 +1,208 @@ +start_server {tags {"cli"}} { + proc open_cli {} { + set ::env(TERM) dumb + set fd [open [format "|src/redis-cli -p %d -n 9" [srv port]] "r+"] + fconfigure $fd -buffering none + fconfigure $fd -blocking false + fconfigure $fd -translation binary + assert_equal "redis> " [read_cli $fd] + set _ $fd + } + + proc close_cli {fd} { + close $fd + } + + proc read_cli {fd} { + set buf [read $fd] + while {[string length $buf] == 0} { + # wait some time and try again + after 10 + set buf [read $fd] + } + set _ $buf + } + + proc write_cli {fd buf} { + puts $fd $buf + flush $fd + } + + # Helpers to run tests in interactive mode + proc run_command {fd cmd} { + write_cli $fd $cmd + set lines [split [read_cli $fd] "\n"] + assert_equal "redis> " [lindex $lines end] + join [lrange $lines 0 end-1] "\n" + } + + proc test_interactive_cli {name code} { + set ::env(FAKETTY) 1 + set fd [open_cli] + test "Interactive CLI: $name" $code + close_cli $fd + unset ::env(FAKETTY) + } + + # Helpers to run tests where stdout is not a tty + proc write_tmpfile {contents} { + set tmp [tmpfile "cli"] + set tmpfd [open $tmp "w"] + puts -nonewline $tmpfd $contents + close $tmpfd + set _ $tmp + } + + proc _run_cli {opts args} { + set cmd [format "src/redis-cli -p %d -n 9 $args" [srv port]] + foreach {key value} $opts { + if {$key eq "pipe"} { + set cmd "sh -c \"$value | $cmd\"" + } + if {$key eq "path"} { + set cmd "$cmd < $value" + } + } + + set fd [open "|$cmd" "r"] + fconfigure $fd -buffering none + fconfigure $fd -translation binary + set resp [read $fd 1048576] + close $fd + set _ $resp + } + + proc run_cli {args} { + _run_cli {} {*}$args + } + + proc run_cli_with_input_pipe {cmd args} { + _run_cli [list pipe $cmd] {*}$args + } + + proc run_cli_with_input_file {path args} { + _run_cli [list path $path] {*}$args + } + + proc test_nontty_cli {name code} { + test "Non-interactive non-TTY CLI: $name" $code + } + + # Helpers to run tests where stdout is a tty (fake it) + proc test_tty_cli {name code} { + set ::env(FAKETTY) 1 + test "Non-interactive TTY CLI: $name" $code + unset ::env(FAKETTY) + } + + test_interactive_cli "INFO response should be printed raw" { + set lines [split [run_command $fd info] "\n"] + foreach line $lines { + assert [regexp {^[a-z0-9_]+:[a-z0-9_]+} $line] + } + } + + test_interactive_cli "Status reply" { + assert_equal "OK" [run_command $fd "set key foo"] + } + + test_interactive_cli "Integer reply" { + assert_equal "(integer) 1" [run_command $fd "incr counter"] + } + + test_interactive_cli "Bulk reply" { + r set key foo + assert_equal "\"foo\"" [run_command $fd "get key"] + } + + test_interactive_cli "Multi-bulk reply" { + r rpush list foo + r rpush list bar + assert_equal "1. \"foo\"\n2. \"bar\"" [run_command $fd "lrange list 0 -1"] + } + + test_interactive_cli "Parsing quotes" { + assert_equal "OK" [run_command $fd "set key \"bar\""] + assert_equal "bar" [r get key] + assert_equal "OK" [run_command $fd "set key \" bar \""] + assert_equal " bar " [r get key] + assert_equal "OK" [run_command $fd "set key \"\\\"bar\\\"\""] + assert_equal "\"bar\"" [r get key] + assert_equal "OK" [run_command $fd "set key \"\tbar\t\""] + assert_equal "\tbar\t" [r get key] + + # invalid quotation + assert_equal "Invalid argument(s)" [run_command $fd "get \"\"key"] + assert_equal "Invalid argument(s)" [run_command $fd "get \"key\"x"] + + # quotes after the argument are weird, but should be allowed + assert_equal "OK" [run_command $fd "set key\"\" bar"] + assert_equal "bar" [r get key] + } + + test_tty_cli "Status reply" { + assert_equal "OK\n" [run_cli set key bar] + assert_equal "bar" [r get key] + } + + test_tty_cli "Integer reply" { + r del counter + assert_equal "(integer) 1\n" [run_cli incr counter] + } + + test_tty_cli "Bulk reply" { + r set key "tab\tnewline\n" + assert_equal "\"tab\\tnewline\\n\"\n" [run_cli get key] + } + + test_tty_cli "Multi-bulk reply" { + r del list + r rpush list foo + r rpush list bar + assert_equal "1. \"foo\"\n2. \"bar\"\n" [run_cli lrange list 0 -1] + } + + test_tty_cli "Read last argument from pipe" { + assert_equal "OK\n" [run_cli_with_input_pipe "echo foo" set key] + assert_equal "foo\n" [r get key] + } + + test_tty_cli "Read last argument from file" { + set tmpfile [write_tmpfile "from file"] + assert_equal "OK\n" [run_cli_with_input_file $tmpfile set key] + assert_equal "from file" [r get key] + } + + test_nontty_cli "Status reply" { + assert_equal "OK" [run_cli set key bar] + assert_equal "bar" [r get key] + } + + test_nontty_cli "Integer reply" { + r del counter + assert_equal "1" [run_cli incr counter] + } + + test_nontty_cli "Bulk reply" { + r set key "tab\tnewline\n" + assert_equal "tab\tnewline\n" [run_cli get key] + } + + test_nontty_cli "Multi-bulk reply" { + r del list + r rpush list foo + r rpush list bar + assert_equal "foo\nbar" [run_cli lrange list 0 -1] + } + + test_nontty_cli "Read last argument from pipe" { + assert_equal "OK" [run_cli_with_input_pipe "echo foo" set key] + assert_equal "foo\n" [r get key] + } + + test_nontty_cli "Read last argument from file" { + set tmpfile [write_tmpfile "from file"] + assert_equal "OK" [run_cli_with_input_file $tmpfile set key] + assert_equal "from file" [r get key] + } +} diff --git a/tests/test_helper.tcl b/tests/test_helper.tcl index ef1f9923..4ae9cc65 100644 --- a/tests/test_helper.tcl +++ b/tests/test_helper.tcl @@ -25,7 +25,14 @@ proc execute_tests name { # are nested, use "srv 0 pid" to get the pid of the inner server. To access # outer servers, use "srv -1 pid" etcetera. set ::servers {} -proc srv {level property} { +proc srv {args} { + set level 0 + if {[string is integer [lindex $args 0]]} { + set level [lindex $args 0] + set property [lindex $args 1] + } else { + set property [lindex $args 0] + } set srv [lindex $::servers end+$level] dict get $srv $property } @@ -88,6 +95,7 @@ proc main {} { execute_tests "unit/cas" execute_tests "integration/replication" execute_tests "integration/aof" + execute_tests "integration/redis-cli" execute_tests "unit/pubsub" # run tests with VM enabled diff --git a/tests/unit/protocol.tcl b/tests/unit/protocol.tcl index 8717cd9f..9eebf77f 100644 --- a/tests/unit/protocol.tcl +++ b/tests/unit/protocol.tcl @@ -27,6 +27,13 @@ start_server {} { gets $fd } {*invalid bulk*count*} + test {bulk payload is not a number} { + set fd [r channel] + puts -nonewline $fd "SET x blabla\r\n" + flush $fd + gets $fd + } {*invalid bulk*count*} + test {Multi bulk request not followed by bulk args} { set fd [r channel] puts -nonewline $fd "*1\r\nfoo\r\n" diff --git a/utils/redis-copy.rb b/utils/redis-copy.rb index af214b79..d892e377 100644 --- a/utils/redis-copy.rb +++ b/utils/redis-copy.rb @@ -1,12 +1,10 @@ -# redis-sha1.rb - Copyright (C) 2009 Salvatore Sanfilippo +# redis-copy.rb - Copyright (C) 2009-2010 Salvatore Sanfilippo # BSD license, See the COPYING file for more information. # -# Performs the SHA1 sum of the whole datset. -# This is useful to spot bugs in persistence related code and to make sure -# Slaves and Masters are in SYNC. +# Copy the whole dataset from one Redis instance to another one # -# If you hack this code make sure to sort keys and set elements as this are -# unsorted elements. Otherwise the sum may differ with equal dataset. +# WARNING: currently hashes and sorted sets are not supported! This +# program should be updated. require 'rubygems' require 'redis'