You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

359 lines
7.4 KiB

  1. // Copyright 2011 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. // +build darwin dragonfly freebsd linux,!appengine netbsd openbsd windows plan9 solaris
  5. package terminal
  6. import (
  7. "bytes"
  8. "io"
  9. "os"
  10. "runtime"
  11. "testing"
  12. )
  13. type MockTerminal struct {
  14. toSend []byte
  15. bytesPerRead int
  16. received []byte
  17. }
  18. func (c *MockTerminal) Read(data []byte) (n int, err error) {
  19. n = len(data)
  20. if n == 0 {
  21. return
  22. }
  23. if n > len(c.toSend) {
  24. n = len(c.toSend)
  25. }
  26. if n == 0 {
  27. return 0, io.EOF
  28. }
  29. if c.bytesPerRead > 0 && n > c.bytesPerRead {
  30. n = c.bytesPerRead
  31. }
  32. copy(data, c.toSend[:n])
  33. c.toSend = c.toSend[n:]
  34. return
  35. }
  36. func (c *MockTerminal) Write(data []byte) (n int, err error) {
  37. c.received = append(c.received, data...)
  38. return len(data), nil
  39. }
  40. func TestClose(t *testing.T) {
  41. c := &MockTerminal{}
  42. ss := NewTerminal(c, "> ")
  43. line, err := ss.ReadLine()
  44. if line != "" {
  45. t.Errorf("Expected empty line but got: %s", line)
  46. }
  47. if err != io.EOF {
  48. t.Errorf("Error should have been EOF but got: %s", err)
  49. }
  50. }
  51. var keyPressTests = []struct {
  52. in string
  53. line string
  54. err error
  55. throwAwayLines int
  56. }{
  57. {
  58. err: io.EOF,
  59. },
  60. {
  61. in: "\r",
  62. line: "",
  63. },
  64. {
  65. in: "foo\r",
  66. line: "foo",
  67. },
  68. {
  69. in: "a\x1b[Cb\r", // right
  70. line: "ab",
  71. },
  72. {
  73. in: "a\x1b[Db\r", // left
  74. line: "ba",
  75. },
  76. {
  77. in: "a\177b\r", // backspace
  78. line: "b",
  79. },
  80. {
  81. in: "\x1b[A\r", // up
  82. },
  83. {
  84. in: "\x1b[B\r", // down
  85. },
  86. {
  87. in: "line\x1b[A\x1b[B\r", // up then down
  88. line: "line",
  89. },
  90. {
  91. in: "line1\rline2\x1b[A\r", // recall previous line.
  92. line: "line1",
  93. throwAwayLines: 1,
  94. },
  95. {
  96. // recall two previous lines and append.
  97. in: "line1\rline2\rline3\x1b[A\x1b[Axxx\r",
  98. line: "line1xxx",
  99. throwAwayLines: 2,
  100. },
  101. {
  102. // Ctrl-A to move to beginning of line followed by ^K to kill
  103. // line.
  104. in: "a b \001\013\r",
  105. line: "",
  106. },
  107. {
  108. // Ctrl-A to move to beginning of line, Ctrl-E to move to end,
  109. // finally ^K to kill nothing.
  110. in: "a b \001\005\013\r",
  111. line: "a b ",
  112. },
  113. {
  114. in: "\027\r",
  115. line: "",
  116. },
  117. {
  118. in: "a\027\r",
  119. line: "",
  120. },
  121. {
  122. in: "a \027\r",
  123. line: "",
  124. },
  125. {
  126. in: "a b\027\r",
  127. line: "a ",
  128. },
  129. {
  130. in: "a b \027\r",
  131. line: "a ",
  132. },
  133. {
  134. in: "one two thr\x1b[D\027\r",
  135. line: "one two r",
  136. },
  137. {
  138. in: "\013\r",
  139. line: "",
  140. },
  141. {
  142. in: "a\013\r",
  143. line: "a",
  144. },
  145. {
  146. in: "ab\x1b[D\013\r",
  147. line: "a",
  148. },
  149. {
  150. in: "Ξεσκεπάζω\r",
  151. line: "Ξεσκεπάζω",
  152. },
  153. {
  154. in: "£\r\x1b[A\177\r", // non-ASCII char, enter, up, backspace.
  155. line: "",
  156. throwAwayLines: 1,
  157. },
  158. {
  159. in: "£\r££\x1b[A\x1b[B\177\r", // non-ASCII char, enter, 2x non-ASCII, up, down, backspace, enter.
  160. line: "£",
  161. throwAwayLines: 1,
  162. },
  163. {
  164. // Ctrl-D at the end of the line should be ignored.
  165. in: "a\004\r",
  166. line: "a",
  167. },
  168. {
  169. // a, b, left, Ctrl-D should erase the b.
  170. in: "ab\x1b[D\004\r",
  171. line: "a",
  172. },
  173. {
  174. // a, b, c, d, left, left, ^U should erase to the beginning of
  175. // the line.
  176. in: "abcd\x1b[D\x1b[D\025\r",
  177. line: "cd",
  178. },
  179. {
  180. // Bracketed paste mode: control sequences should be returned
  181. // verbatim in paste mode.
  182. in: "abc\x1b[200~de\177f\x1b[201~\177\r",
  183. line: "abcde\177",
  184. },
  185. {
  186. // Enter in bracketed paste mode should still work.
  187. in: "abc\x1b[200~d\refg\x1b[201~h\r",
  188. line: "efgh",
  189. throwAwayLines: 1,
  190. },
  191. {
  192. // Lines consisting entirely of pasted data should be indicated as such.
  193. in: "\x1b[200~a\r",
  194. line: "a",
  195. err: ErrPasteIndicator,
  196. },
  197. }
  198. func TestKeyPresses(t *testing.T) {
  199. for i, test := range keyPressTests {
  200. for j := 1; j < len(test.in); j++ {
  201. c := &MockTerminal{
  202. toSend: []byte(test.in),
  203. bytesPerRead: j,
  204. }
  205. ss := NewTerminal(c, "> ")
  206. for k := 0; k < test.throwAwayLines; k++ {
  207. _, err := ss.ReadLine()
  208. if err != nil {
  209. t.Errorf("Throwaway line %d from test %d resulted in error: %s", k, i, err)
  210. }
  211. }
  212. line, err := ss.ReadLine()
  213. if line != test.line {
  214. t.Errorf("Line resulting from test %d (%d bytes per read) was '%s', expected '%s'", i, j, line, test.line)
  215. break
  216. }
  217. if err != test.err {
  218. t.Errorf("Error resulting from test %d (%d bytes per read) was '%v', expected '%v'", i, j, err, test.err)
  219. break
  220. }
  221. }
  222. }
  223. }
  224. func TestPasswordNotSaved(t *testing.T) {
  225. c := &MockTerminal{
  226. toSend: []byte("password\r\x1b[A\r"),
  227. bytesPerRead: 1,
  228. }
  229. ss := NewTerminal(c, "> ")
  230. pw, _ := ss.ReadPassword("> ")
  231. if pw != "password" {
  232. t.Fatalf("failed to read password, got %s", pw)
  233. }
  234. line, _ := ss.ReadLine()
  235. if len(line) > 0 {
  236. t.Fatalf("password was saved in history")
  237. }
  238. }
  239. var setSizeTests = []struct {
  240. width, height int
  241. }{
  242. {40, 13},
  243. {80, 24},
  244. {132, 43},
  245. }
  246. func TestTerminalSetSize(t *testing.T) {
  247. for _, setSize := range setSizeTests {
  248. c := &MockTerminal{
  249. toSend: []byte("password\r\x1b[A\r"),
  250. bytesPerRead: 1,
  251. }
  252. ss := NewTerminal(c, "> ")
  253. ss.SetSize(setSize.width, setSize.height)
  254. pw, _ := ss.ReadPassword("Password: ")
  255. if pw != "password" {
  256. t.Fatalf("failed to read password, got %s", pw)
  257. }
  258. if string(c.received) != "Password: \r\n" {
  259. t.Errorf("failed to set the temporary prompt expected %q, got %q", "Password: ", c.received)
  260. }
  261. }
  262. }
  263. func TestReadPasswordLineEnd(t *testing.T) {
  264. var tests = []struct {
  265. input string
  266. want string
  267. }{
  268. {"\n", ""},
  269. {"\r\n", ""},
  270. {"test\r\n", "test"},
  271. {"testtesttesttes\n", "testtesttesttes"},
  272. {"testtesttesttes\r\n", "testtesttesttes"},
  273. {"testtesttesttesttest\n", "testtesttesttesttest"},
  274. {"testtesttesttesttest\r\n", "testtesttesttesttest"},
  275. }
  276. for _, test := range tests {
  277. buf := new(bytes.Buffer)
  278. if _, err := buf.WriteString(test.input); err != nil {
  279. t.Fatal(err)
  280. }
  281. have, err := readPasswordLine(buf)
  282. if err != nil {
  283. t.Errorf("readPasswordLine(%q) failed: %v", test.input, err)
  284. continue
  285. }
  286. if string(have) != test.want {
  287. t.Errorf("readPasswordLine(%q) returns %q, but %q is expected", test.input, string(have), test.want)
  288. continue
  289. }
  290. if _, err = buf.WriteString(test.input); err != nil {
  291. t.Fatal(err)
  292. }
  293. have, err = readPasswordLine(buf)
  294. if err != nil {
  295. t.Errorf("readPasswordLine(%q) failed: %v", test.input, err)
  296. continue
  297. }
  298. if string(have) != test.want {
  299. t.Errorf("readPasswordLine(%q) returns %q, but %q is expected", test.input, string(have), test.want)
  300. continue
  301. }
  302. }
  303. }
  304. func TestMakeRawState(t *testing.T) {
  305. fd := int(os.Stdout.Fd())
  306. if !IsTerminal(fd) {
  307. t.Skip("stdout is not a terminal; skipping test")
  308. }
  309. st, err := GetState(fd)
  310. if err != nil {
  311. t.Fatalf("failed to get terminal state from GetState: %s", err)
  312. }
  313. if runtime.GOOS == "darwin" && (runtime.GOARCH == "arm" || runtime.GOARCH == "arm64") {
  314. t.Skip("MakeRaw not allowed on iOS; skipping test")
  315. }
  316. defer Restore(fd, st)
  317. raw, err := MakeRaw(fd)
  318. if err != nil {
  319. t.Fatalf("failed to get terminal state from MakeRaw: %s", err)
  320. }
  321. if *st != *raw {
  322. t.Errorf("states do not match; was %v, expected %v", raw, st)
  323. }
  324. }
  325. func TestOutputNewlines(t *testing.T) {
  326. // \n should be changed to \r\n in terminal output.
  327. buf := new(bytes.Buffer)
  328. term := NewTerminal(buf, ">")
  329. term.Write([]byte("1\n2\n"))
  330. output := string(buf.Bytes())
  331. const expected = "1\r\n2\r\n"
  332. if output != expected {
  333. t.Errorf("incorrect output: was %q, expected %q", output, expected)
  334. }
  335. }