Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f7467ca

Browse files
authored
fix: Improve ptytest closure on expect match timeout (#5337)
To ensure ptytest closure always happens the same way, we now define a new `Close` function on `PTY` and always call the close function instead of manually closing read/writers. A few additional log messages have been added as well, to better understand the shutdown process in case of errors.
1 parent a973c35 commit f7467ca

File tree

1 file changed

+60
-56
lines changed

1 file changed

+60
-56
lines changed

pty/ptytest/ptytest.go

Lines changed: 60 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -46,62 +46,69 @@ func create(t *testing.T, ptty pty.PTY, name string) *PTY {
4646
// Use pipe for logging.
4747
logDone := make(chan struct{})
4848
logr, logw := io.Pipe()
49-
t.Cleanup(func() {
50-
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
51-
defer cancel()
52-
53-
logf(t, name, "close logw on cleanup")
54-
_ = logw.Close()
55-
56-
logf(t, name, "close logr on cleanup")
57-
_ = logr.Close()
58-
59-
logf(t, name, "logr and logw closed")
60-
61-
select {
62-
case <-ctx.Done():
63-
fatalf(t, name, "cleanup", "log pipe did not close in time")
64-
case <-logDone: // Guard against logging after test.
65-
}
66-
})
6749

6850
// Write to log and output buffer.
6951
copyDone := make(chan struct{})
7052
out := newStdbuf()
7153
w := io.MultiWriter(logw, out)
72-
go func() {
73-
defer close(copyDone)
74-
_, err := io.Copy(w, ptty.Output())
75-
_ = out.closeErr(err)
76-
}()
54+
55+
tpty := &PTY{
56+
t: t,
57+
PTY: ptty,
58+
out: out,
59+
name: name,
60+
61+
runeReader: bufio.NewReaderSize(out, utf8.UTFMax),
62+
}
63+
// Ensure pty is cleaned up at the end of test.
7764
t.Cleanup(func() {
65+
_ = tpty.Close()
66+
})
67+
68+
logClose := func(name string, c io.Closer) {
69+
tpty.logf("closing %s", name)
70+
err := c.Close()
71+
tpty.logf("closed %s: %v", name, err)
72+
}
73+
// Set the actual close function for the tpty.
74+
tpty.close = func(reason string) error {
7875
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
7976
defer cancel()
8077

78+
tpty.logf("closing tpty: %s", reason)
79+
8180
// Close pty only so that the copy goroutine can consume the
8281
// remainder of it's buffer and then exit.
83-
logf(t, name, "close pty on cleanup")
84-
err := ptty.Close()
85-
// Pty may already be closed, so don't fail the test, but log
86-
// the error in case it's significant.
87-
logf(t, name, "closed pty: %v", err)
88-
82+
logClose("pty", ptty)
8983
select {
9084
case <-ctx.Done():
91-
fatalf(t, name, "cleanup", "copy did not close in time")
85+
tpty.fatalf("close", "copy did not close in time")
9286
case <-copyDone:
9387
}
94-
})
9588

96-
tpty := &PTY{
97-
t: t,
98-
PTY: ptty,
99-
out: out,
100-
name: name,
89+
logClose("logw", logw)
90+
logClose("logr", logr)
91+
select {
92+
case <-ctx.Done():
93+
tpty.fatalf("close", "log pipe did not close in time")
94+
case <-logDone:
95+
}
10196

102-
runeReader: bufio.NewReaderSize(out, utf8.UTFMax),
97+
tpty.logf("closed tpty")
98+
99+
return nil
103100
}
104101

102+
go func() {
103+
defer close(copyDone)
104+
_, err := io.Copy(w, ptty.Output())
105+
tpty.logf("copy done: %v", err)
106+
tpty.logf("closing out")
107+
err = out.closeErr(err)
108+
tpty.logf("closed out: %v", err)
109+
}()
110+
111+
// Log all output as part of test for easier debugging on errors.
105112
go func() {
106113
defer close(logDone)
107114
s := bufio.NewScanner(logr)
@@ -116,13 +123,20 @@ func create(t *testing.T, ptty pty.PTY, name string) *PTY {
116123

117124
type PTY struct {
118125
pty.PTY
119-
t *testing.T
120-
out *stdbuf
121-
name string
126+
t *testing.T
127+
close func(reason string) error
128+
out *stdbuf
129+
name string
122130

123131
runeReader *bufio.Reader
124132
}
125133

134+
func (p *PTY) Close() error {
135+
p.t.Helper()
136+
137+
return p.close("close")
138+
}
139+
126140
func (p *PTY) ExpectMatch(str string) string {
127141
p.t.Helper()
128142

@@ -160,7 +174,7 @@ func (p *PTY) ExpectMatch(str string) string {
160174
return buffer.String()
161175
case <-timeout.Done():
162176
// Ensure goroutine is cleaned up before test exit.
163-
_ = p.out.closeErr(p.Close())
177+
_ = p.close("expect match timeout")
164178
<-match
165179

166180
p.fatalf("match exceeded deadline", "wanted %q; got %q", str, buffer.String())
@@ -190,30 +204,20 @@ func (p *PTY) WriteLine(str string) {
190204

191205
func (p *PTY) logf(format string, args ...interface{}) {
192206
p.t.Helper()
193-
logf(p.t, p.name, format, args...)
194-
}
195-
196-
func (p *PTY) fatalf(reason string, format string, args ...interface{}) {
197-
p.t.Helper()
198-
fatalf(p.t, p.name, reason, format, args...)
199-
}
200-
201-
func logf(t *testing.T, name, format string, args ...interface{}) {
202-
t.Helper()
203207

204208
// Match regular logger timestamp format, we seem to be logging in
205209
// UTC in other places as well, so match here.
206-
t.Logf("%s: %s: %s", time.Now().UTC().Format("2006-01-02 15:04:05.000"), name, fmt.Sprintf(format, args...))
210+
p.t.Logf("%s: %s: %s", time.Now().UTC().Format("2006-01-02 15:04:05.000"), p.name, fmt.Sprintf(format, args...))
207211
}
208212

209-
func fatalf(t *testing.T, name, reason, format string, args ...interface{}) {
210-
t.Helper()
213+
func (p *PTY) fatalf(reason string, format string, args ...interface{}) {
214+
p.t.Helper()
211215

212216
// Ensure the message is part of the normal log stream before
213217
// failing the test.
214-
logf(t, name, "%s: %s", reason, fmt.Sprintf(format, args...))
218+
p.logf("%s: %s", reason, fmt.Sprintf(format, args...))
215219

216-
require.FailNowf(t, reason, format, args...)
220+
require.FailNowf(p.t, reason, format, args...)
217221
}
218222

219223
// stdbuf is like a buffered stdout, it buffers writes until read.

0 commit comments

Comments
 (0)