Visitar URL original
term: process bytes returned with a read error · golang/term@7c2fb74 · GitHub
Skip to content

Commit 7c2fb74

Browse files
racequitegopherbot
authored andcommitted
term: process bytes returned with a read error
The io.Reader contract permits Read to return both data and a non-nil error. readLine currently returns the error before processing the data, which can discard the final complete line when a reader returns data with io.EOF. Append returned bytes to the input buffer and save the accompanying error. Report the error after the buffered input has been processed, preserving it across ReadLine calls when the input contains a complete line. Add a regression test for a reader that returns "line\r" and io.EOF in the same call. Fixes golang/go#80661 Change-Id: I34ac28731ec40f3398b9c0990e19cc17b737ba9e Reviewed-on: https://go-review.googlesource.com/c/term/+/808420 Reviewed-by: Alan Donovan <adonovan@google.com> LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Alan Donovan <adonovan@google.com> Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
1 parent 3963fce commit 7c2fb74

2 files changed

Lines changed: 55 additions & 7 deletions

File tree

‎terminal.go‎

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@ type Terminal struct {
106106
// a read. It aliases into inBuf.
107107
remainder []byte
108108
inBuf [256]byte
109+
// readErr is an error returned by a read that also returned data. It is
110+
// reported after all of that data has been processed.
111+
readErr error
109112

110113
// History records and retrieves lines of input read by [ReadLine] which
111114
// a user can retrieve and navigate using the up and down arrow keys.
@@ -788,7 +791,11 @@ func (t *Terminal) ReadPassword(prompt string) (line string, err error) {
788791
return
789792
}
790793

791-
// ReadLine returns a line of input from the terminal.
794+
// ReadLine returns a line of input from the terminal, excluding the
795+
// trailing newline. It may return partial data along with an error.
796+
// An [io.EOF] error indicates the end of the stream. For other errors,
797+
// such as [ErrPasteIndicator] in bracketed paste mode, a subsequent
798+
// call may return more data.
792799
func (t *Terminal) ReadLine() (line string, err error) {
793800
t.lock.Lock()
794801
defer t.lock.Unlock()
@@ -863,21 +870,24 @@ func (t *Terminal) readLine() (line string, err error) {
863870
}
864871
return
865872
}
873+
if t.readErr != nil {
874+
err = t.readErr
875+
t.readErr = nil
876+
return
877+
}
866878

867879
// t.remainder is a slice at the beginning of t.inBuf
868880
// containing a partial key sequence
869881
readBuf := t.inBuf[len(t.remainder):]
870-
var n int
871882

872883
t.lock.Unlock()
873-
n, err = t.c.Read(readBuf)
884+
n, readErr := t.c.Read(readBuf)
874885
t.lock.Lock()
875886

876-
if err != nil {
877-
return
878-
}
879-
880887
t.remainder = t.inBuf[:n+len(t.remainder)]
888+
if readErr != nil {
889+
t.readErr = readErr
890+
}
881891
}
882892
}
883893

‎terminal_test.go‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,44 @@ func TestClose(t *testing.T) {
5656
}
5757
}
5858

59+
func TestReadLineProcessesDataWithEOF(t *testing.T) {
60+
c := &dataAndEOFReader{data: []byte("line\r")}
61+
ss := NewTerminal(c, "> ")
62+
line, err := ss.ReadLine()
63+
if err != nil {
64+
t.Fatalf("ReadLine returned error with line data: %v", err)
65+
}
66+
if line != "line" {
67+
t.Fatalf("ReadLine returned %q, want %q", line, "line")
68+
}
69+
70+
line, err = ss.ReadLine()
71+
if line != "" || err != io.EOF {
72+
t.Fatalf("ReadLine after buffered data returned (%q, %v), want (%q, EOF)", line, err, "")
73+
}
74+
if c.reads != 1 {
75+
t.Fatalf("ReadLine performed %d reads, want 1", c.reads)
76+
}
77+
}
78+
79+
type dataAndEOFReader struct {
80+
data []byte
81+
reads int
82+
}
83+
84+
func (r *dataAndEOFReader) Read(p []byte) (int, error) {
85+
if r.reads > 0 {
86+
return 0, io.EOF
87+
}
88+
r.reads++
89+
n := copy(p, r.data)
90+
return n, io.EOF
91+
}
92+
93+
func (r *dataAndEOFReader) Write(p []byte) (int, error) {
94+
return len(p), nil
95+
}
96+
5997
var keyPressTests = []struct {
6098
in string
6199
line string

0 commit comments

Comments
 (0)