@@ -6,8 +6,10 @@ package pty_test
6
6
import (
7
7
"bytes"
8
8
"context"
9
+ "fmt"
9
10
"io"
10
11
"os/exec"
12
+ "strings"
11
13
"testing"
12
14
"time"
13
15
@@ -77,7 +79,7 @@ func Test_Start_copy(t *testing.T) {
77
79
case <- ctx .Done ():
78
80
t .Error ("read timed out" )
79
81
}
80
- assert .Equal (t , "test" , b .String ())
82
+ assert .Contains (t , b .String (), "test" )
81
83
82
84
cmdDone := make (chan error )
83
85
go func () {
@@ -91,3 +93,100 @@ func Test_Start_copy(t *testing.T) {
91
93
t .Error ("cmd.Wait() timed out" )
92
94
}
93
95
}
96
+
97
+ const countEnd = 1000
98
+
99
+ func Test_Start_trucation (t * testing.T ) {
100
+ t .Parallel ()
101
+
102
+ ctx , cancel := context .WithTimeout (context .Background (), time .Second * 1000 )
103
+ defer cancel ()
104
+
105
+ pc , cmd , err := pty .Start (exec .CommandContext (ctx ,
106
+ "cmd.exe" , "/c" ,
107
+ fmt .Sprintf ("for /L %%n in (1,1,%d) do @echo %%n" , countEnd )))
108
+ require .NoError (t , err )
109
+ readDone := make (chan struct {})
110
+ go func () {
111
+ defer close (readDone )
112
+ // avoid buffered IO so that we can precisely control how many bytes to read.
113
+ n := 1
114
+ for n < countEnd - 25 {
115
+ want := fmt .Sprintf ("%d\r \n " , n )
116
+ // the output also contains virtual terminal sequences
117
+ // so just read until we see the number we want.
118
+ err := readUntil (ctx , want , pc .OutputReader ())
119
+ require .NoError (t , err , "want: %s" , want )
120
+ n ++
121
+ }
122
+ }()
123
+
124
+ select {
125
+ case <- readDone :
126
+ // OK!
127
+ case <- ctx .Done ():
128
+ t .Error ("read timed out" )
129
+ }
130
+
131
+ cmdDone := make (chan error )
132
+ go func () {
133
+ cmdDone <- cmd .Wait ()
134
+ }()
135
+
136
+ select {
137
+ case err := <- cmdDone :
138
+ require .NoError (t , err )
139
+ case <- ctx .Done ():
140
+ t .Error ("cmd.Wait() timed out" )
141
+ }
142
+
143
+ // do our final 25 reads, to make sure the output wasn't lost
144
+ readDone = make (chan struct {})
145
+ go func () {
146
+ defer close (readDone )
147
+ // avoid buffered IO so that we can precisely control how many bytes to read.
148
+ n := countEnd - 25
149
+ for n <= countEnd {
150
+ want := fmt .Sprintf ("%d\r \n " , n )
151
+ err := readUntil (ctx , want , pc .OutputReader ())
152
+ if n < countEnd {
153
+ require .NoError (t , err , "want: %s" , want )
154
+ } else {
155
+ require .ErrorIs (t , err , io .EOF )
156
+ }
157
+ n ++
158
+ }
159
+ }()
160
+
161
+ select {
162
+ case <- readDone :
163
+ // OK!
164
+ case <- ctx .Done ():
165
+ t .Error ("read timed out" )
166
+ }
167
+ }
168
+
169
+ // readUntil reads one byte at a time until we either see the string we want, or the context expires
170
+ func readUntil (ctx context.Context , want string , r io.Reader ) error {
171
+ got := ""
172
+ readErrs := make (chan error , 1 )
173
+ for {
174
+ b := make ([]byte , 1 )
175
+ go func () {
176
+ _ , err := r .Read (b )
177
+ readErrs <- err
178
+ }()
179
+ select {
180
+ case err := <- readErrs :
181
+ if err != nil {
182
+ return err
183
+ }
184
+ got = got + string (b )
185
+ case <- ctx .Done ():
186
+ return ctx .Err ()
187
+ }
188
+ if strings .Contains (got , want ) {
189
+ return nil
190
+ }
191
+ }
192
+ }
0 commit comments