]> git.saurik.com Git - redis.git/commitdiff
Merge remote branch 'pietern/intset-split'
authorantirez <antirez@gmail.com>
Thu, 26 Aug 2010 10:04:24 +0000 (12:04 +0200)
committerantirez <antirez@gmail.com>
Thu, 26 Aug 2010 10:04:24 +0000 (12:04 +0200)
12 files changed:
src/db.c
src/networking.c
src/object.c
src/redis-cli.c
src/redis.c
src/redis.h
src/replication.c
src/sds.c
tests/integration/redis-cli.tcl [new file with mode: 0644]
tests/test_helper.tcl
tests/unit/protocol.tcl
utils/redis-copy.rb

index 0dec95b1c5763a8ecea00b2a469cebf14ce5ffb4..6d287d72c914762eef4ec17eb08a2f324df71e58 100644 (file)
--- 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) {
index e5a66984662e622a84f2cce9c9be48d1de4e9b7d..a39be7c4ea8b81ead00a145cdc7a2dcec516d9c7 100644 (file)
@@ -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))
index b16123eba641ba3307fbaacb9320cf8a80f3026b..dae7f97b21924fdda844e4e07a1b276cba0d7350 100644 (file)
@@ -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;
     }
index b4a108904962578f7f6c40decb971feec223c858..8b7d0777dc5f7b4f4c7f8a9819d3d53f632dbf29 100644 (file)
@@ -36,6 +36,8 @@
 #include <stdlib.h>
 #include <unistd.h>
 #include <ctype.h>
+#include <errno.h>
+#include <sys/stat.h>
 
 #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));
 }
index 7b2ed42e670c4733b55e0698582186a3c044bf3a..9fbd52f2e19ca1b557fc74912449df67a1ffcf7a 100644 (file)
@@ -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);
 }
 
index 0b78320c117db7ad0d304afcfb075ff26b650a86..6156a6ca74187dbb1ee9460f4cf941594e0c48f9 100644 (file)
@@ -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;
index 5387db913cc39719bc820a0d066e0c326fc6fca5..363ce54ac802db07452987d441dc02248ef8aba8 100644 (file)
@@ -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;
index 4878f8a625714ce4190555953954f5e24fbcd2af..d7d23c45a859a7aba07981637d7e25def7df8bf1 100644 (file)
--- 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 (file)
index 0000000..40e4222
--- /dev/null
@@ -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]
+    }
+}
index ef1f99233434fe9682ff35ad217a5aaded9a2d51..4ae9cc65967f30b83af2cc70193960b7a06e1eec 100644 (file)
@@ -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
index 8717cd9ffa02914a3f82c7dd8858d3e9bec9efee..9eebf77fdf5d96e3e87cb169ce991c4ec243da96 100644 (file)
@@ -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"
index af214b7980630303b7eceab874103f2a88a8e4ea..d892e377f38aacd5894229026e762236516ba9c9 100644 (file)
@@ -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'