1
2
3
4
5 package term
6
7 import (
8 "bytes"
9 "io"
10 "os"
11 "runtime"
12 "testing"
13 )
14
15 type MockTerminal struct {
16 toSend []byte
17 bytesPerRead int
18 received []byte
19 }
20
21 func (c *MockTerminal) Read(data []byte) (n int, err error) {
22 n = len(data)
23 if n == 0 {
24 return
25 }
26 if n > len(c.toSend) {
27 n = len(c.toSend)
28 }
29 if n == 0 {
30 return 0, io.EOF
31 }
32 if c.bytesPerRead > 0 && n > c.bytesPerRead {
33 n = c.bytesPerRead
34 }
35 copy(data, c.toSend[:n])
36 c.toSend = c.toSend[n:]
37 return
38 }
39
40 func (c *MockTerminal) Write(data []byte) (n int, err error) {
41 c.received = append(c.received, data...)
42 return len(data), nil
43 }
44
45 func TestClose(t *testing.T) {
46 c := &MockTerminal{}
47 ss := NewTerminal(c, "> ")
48 line, err := ss.ReadLine()
49 if line != "" {
50 t.Errorf("Expected empty line but got: %s", line)
51 }
52 if err != io.EOF {
53 t.Errorf("Error should have been EOF but got: %s", err)
54 }
55 }
56
57 var keyPressTests = []struct {
58 in string
59 line string
60 err error
61 throwAwayLines int
62 }{
63 {
64 err: io.EOF,
65 },
66 {
67 in: "\r",
68 line: "",
69 },
70 {
71 in: "foo\r",
72 line: "foo",
73 },
74 {
75 in: "a\x1b[Cb\r",
76 line: "ab",
77 },
78 {
79 in: "a\x1b[Db\r",
80 line: "ba",
81 },
82 {
83 in: "a\006b\r",
84 line: "ab",
85 },
86 {
87 in: "a\002b\r",
88 line: "ba",
89 },
90 {
91 in: "a\177b\r",
92 line: "b",
93 },
94 {
95 in: "\x1b[A\r",
96 },
97 {
98 in: "\x1b[B\r",
99 },
100 {
101 in: "\016\r",
102 },
103 {
104 in: "\014\r",
105 },
106 {
107 in: "line\x1b[A\x1b[B\r",
108 line: "line",
109 },
110 {
111 in: "line1\rline2\x1b[A\r",
112 line: "line1",
113 throwAwayLines: 1,
114 },
115 {
116
117 in: "line1\rline2\rline3\x1b[A\x1b[Axxx\r",
118 line: "line1xxx",
119 throwAwayLines: 2,
120 },
121 {
122
123
124 in: "a b \001\013\r",
125 line: "",
126 },
127 {
128
129
130 in: "a b \001\005\013\r",
131 line: "a b ",
132 },
133 {
134 in: "\027\r",
135 line: "",
136 },
137 {
138 in: "a\027\r",
139 line: "",
140 },
141 {
142 in: "a \027\r",
143 line: "",
144 },
145 {
146 in: "a b\027\r",
147 line: "a ",
148 },
149 {
150 in: "a b \027\r",
151 line: "a ",
152 },
153 {
154 in: "one two thr\x1b[D\027\r",
155 line: "one two r",
156 },
157 {
158 in: "\013\r",
159 line: "",
160 },
161 {
162 in: "a\013\r",
163 line: "a",
164 },
165 {
166 in: "ab\x1b[D\013\r",
167 line: "a",
168 },
169 {
170 in: "Ξεσκεπάζω\r",
171 line: "Ξεσκεπάζω",
172 },
173 {
174 in: "£\r\x1b[A\177\r",
175 line: "",
176 throwAwayLines: 1,
177 },
178 {
179 in: "£\r££\x1b[A\x1b[B\177\r",
180 line: "£",
181 throwAwayLines: 1,
182 },
183 {
184
185 in: "a\004\r",
186 line: "a",
187 },
188 {
189
190 in: "ab\x1b[D\004\r",
191 line: "a",
192 },
193 {
194
195
196 in: "abcd\x1b[D\x1b[D\025\r",
197 line: "cd",
198 },
199 {
200
201
202 in: "abc\x1b[200~de\177f\x1b[201~\177\r",
203 line: "abcde\177",
204 },
205 {
206
207 in: "abc\x1b[200~d\refg\x1b[201~h\r",
208 line: "efgh",
209 throwAwayLines: 1,
210 },
211 {
212
213 in: "\x1b[200~a\r",
214 line: "a",
215 err: ErrPasteIndicator,
216 },
217 {
218
219 in: "\003",
220 err: io.EOF,
221 },
222 {
223
224 in: "a\003\r",
225 err: io.EOF,
226 },
227 }
228
229 func TestKeyPresses(t *testing.T) {
230 for i, test := range keyPressTests {
231 for j := 1; j < len(test.in); j++ {
232 c := &MockTerminal{
233 toSend: []byte(test.in),
234 bytesPerRead: j,
235 }
236 ss := NewTerminal(c, "> ")
237 for k := 0; k < test.throwAwayLines; k++ {
238 _, err := ss.ReadLine()
239 if err != nil {
240 t.Errorf("Throwaway line %d from test %d resulted in error: %s", k, i, err)
241 }
242 }
243 line, err := ss.ReadLine()
244 if line != test.line {
245 t.Errorf("Line resulting from test %d (%d bytes per read) was '%s', expected '%s'", i, j, line, test.line)
246 break
247 }
248 if err != test.err {
249 t.Errorf("Error resulting from test %d (%d bytes per read) was '%v', expected '%v'", i, j, err, test.err)
250 break
251 }
252 }
253 }
254 }
255
256 var renderTests = []struct {
257 in string
258 received string
259 err error
260 }{
261 {
262
263 in: "abcd\x1b[H\r",
264 received: "> abcd\x1b[4D\x1b[4C\r\n",
265 },
266 {
267
268 in: "cdef\x1b[Hab\r",
269 received: "> cdef" +
270 "\x1b[4Da" +
271 "cdef" +
272 "\x1b[4Dbcdef" +
273 "\x1b[4D" +
274 "\x1b[4C\r\n",
275 },
276 }
277
278 func TestRender(t *testing.T) {
279 for i, test := range renderTests {
280 for j := 1; j < len(test.in); j++ {
281 c := &MockTerminal{
282 toSend: []byte(test.in),
283 bytesPerRead: j,
284 }
285 ss := NewTerminal(c, "> ")
286 _, err := ss.ReadLine()
287 if err != test.err {
288 t.Errorf("Error resulting from test %d (%d bytes per read) was '%v', expected '%v'", i, j, err, test.err)
289 break
290 }
291 if test.received != string(c.received) {
292 t.Errorf("Results rendered from test %d (%d bytes per read) was '%s', expected '%s'", i, j, c.received, test.received)
293 break
294 }
295 }
296 }
297 }
298
299 func TestPasswordNotSaved(t *testing.T) {
300 c := &MockTerminal{
301 toSend: []byte("password\r\x1b[A\r"),
302 bytesPerRead: 1,
303 }
304 ss := NewTerminal(c, "> ")
305 pw, _ := ss.ReadPassword("> ")
306 if pw != "password" {
307 t.Fatalf("failed to read password, got %s", pw)
308 }
309 line, _ := ss.ReadLine()
310 if len(line) > 0 {
311 t.Fatalf("password was saved in history")
312 }
313 }
314
315 var setSizeTests = []struct {
316 width, height int
317 }{
318 {40, 13},
319 {80, 24},
320 {132, 43},
321 }
322
323 func TestTerminalSetSize(t *testing.T) {
324 for _, setSize := range setSizeTests {
325 c := &MockTerminal{
326 toSend: []byte("password\r\x1b[A\r"),
327 bytesPerRead: 1,
328 }
329 ss := NewTerminal(c, "> ")
330 ss.SetSize(setSize.width, setSize.height)
331 pw, _ := ss.ReadPassword("Password: ")
332 if pw != "password" {
333 t.Fatalf("failed to read password, got %s", pw)
334 }
335 if string(c.received) != "Password: \r\n" {
336 t.Errorf("failed to set the temporary prompt expected %q, got %q", "Password: ", c.received)
337 }
338 }
339 }
340
341 func TestReadPasswordLineEnd(t *testing.T) {
342 type testType struct {
343 input string
344 want string
345 }
346 var tests = []testType{
347 {"\r\n", ""},
348 {"test\r\n", "test"},
349 {"test\r", "test"},
350 {"test\n", "test"},
351 {"testtesttesttes\n", "testtesttesttes"},
352 {"testtesttesttes\r\n", "testtesttesttes"},
353 {"testtesttesttesttest\n", "testtesttesttesttest"},
354 {"testtesttesttesttest\r\n", "testtesttesttesttest"},
355 {"\btest", "test"},
356 {"t\best", "est"},
357 {"te\bst", "tst"},
358 {"test\b", "tes"},
359 {"test\b\r\n", "tes"},
360 {"test\b\n", "tes"},
361 {"test\b\r", "tes"},
362 }
363 eol := "\n"
364 if runtime.GOOS == "windows" {
365 eol = "\r"
366 }
367 tests = append(tests, testType{eol, ""})
368 for _, test := range tests {
369 buf := new(bytes.Buffer)
370 if _, err := buf.WriteString(test.input); err != nil {
371 t.Fatal(err)
372 }
373
374 have, err := readPasswordLine(buf)
375 if err != nil {
376 t.Errorf("readPasswordLine(%q) failed: %v", test.input, err)
377 continue
378 }
379 if string(have) != test.want {
380 t.Errorf("readPasswordLine(%q) returns %q, but %q is expected", test.input, string(have), test.want)
381 continue
382 }
383
384 if _, err = buf.WriteString(test.input); err != nil {
385 t.Fatal(err)
386 }
387 have, err = readPasswordLine(buf)
388 if err != nil {
389 t.Errorf("readPasswordLine(%q) failed: %v", test.input, err)
390 continue
391 }
392 if string(have) != test.want {
393 t.Errorf("readPasswordLine(%q) returns %q, but %q is expected", test.input, string(have), test.want)
394 continue
395 }
396 }
397 }
398
399 func TestMakeRawState(t *testing.T) {
400 fd := int(os.Stdout.Fd())
401 if !IsTerminal(fd) {
402 t.Skip("stdout is not a terminal; skipping test")
403 }
404
405 st, err := GetState(fd)
406 if err != nil {
407 t.Fatalf("failed to get terminal state from GetState: %s", err)
408 }
409
410 if runtime.GOOS == "ios" {
411 t.Skip("MakeRaw not allowed on iOS; skipping test")
412 }
413
414 defer Restore(fd, st)
415 raw, err := MakeRaw(fd)
416 if err != nil {
417 t.Fatalf("failed to get terminal state from MakeRaw: %s", err)
418 }
419
420 if *st != *raw {
421 t.Errorf("states do not match; was %v, expected %v", raw, st)
422 }
423 }
424
425 func TestOutputNewlines(t *testing.T) {
426
427 buf := new(bytes.Buffer)
428 term := NewTerminal(buf, ">")
429
430 term.Write([]byte("1\n2\n"))
431 output := buf.String()
432 const expected = "1\r\n2\r\n"
433
434 if output != expected {
435 t.Errorf("incorrect output: was %q, expected %q", output, expected)
436 }
437 }
438
View as plain text