...

Source file src/golang.org/x/term/terminal_test.go

Documentation: golang.org/x/term

     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  
     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", // right
    76  		line: "ab",
    77  	},
    78  	{
    79  		in:   "a\x1b[Db\r", // left
    80  		line: "ba",
    81  	},
    82  	{
    83  		in:   "a\006b\r", // ^F
    84  		line: "ab",
    85  	},
    86  	{
    87  		in:   "a\002b\r", // ^B
    88  		line: "ba",
    89  	},
    90  	{
    91  		in:   "a\177b\r", // backspace
    92  		line: "b",
    93  	},
    94  	{
    95  		in: "\x1b[A\r", // up
    96  	},
    97  	{
    98  		in: "\x1b[B\r", // down
    99  	},
   100  	{
   101  		in: "\016\r", // ^P
   102  	},
   103  	{
   104  		in: "\014\r", // ^N
   105  	},
   106  	{
   107  		in:   "line\x1b[A\x1b[B\r", // up then down
   108  		line: "line",
   109  	},
   110  	{
   111  		in:             "line1\rline2\x1b[A\r", // recall previous line.
   112  		line:           "line1",
   113  		throwAwayLines: 1,
   114  	},
   115  	{
   116  		// recall two previous lines and append.
   117  		in:             "line1\rline2\rline3\x1b[A\x1b[Axxx\r",
   118  		line:           "line1xxx",
   119  		throwAwayLines: 2,
   120  	},
   121  	{
   122  		// Ctrl-A to move to beginning of line followed by ^K to kill
   123  		// line.
   124  		in:   "a b \001\013\r",
   125  		line: "",
   126  	},
   127  	{
   128  		// Ctrl-A to move to beginning of line, Ctrl-E to move to end,
   129  		// finally ^K to kill nothing.
   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", // non-ASCII char, enter, up, backspace.
   175  		line:           "",
   176  		throwAwayLines: 1,
   177  	},
   178  	{
   179  		in:             "£\r££\x1b[A\x1b[B\177\r", // non-ASCII char, enter, 2x non-ASCII, up, down, backspace, enter.
   180  		line:           "£",
   181  		throwAwayLines: 1,
   182  	},
   183  	{
   184  		// Ctrl-D at the end of the line should be ignored.
   185  		in:   "a\004\r",
   186  		line: "a",
   187  	},
   188  	{
   189  		// a, b, left, Ctrl-D should erase the b.
   190  		in:   "ab\x1b[D\004\r",
   191  		line: "a",
   192  	},
   193  	{
   194  		// a, b, c, d, left, left, ^U should erase to the beginning of
   195  		// the line.
   196  		in:   "abcd\x1b[D\x1b[D\025\r",
   197  		line: "cd",
   198  	},
   199  	{
   200  		// Bracketed paste mode: control sequences should be returned
   201  		// verbatim in paste mode.
   202  		in:   "abc\x1b[200~de\177f\x1b[201~\177\r",
   203  		line: "abcde\177",
   204  	},
   205  	{
   206  		// Enter in bracketed paste mode should still work.
   207  		in:             "abc\x1b[200~d\refg\x1b[201~h\r",
   208  		line:           "efgh",
   209  		throwAwayLines: 1,
   210  	},
   211  	{
   212  		// Lines consisting entirely of pasted data should be indicated as such.
   213  		in:   "\x1b[200~a\r",
   214  		line: "a",
   215  		err:  ErrPasteIndicator,
   216  	},
   217  	{
   218  		// Ctrl-C terminates readline
   219  		in:  "\003",
   220  		err: io.EOF,
   221  	},
   222  	{
   223  		// Ctrl-C at the end of line also terminates readline
   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  		// Cursor move after keyHome (left 4) then enter (right 4, newline)
   263  		in:       "abcd\x1b[H\r",
   264  		received: "> abcd\x1b[4D\x1b[4C\r\n",
   265  	},
   266  	{
   267  		// Write, home, prepend, enter. Prepends rewrites the line.
   268  		in: "cdef\x1b[Hab\r",
   269  		received: "> cdef" + // Initial input
   270  			"\x1b[4Da" + // Move cursor back, insert first char
   271  			"cdef" + // Copy over original string
   272  			"\x1b[4Dbcdef" + // Repeat for second char with copy
   273  			"\x1b[4D" + // Put cursor back in position to insert again
   274  			"\x1b[4C\r\n", // Put cursor at the end of the line and newline.
   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  	// \n should be changed to \r\n in terminal output.
   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