4
4
"bufio"
5
5
"bytes"
6
6
"context"
7
+ "fmt"
7
8
"io"
8
9
"os/exec"
9
10
"runtime"
@@ -21,13 +22,17 @@ import (
21
22
)
22
23
23
24
func New (t * testing.T ) * PTY {
25
+ t .Helper ()
26
+
24
27
ptty , err := pty .New ()
25
28
require .NoError (t , err )
26
29
27
30
return create (t , ptty , "cmd" )
28
31
}
29
32
30
33
func Start (t * testing.T , cmd * exec.Cmd ) (* PTY , pty.Process ) {
34
+ t .Helper ()
35
+
31
36
ptty , ps , err := pty .Start (cmd )
32
37
require .NoError (t , err )
33
38
return create (t , ptty , cmd .Args [0 ]), ps
@@ -42,14 +47,6 @@ func create(t *testing.T, ptty pty.PTY, name string) *PTY {
42
47
_ = logr .Close ()
43
48
<- logDone // Guard against logging after test.
44
49
})
45
- go func () {
46
- defer close (logDone )
47
- s := bufio .NewScanner (logr )
48
- for s .Scan () {
49
- // Quote output to avoid terminal escape codes, e.g. bell.
50
- t .Logf ("%s: stdout: %q" , name , s .Text ())
51
- }
52
- }()
53
50
54
51
// Write to log and output buffer.
55
52
copyDone := make (chan struct {})
@@ -66,19 +63,32 @@ func create(t *testing.T, ptty pty.PTY, name string) *PTY {
66
63
<- copyDone
67
64
})
68
65
69
- return & PTY {
70
- t : t ,
71
- PTY : ptty ,
72
- out : out ,
66
+ tpty := & PTY {
67
+ t : t ,
68
+ PTY : ptty ,
69
+ out : out ,
70
+ name : name ,
73
71
74
72
runeReader : bufio .NewReaderSize (out , utf8 .UTFMax ),
75
73
}
74
+
75
+ go func () {
76
+ defer close (logDone )
77
+ s := bufio .NewScanner (logr )
78
+ for s .Scan () {
79
+ // Quote output to avoid terminal escape codes, e.g. bell.
80
+ tpty .logf ("stdout: %q" , s .Text ())
81
+ }
82
+ }()
83
+
84
+ return tpty
76
85
}
77
86
78
87
type PTY struct {
79
- t * testing.T
80
88
pty.PTY
81
- out * stdbuf
89
+ t * testing.T
90
+ out * stdbuf
91
+ name string
82
92
83
93
runeReader * bufio.Reader
84
94
}
@@ -113,26 +123,27 @@ func (p *PTY) ExpectMatch(str string) string {
113
123
select {
114
124
case err := <- match :
115
125
if err != nil {
116
- p .t . Fatalf ( "%s: read error: %v (wanted %q; got %q)", time . Now () , err , str , buffer .String ())
126
+ p .fatalf ( " read error" , " %v (wanted %q; got %q)" , err , str , buffer .String ())
117
127
return ""
118
128
}
119
- p .t . Logf ( "%s: matched %q = %q", time . Now () , str , buffer .String ())
129
+ p .logf ( " matched %q = %q" , str , buffer .String ())
120
130
return buffer .String ()
121
131
case <- timeout .Done ():
122
132
// Ensure goroutine is cleaned up before test exit.
123
133
_ = p .out .closeErr (p .Close ())
124
134
<- match
125
135
126
- p .t . Fatalf ( "%s: match exceeded deadline: wanted %q; got %q", time . Now () , str , buffer .String ())
136
+ p .fatalf ( " match exceeded deadline" , " wanted %q; got %q" , str , buffer .String ())
127
137
return ""
128
138
}
129
139
}
130
140
131
141
func (p * PTY ) Write (r rune ) {
132
142
p .t .Helper ()
133
143
144
+ p .logf ("stdin: %q" , r )
134
145
_ , err := p .Input ().Write ([]byte {byte (r )})
135
- require .NoError (p .t , err )
146
+ require .NoError (p .t , err , "write failed" )
136
147
}
137
148
138
149
func (p * PTY ) WriteLine (str string ) {
@@ -142,8 +153,27 @@ func (p *PTY) WriteLine(str string) {
142
153
if runtime .GOOS == "windows" {
143
154
newline = append (newline , '\n' )
144
155
}
156
+ p .logf ("stdin: %q" , str + string (newline ))
145
157
_ , err := p .Input ().Write (append ([]byte (str ), newline ... ))
146
- require .NoError (p .t , err )
158
+ require .NoError (p .t , err , "write line failed" )
159
+ }
160
+
161
+ func (p * PTY ) logf (format string , args ... interface {}) {
162
+ p .t .Helper ()
163
+
164
+ // Match regular logger timestamp format, we seem to be logging in
165
+ // UTC in other places as well, so match here.
166
+ p .t .Logf ("%s: %s: %s" , time .Now ().UTC ().Format ("2006-01-02 15:04:05.000" ), p .name , fmt .Sprintf (format , args ... ))
167
+ }
168
+
169
+ func (p * PTY ) fatalf (reason string , format string , args ... interface {}) {
170
+ p .t .Helper ()
171
+
172
+ // Ensure the message is part of the normal log stream before
173
+ // failing the test.
174
+ p .logf ("%s: %s" , reason , fmt .Sprintf (format , args ... ))
175
+
176
+ require .FailNowf (p .t , reason , format , args ... )
147
177
}
148
178
149
179
// stdbuf is like a buffered stdout, it buffers writes until read.
0 commit comments