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;
if (reply == NULL) return 1;
if (!quiet)
- printf("%s\n", reply);
+ printf("%s", reply);
sdsfree(reply);
return 0;
}
}
s++;
}
- printf("\"\n");
+ printf("\"");
}
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;
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;
}
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);
printf("Reading messages... (press Ctrl-c to quit)\n");
while (1) {
cliReadReply(fd);
- printf("\n");
+ printf("\n\n");
}
}
if (retval) {
return retval;
}
+ if (!config.raw_output && config.tty) {
+ printf("\n");
+ }
}
return 0;
}
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")) {
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)
{
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);
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) {
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) {
}
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);
}
return vector;
}
}
+
+err:
+ while(*argc--)
+ sdsfree(vector[*argc]);
+ zfree(vector);
+ if (current) sdsfree(current);
+ return NULL;
}
--- /dev/null
+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]
+ }
+}
# 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
}
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