SetSize has a problem may cause the following ReadPassword setting
temporary prompt not working, when changing width the current
SetSize will call clearAndRepaintLinePlusNPrevious which would
print an old prompt whatever the current line has, causing a following
ReadPassword with temporary prompt not printing the different prompt.
When running code like this, the nt.SetSize prints a "> " as
prompt then the temporary "Password: " prompt would never show up.
```go
oldState, err := terminal.MakeRaw(int(os.Stdin.Fd()))
width, height, _ = terminal.GetSize(int(os.Stdin.Fd()))
nt := terminal.NewTerminal(os.Stdin, "> ")
nt.SetSize(width, height)
password, err = nt.ReadPassword("Password: ")
```
the new test cases is to test SetSize with different terminal sizes,
either shrinking or expanding, a following ReadPassword should get the
correct temporary prompt.
Change-Id: I33d13b2c732997c0c88670d53545b8c0048b94b6
Reviewed-on: https://go-review.googlesource.com/1861
Reviewed-by: Adam Langley <agl@golang.org>
270 lines
5.2 KiB
Go
270 lines
5.2 KiB
Go
// Copyright 2011 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package terminal
|
|
|
|
import (
|
|
"io"
|
|
"testing"
|
|
)
|
|
|
|
type MockTerminal struct {
|
|
toSend []byte
|
|
bytesPerRead int
|
|
received []byte
|
|
}
|
|
|
|
func (c *MockTerminal) Read(data []byte) (n int, err error) {
|
|
n = len(data)
|
|
if n == 0 {
|
|
return
|
|
}
|
|
if n > len(c.toSend) {
|
|
n = len(c.toSend)
|
|
}
|
|
if n == 0 {
|
|
return 0, io.EOF
|
|
}
|
|
if c.bytesPerRead > 0 && n > c.bytesPerRead {
|
|
n = c.bytesPerRead
|
|
}
|
|
copy(data, c.toSend[:n])
|
|
c.toSend = c.toSend[n:]
|
|
return
|
|
}
|
|
|
|
func (c *MockTerminal) Write(data []byte) (n int, err error) {
|
|
c.received = append(c.received, data...)
|
|
return len(data), nil
|
|
}
|
|
|
|
func TestClose(t *testing.T) {
|
|
c := &MockTerminal{}
|
|
ss := NewTerminal(c, "> ")
|
|
line, err := ss.ReadLine()
|
|
if line != "" {
|
|
t.Errorf("Expected empty line but got: %s", line)
|
|
}
|
|
if err != io.EOF {
|
|
t.Errorf("Error should have been EOF but got: %s", err)
|
|
}
|
|
}
|
|
|
|
var keyPressTests = []struct {
|
|
in string
|
|
line string
|
|
err error
|
|
throwAwayLines int
|
|
}{
|
|
{
|
|
err: io.EOF,
|
|
},
|
|
{
|
|
in: "\r",
|
|
line: "",
|
|
},
|
|
{
|
|
in: "foo\r",
|
|
line: "foo",
|
|
},
|
|
{
|
|
in: "a\x1b[Cb\r", // right
|
|
line: "ab",
|
|
},
|
|
{
|
|
in: "a\x1b[Db\r", // left
|
|
line: "ba",
|
|
},
|
|
{
|
|
in: "a\177b\r", // backspace
|
|
line: "b",
|
|
},
|
|
{
|
|
in: "\x1b[A\r", // up
|
|
},
|
|
{
|
|
in: "\x1b[B\r", // down
|
|
},
|
|
{
|
|
in: "line\x1b[A\x1b[B\r", // up then down
|
|
line: "line",
|
|
},
|
|
{
|
|
in: "line1\rline2\x1b[A\r", // recall previous line.
|
|
line: "line1",
|
|
throwAwayLines: 1,
|
|
},
|
|
{
|
|
// recall two previous lines and append.
|
|
in: "line1\rline2\rline3\x1b[A\x1b[Axxx\r",
|
|
line: "line1xxx",
|
|
throwAwayLines: 2,
|
|
},
|
|
{
|
|
// Ctrl-A to move to beginning of line followed by ^K to kill
|
|
// line.
|
|
in: "a b \001\013\r",
|
|
line: "",
|
|
},
|
|
{
|
|
// Ctrl-A to move to beginning of line, Ctrl-E to move to end,
|
|
// finally ^K to kill nothing.
|
|
in: "a b \001\005\013\r",
|
|
line: "a b ",
|
|
},
|
|
{
|
|
in: "\027\r",
|
|
line: "",
|
|
},
|
|
{
|
|
in: "a\027\r",
|
|
line: "",
|
|
},
|
|
{
|
|
in: "a \027\r",
|
|
line: "",
|
|
},
|
|
{
|
|
in: "a b\027\r",
|
|
line: "a ",
|
|
},
|
|
{
|
|
in: "a b \027\r",
|
|
line: "a ",
|
|
},
|
|
{
|
|
in: "one two thr\x1b[D\027\r",
|
|
line: "one two r",
|
|
},
|
|
{
|
|
in: "\013\r",
|
|
line: "",
|
|
},
|
|
{
|
|
in: "a\013\r",
|
|
line: "a",
|
|
},
|
|
{
|
|
in: "ab\x1b[D\013\r",
|
|
line: "a",
|
|
},
|
|
{
|
|
in: "Ξεσκεπάζω\r",
|
|
line: "Ξεσκεπάζω",
|
|
},
|
|
{
|
|
in: "£\r\x1b[A\177\r", // non-ASCII char, enter, up, backspace.
|
|
line: "",
|
|
throwAwayLines: 1,
|
|
},
|
|
{
|
|
in: "£\r££\x1b[A\x1b[B\177\r", // non-ASCII char, enter, 2x non-ASCII, up, down, backspace, enter.
|
|
line: "£",
|
|
throwAwayLines: 1,
|
|
},
|
|
{
|
|
// Ctrl-D at the end of the line should be ignored.
|
|
in: "a\004\r",
|
|
line: "a",
|
|
},
|
|
{
|
|
// a, b, left, Ctrl-D should erase the b.
|
|
in: "ab\x1b[D\004\r",
|
|
line: "a",
|
|
},
|
|
{
|
|
// a, b, c, d, left, left, ^U should erase to the beginning of
|
|
// the line.
|
|
in: "abcd\x1b[D\x1b[D\025\r",
|
|
line: "cd",
|
|
},
|
|
{
|
|
// Bracketed paste mode: control sequences should be returned
|
|
// verbatim in paste mode.
|
|
in: "abc\x1b[200~de\177f\x1b[201~\177\r",
|
|
line: "abcde\177",
|
|
},
|
|
{
|
|
// Enter in bracketed paste mode should still work.
|
|
in: "abc\x1b[200~d\refg\x1b[201~h\r",
|
|
line: "efgh",
|
|
throwAwayLines: 1,
|
|
},
|
|
{
|
|
// Lines consisting entirely of pasted data should be indicated as such.
|
|
in: "\x1b[200~a\r",
|
|
line: "a",
|
|
err: ErrPasteIndicator,
|
|
},
|
|
}
|
|
|
|
func TestKeyPresses(t *testing.T) {
|
|
for i, test := range keyPressTests {
|
|
for j := 1; j < len(test.in); j++ {
|
|
c := &MockTerminal{
|
|
toSend: []byte(test.in),
|
|
bytesPerRead: j,
|
|
}
|
|
ss := NewTerminal(c, "> ")
|
|
for k := 0; k < test.throwAwayLines; k++ {
|
|
_, err := ss.ReadLine()
|
|
if err != nil {
|
|
t.Errorf("Throwaway line %d from test %d resulted in error: %s", k, i, err)
|
|
}
|
|
}
|
|
line, err := ss.ReadLine()
|
|
if line != test.line {
|
|
t.Errorf("Line resulting from test %d (%d bytes per read) was '%s', expected '%s'", i, j, line, test.line)
|
|
break
|
|
}
|
|
if err != test.err {
|
|
t.Errorf("Error resulting from test %d (%d bytes per read) was '%v', expected '%v'", i, j, err, test.err)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPasswordNotSaved(t *testing.T) {
|
|
c := &MockTerminal{
|
|
toSend: []byte("password\r\x1b[A\r"),
|
|
bytesPerRead: 1,
|
|
}
|
|
ss := NewTerminal(c, "> ")
|
|
pw, _ := ss.ReadPassword("> ")
|
|
if pw != "password" {
|
|
t.Fatalf("failed to read password, got %s", pw)
|
|
}
|
|
line, _ := ss.ReadLine()
|
|
if len(line) > 0 {
|
|
t.Fatalf("password was saved in history")
|
|
}
|
|
}
|
|
|
|
var setSizeTests = []struct {
|
|
width, height int
|
|
}{
|
|
{40, 13},
|
|
{80, 24},
|
|
{132, 43},
|
|
}
|
|
|
|
func TestTerminalSetSize(t *testing.T) {
|
|
for _, setSize := range setSizeTests {
|
|
c := &MockTerminal{
|
|
toSend: []byte("password\r\x1b[A\r"),
|
|
bytesPerRead: 1,
|
|
}
|
|
ss := NewTerminal(c, "> ")
|
|
ss.SetSize(setSize.width, setSize.height)
|
|
pw, _ := ss.ReadPassword("Password: ")
|
|
if pw != "password" {
|
|
t.Fatalf("failed to read password, got %s", pw)
|
|
}
|
|
if string(c.received) != "Password: \r\n" {
|
|
t.Errorf("failed to set the temporary prompt expected %q, got %q", "Password: ", c.received)
|
|
}
|
|
}
|
|
}
|