From: Pieter Noordhuis Date: Wed, 25 Aug 2010 07:54:02 +0000 (+0200) Subject: Merge master and move argument splitting patch to sds.c X-Git-Url: https://git.saurik.com/redis.git/commitdiff_plain/4b93e5e2676e1dc45de1c118c03042de1ce9f024?hp=c0b3d42372dbe67c6ef096372869e2b60d4a1cdc Merge master and move argument splitting patch to sds.c --- diff --git a/src/redis-cli.c b/src/redis-cli.c index a2a909ba..1bd0798b 100644 --- a/src/redis-cli.c +++ b/src/redis-cli.c @@ -56,11 +56,12 @@ static struct config { 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; @@ -111,7 +112,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; } @@ -138,7 +139,7 @@ static void printStringRepr(char *s, int len) { } s++; } - printf("\"\n"); + printf("\""); } static int cliReadBulkReply(int fd) { @@ -156,7 +157,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; @@ -186,8 +187,9 @@ static int cliReadMultiBulkReply(int fd) { printf("(empty list or set)\n"); } while(elements--) { - printf("%d. ", c); + if (config.tty) printf("%d. ", c); if (cliReadReply(fd)) retval = 1; + if (elements) printf("%c",config.mb_sep); c++; } return retval; @@ -210,13 +212,13 @@ static int cliReadReply(int fd) { } 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); @@ -286,7 +288,7 @@ 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"); } } @@ -294,6 +296,9 @@ static int cliSendCommand(int argc, char **argv, int repeat) { if (retval) { return retval; } + if (!config.raw_output && config.tty) { + printf("\n"); + } } return 0; } @@ -327,7 +332,10 @@ 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; } else if (!strcmp(argv[i],"-v")) { @@ -390,7 +398,10 @@ static void repl() { 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) { @@ -430,12 +441,13 @@ int main(int argc, char **argv) { config.dbnum = 0; config.argn_from_stdin = 0; config.shutdown = 0; - config.interactive = 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); @@ -455,8 +467,8 @@ int main(int argc, char **argv) { cliSendCommand(2, convertToSds(2, authargv), 1); } - if (argc == 0) config.interactive = 1; - if (config.interactive) repl(); + /* Start interactive mode when no command is provided */ + if (argc == 0) repl(); argvcopy = convertToSds(argc+1, argv); if (config.argn_from_stdin) { 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..c4954304 --- /dev/null +++ b/tests/integration/redis-cli.tcl @@ -0,0 +1,156 @@ +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 + } + + 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 fd [open_cli] + test "Interactive CLI: $name" $code + close_cli $fd + } + + proc run_nontty_cli {args} { + set fd [open [format "|src/redis-cli -p %d -n 9 $args" [srv port]] "r"] + fconfigure $fd -buffering none + fconfigure $fd -translation binary + set resp [read $fd 1048576] + close $fd + set _ $resp + } + + proc test_nontty_cli {name code} { + test "Non-interactive non-TTY CLI: $name" $code + } + + proc run_tty_cli {args} { + set ::env(FAKETTY) 1 + set resp [run_nontty_cli {*}$args] + unset ::env(FAKETTY) + set _ $resp + } + + proc test_tty_cli {name code} { + test "Non-interactive TTY CLI: $name" $code + } + + 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_tty_cli set key bar] + assert_equal "bar" [r get key] + } + + test_tty_cli "Integer reply" { + r del counter + assert_equal "(integer) 1\n" [run_tty_cli incr counter] + } + + test_tty_cli "Bulk reply" { + r set key "tab\tnewline\n" + assert_equal "\"tab\\tnewline\\n\"\n" [run_tty_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_tty_cli lrange list 0 -1] + } + + test_nontty_cli "Status reply" { + assert_equal "OK" [run_nontty_cli set key bar] + assert_equal "bar" [r get key] + } + + test_nontty_cli "Integer reply" { + r del counter + assert_equal "1" [run_nontty_cli incr counter] + } + + test_nontty_cli "Bulk reply" { + r set key "tab\tnewline\n" + assert_equal "tab\tnewline\n" [run_nontty_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_nontty_cli lrange list 0 -1] + } +} 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