1start_server {tags {"cli"}} {
2    proc open_cli {} {
3        set ::env(TERM) dumb
4        set fd [open [format "|src/redis-cli -p %d -n 9" [srv port]] "r+"]
5        fconfigure $fd -buffering none
6        fconfigure $fd -blocking false
7        fconfigure $fd -translation binary
8        assert_equal "redis> " [read_cli $fd]
9        set _ $fd
10    }
11
12    proc close_cli {fd} {
13        close $fd
14    }
15
16    proc read_cli {fd} {
17        set buf [read $fd]
18        while {[string length $buf] == 0} {
19            # wait some time and try again
20            after 10
21            set buf [read $fd]
22        }
23        set _ $buf
24    }
25
26    proc write_cli {fd buf} {
27        puts $fd $buf
28        flush $fd
29    }
30
31    # Helpers to run tests in interactive mode
32    proc run_command {fd cmd} {
33        write_cli $fd $cmd
34        set lines [split [read_cli $fd] "\n"]
35        assert_equal "redis> " [lindex $lines end]
36        join [lrange $lines 0 end-1] "\n"
37    }
38
39    proc test_interactive_cli {name code} {
40        set ::env(FAKETTY) 1
41        set fd [open_cli]
42        test "Interactive CLI: $name" $code
43        close_cli $fd
44        unset ::env(FAKETTY)
45    }
46
47    # Helpers to run tests where stdout is not a tty
48    proc write_tmpfile {contents} {
49        set tmp [tmpfile "cli"]
50        set tmpfd [open $tmp "w"]
51        puts -nonewline $tmpfd $contents
52        close $tmpfd
53        set _ $tmp
54    }
55
56    proc _run_cli {opts args} {
57        set cmd [format "src/redis-cli -p %d -n 9 $args" [srv port]]
58        foreach {key value} $opts {
59            if {$key eq "pipe"} {
60                set cmd "sh -c \"$value | $cmd\""
61            }
62            if {$key eq "path"} {
63                set cmd "$cmd < $value"
64            }
65        }
66
67        set fd [open "|$cmd" "r"]
68        fconfigure $fd -buffering none
69        fconfigure $fd -translation binary
70        set resp [read $fd 1048576]
71        close $fd
72        set _ $resp
73    }
74
75    proc run_cli {args} {
76        _run_cli {} {*}$args
77    }
78
79    proc run_cli_with_input_pipe {cmd args} {
80        _run_cli [list pipe $cmd] {*}$args
81    }
82
83    proc run_cli_with_input_file {path args} {
84        _run_cli [list path $path] {*}$args
85    }
86
87    proc test_nontty_cli {name code} {
88        test "Non-interactive non-TTY CLI: $name" $code
89    }
90
91    # Helpers to run tests where stdout is a tty (fake it)
92    proc test_tty_cli {name code} {
93        set ::env(FAKETTY) 1
94        test "Non-interactive TTY CLI: $name" $code
95        unset ::env(FAKETTY)
96    }
97
98    test_interactive_cli "INFO response should be printed raw" {
99        set lines [split [run_command $fd info] "\n"]
100        foreach line $lines {
101            assert [regexp {^[a-z0-9_]+:[a-z0-9_]+} $line]
102        }
103    }
104
105    test_interactive_cli "Status reply" {
106        assert_equal "OK" [run_command $fd "set key foo"]
107    }
108
109    test_interactive_cli "Integer reply" {
110        assert_equal "(integer) 1" [run_command $fd "incr counter"]
111    }
112
113    test_interactive_cli "Bulk reply" {
114        r set key foo
115        assert_equal "\"foo\"" [run_command $fd "get key"]
116    }
117
118    test_interactive_cli "Multi-bulk reply" {
119        r rpush list foo
120        r rpush list bar
121        assert_equal "1. \"foo\"\n2. \"bar\"" [run_command $fd "lrange list 0 -1"]
122    }
123
124    test_interactive_cli "Parsing quotes" {
125        assert_equal "OK" [run_command $fd "set key \"bar\""]
126        assert_equal "bar" [r get key]
127        assert_equal "OK" [run_command $fd "set key \" bar \""]
128        assert_equal " bar " [r get key]
129        assert_equal "OK" [run_command $fd "set key \"\\\"bar\\\"\""]
130        assert_equal "\"bar\"" [r get key]
131        assert_equal "OK" [run_command $fd "set key \"\tbar\t\""]
132        assert_equal "\tbar\t" [r get key]
133
134        # invalid quotation
135        assert_equal "Invalid argument(s)" [run_command $fd "get \"\"key"]
136        assert_equal "Invalid argument(s)" [run_command $fd "get \"key\"x"]
137
138        # quotes after the argument are weird, but should be allowed
139        assert_equal "OK" [run_command $fd "set key\"\" bar"]
140        assert_equal "bar" [r get key]
141    }
142
143    test_tty_cli "Status reply" {
144        assert_equal "OK\n" [run_cli set key bar]
145        assert_equal "bar" [r get key]
146    }
147
148    test_tty_cli "Integer reply" {
149        r del counter
150        assert_equal "(integer) 1\n" [run_cli incr counter]
151    }
152
153    test_tty_cli "Bulk reply" {
154        r set key "tab\tnewline\n"
155        assert_equal "\"tab\\tnewline\\n\"\n" [run_cli get key]
156    }
157
158    test_tty_cli "Multi-bulk reply" {
159        r del list
160        r rpush list foo
161        r rpush list bar
162        assert_equal "1. \"foo\"\n2. \"bar\"\n" [run_cli lrange list 0 -1]
163    }
164
165    test_tty_cli "Read last argument from pipe" {
166        assert_equal "OK\n" [run_cli_with_input_pipe "echo foo" set key]
167        assert_equal "foo\n" [r get key]
168    }
169
170    test_tty_cli "Read last argument from file" {
171        set tmpfile [write_tmpfile "from file"]
172        assert_equal "OK\n" [run_cli_with_input_file $tmpfile set key]
173        assert_equal "from file" [r get key]
174    }
175
176    test_nontty_cli "Status reply" {
177        assert_equal "OK" [run_cli set key bar]
178        assert_equal "bar" [r get key]
179    }
180
181    test_nontty_cli "Integer reply" {
182        r del counter
183        assert_equal "1" [run_cli incr counter]
184    }
185
186    test_nontty_cli "Bulk reply" {
187        r set key "tab\tnewline\n"
188        assert_equal "tab\tnewline\n" [run_cli get key]
189    }
190
191    test_nontty_cli "Multi-bulk reply" {
192        r del list
193        r rpush list foo
194        r rpush list bar
195        assert_equal "foo\nbar" [run_cli lrange list 0 -1]
196    }
197
198    test_nontty_cli "Read last argument from pipe" {
199        assert_equal "OK" [run_cli_with_input_pipe "echo foo" set key]
200        assert_equal "foo\n" [r get key]
201    }
202
203    test_nontty_cli "Read last argument from file" {
204        set tmpfile [write_tmpfile "from file"]
205        assert_equal "OK" [run_cli_with_input_file $tmpfile set key]
206        assert_equal "from file" [r get key]
207    }
208}
209