terminal_test.go 5.7 KB

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