Skip to content

Commit 8a71158

Browse files
committed
Get windows tests working with conpty
1 parent 0144a1b commit 8a71158

File tree

5 files changed

+37
-223
lines changed

5 files changed

+37
-223
lines changed

expect/conpty/conpty.go

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,14 @@ type ConPty struct {
1919
hpCon windows.Handle
2020
pipeFdIn windows.Handle
2121
pipeFdOut windows.Handle
22+
pipe3 windows.Handle
23+
pipe4 windows.Handle
2224
consoleSize uintptr
23-
inPipe *os.File
24-
outPipe *os.File
25+
outputR *os.File
26+
outputW *os.File
27+
inputR *os.File
28+
inputW *os.File
29+
closed bool
2530
}
2631

2732
// New returns a new ConPty pseudo terminal device
@@ -35,38 +40,45 @@ func New(columns int16, rows int16) (*ConPty, error) {
3540

3641
// Close closes the pseudo-terminal and cleans up all attached resources
3742
func (c *ConPty) Close() error {
43+
if (c.closed) {
44+
return nil
45+
}
46+
3847
err := closePseudoConsole(c.hpCon)
39-
c.inPipe.Close()
40-
c.outPipe.Close()
48+
c.outputR.Close()
49+
c.outputW.Close()
50+
c.inputR.Close()
51+
c.inputW.Close()
52+
c.closed = true
4153
return err
4254
}
4355

4456
// OutPipe returns the output pipe of the pseudo terminal
4557
func (c *ConPty) OutPipe() *os.File {
46-
return c.inPipe
58+
return c.outputR
4759
}
4860

4961
func (c *ConPty) Reader() io.Reader {
50-
return c.outPipe
62+
return c.outputW
5163
}
5264

5365
// InPipe returns input pipe of the pseudo terminal
5466
// Note: It is safer to use the Write method to prevent partially-written VT sequences
5567
// from corrupting the terminal
5668
func (c *ConPty) InPipe() *os.File {
57-
return c.outPipe
69+
return c.inputR
5870
}
5971

6072
func (c *ConPty) WriteString(str string) (int, error) {
61-
return c.inPipe.WriteString(str)
73+
return c.inputW.WriteString(str)
6274
}
6375

6476
func (c *ConPty) createPseudoConsoleAndPipes() error {
6577
// These are the readers/writers for "stdin", but we only need this to
6678
// successfully call CreatePseudoConsole. After, we can throw it away.
6779
var hPipeInW, hPipeInR windows.Handle
6880

69-
// Create the stdin pipe although we never use this.
81+
// Create the stdin pipe
7082
if err := windows.CreatePipe(&hPipeInR, &hPipeInW, nil, 0); err != nil {
7183
return err
7284
}
@@ -81,16 +93,15 @@ func (c *ConPty) createPseudoConsoleAndPipes() error {
8193
return fmt.Errorf("failed to create pseudo console: %d, %v", uintptr(c.hpCon), err)
8294
}
8395

84-
// Close our stdin cause we're never going to use it
85-
if hPipeInR != windows.InvalidHandle {
86-
windows.CloseHandle(hPipeInR)
87-
}
88-
if hPipeInW != windows.InvalidHandle {
89-
windows.CloseHandle(hPipeInW)
90-
}
96+
c.pipe3 = hPipeInR
97+
c.pipe4 = hPipeInW
98+
99+
c.outputR = os.NewFile(uintptr(c.pipeFdIn), "|0")
100+
c.outputW = os.NewFile(uintptr(c.pipeFdOut), "|1")
91101

92-
c.inPipe = os.NewFile(uintptr(c.pipeFdIn), "|0")
93-
c.outPipe = os.NewFile(uintptr(c.pipeFdOut), "|1")
102+
c.inputR = os.NewFile(uintptr(c.pipe3), "|2")
103+
c.inputW = os.NewFile(uintptr(c.pipe4), "|3")
104+
c.closed = false
94105

95106
return nil
96107
}

expect/console.go

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import (
2121
"io/ioutil"
2222
"log"
2323
"os"
24-
"time"
2524
"unicode/utf8"
2625

2726
"github.com/coder/coder/expect/pty"
@@ -49,7 +48,6 @@ type ConsoleOpts struct {
4948
Closers []io.Closer
5049
ExpectObservers []ExpectObserver
5150
SendObservers []SendObserver
52-
ReadTimeout *time.Duration
5351
}
5452

5553
// ExpectObserver provides an interface for a function callback that will
@@ -114,14 +112,6 @@ func WithSendObserver(observers ...SendObserver) ConsoleOpt {
114112
}
115113
}
116114

117-
// WithDefaultTimeout sets a default read timeout during Expect statements.
118-
func WithDefaultTimeout(timeout time.Duration) ConsoleOpt {
119-
return func(opts *ConsoleOpts) error {
120-
opts.ReadTimeout = &timeout
121-
return nil
122-
}
123-
}
124-
125115
// NewConsole returns a new Console with the given options.
126116
func NewConsole(opts ...ConsoleOpt) (*Console, error) {
127117
options := ConsoleOpts{

expect/expect.go

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import (
1919
"bytes"
2020
"fmt"
2121
"io"
22-
"time"
2322
"unicode/utf8"
2423
)
2524

@@ -59,11 +58,6 @@ func (c *Console) Expect(opts ...ExpectOpt) (string, error) {
5958
writer := io.MultiWriter(append(c.opts.Stdouts, buf)...)
6059
runeWriter := bufio.NewWriterSize(writer, utf8.UTFMax)
6160

62-
readTimeout := c.opts.ReadTimeout
63-
if options.ReadTimeout != nil {
64-
readTimeout = options.ReadTimeout
65-
}
66-
6761
var matcher Matcher
6862
var err error
6963

@@ -78,13 +72,6 @@ func (c *Console) Expect(opts ...ExpectOpt) (string, error) {
7872
}()
7973

8074
for {
81-
if readTimeout != nil {
82-
err = c.passthroughPipe.SetReadDeadline(time.Now().Add(*readTimeout))
83-
if err != nil {
84-
return buf.String(), err
85-
}
86-
}
87-
8875
var r rune
8976
r, _, err = c.runeReader.ReadRune()
9077
if err != nil {

expect/expect_test.go

Lines changed: 1 addition & 175 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,11 @@ import (
1818
"bufio"
1919
"errors"
2020
"fmt"
21-
"io"
22-
// "io/ioutil"
23-
//"log"
24-
// "os"
25-
// "os/exec"
21+
"io"
2622
"runtime/debug"
2723
"strings"
2824
"sync"
2925
"testing"
30-
"time"
3126
)
3227

3328
var (
@@ -70,7 +65,6 @@ func newTestConsole(t *testing.T, opts ...ConsoleOpt) (*Console, error) {
7065
opts = append([]ConsoleOpt{
7166
expectNoError(t),
7267
sendNoError(t),
73-
WithDefaultTimeout(time.Second),
7468
}, opts...)
7569
return NewTestConsole(t, opts...)
7670
}
@@ -131,14 +125,12 @@ func TestExpectf(t *testing.T) {
131125
c.SendLine("2")
132126
c.Expectf("What is %s backwards?", "Netflix")
133127
c.SendLine("xilfteN")
134-
c.ExpectEOF()
135128
}()
136129

137130
err = Prompt(c.InTty(), c.OutTty())
138131
if err != nil {
139132
t.Errorf("Expected no error but got '%s'", err)
140133
}
141-
testCloser(t, c)
142134
wg.Wait()
143135
}
144136

@@ -159,15 +151,12 @@ func TestExpect(t *testing.T) {
159151
c.SendLine("2")
160152
c.ExpectString("What is Netflix backwards?")
161153
c.SendLine("xilfteN")
162-
//c.ExpectEOF()
163154
}()
164155

165156
err = Prompt(c.InTty(), c.OutTty())
166157
if err != nil {
167158
t.Errorf("Expected no error but got '%s'", err)
168159
}
169-
// close the pts so we can expect EOF
170-
testCloser(t, c)
171160
wg.Wait()
172161
}
173162

@@ -187,174 +176,11 @@ func TestExpectOutput(t *testing.T) {
187176
defer wg.Done()
188177
c.ExpectString("What is 1+1?")
189178
c.SendLine("3")
190-
//c.ExpectEOF()
191179
}()
192180

193181
err = Prompt(c.InTty(), c.OutTty())
194182
if err == nil || err != ErrWrongAnswer {
195183
t.Errorf("Expected error '%s' but got '%s' instead", ErrWrongAnswer, err)
196184
}
197-
testCloser(t, c)
198185
wg.Wait()
199186
}
200-
201-
// TODO: Needs to be updated to work on Windows
202-
// func TestExpectDefaultTimeout(t *testing.T) {
203-
// t.Parallel()
204-
205-
// c, err := NewTestConsole(t, WithDefaultTimeout(0))
206-
// if err != nil {
207-
// t.Errorf("Expected no error but got'%s'", err)
208-
// }
209-
// defer testCloser(t, c)
210-
211-
// var wg sync.WaitGroup
212-
// wg.Add(1)
213-
// go func() {
214-
// defer wg.Done()
215-
// Prompt(c.InTty(), c.OutTty())
216-
// }()
217-
218-
// _, err = c.ExpectString("What is 1+2?")
219-
// if err == nil || !strings.Contains(err.Error(), "i/o timeout") {
220-
// t.Errorf("Expected error to contain 'i/o timeout' but got '%s' instead", err)
221-
// }
222-
223-
// //Close to unblock Prompt and wait for the goroutine to exit.
224-
// c.Close()
225-
// wg.Wait()
226-
// }
227-
228-
// func TestExpectTimeout(t *testing.T) {
229-
// t.Parallel()
230-
231-
// c, err := NewTestConsole(t)
232-
// if err != nil {
233-
// t.Errorf("Expected no error but got'%s'", err)
234-
// }
235-
// defer testCloser(t, c)
236-
237-
// var wg sync.WaitGroup
238-
// wg.Add(1)
239-
// go func() {
240-
// defer wg.Done()
241-
// Prompt(c.InTty(), c.OutTty())
242-
// }()
243-
244-
// _, err = c.Expect(String("What is 1+2?"), WithTimeout(0))
245-
// if err == nil || !strings.Contains(err.Error(), "i/o timeout") {
246-
// t.Errorf("Expected error to contain 'i/o timeout' but got '%s' instead", err)
247-
// }
248-
249-
// //Close to unblock Prompt and wait for the goroutine to exit.
250-
// c.Close()
251-
// wg.Wait()
252-
// }
253-
254-
// func TestExpectDefaultTimeoutOverride(t *testing.T) {
255-
// t.Parallel()
256-
257-
// c, err := newTestConsole(t, WithDefaultTimeout(100*time.Millisecond))
258-
// if err != nil {
259-
// t.Errorf("Expected no error but got'%s'", err)
260-
// }
261-
// defer testCloser(t, c)
262-
263-
// var wg sync.WaitGroup
264-
// wg.Add(1)
265-
// go func() {
266-
// defer wg.Done()
267-
// err = Prompt(c.InTty(), c.OutTty())
268-
// if err != nil {
269-
// t.Errorf("Expected no error but got '%s'", err)
270-
// }
271-
// time.Sleep(200 * time.Millisecond)
272-
// c.Close()
273-
// }()
274-
275-
// c.ExpectString("What is 1+1?")
276-
// c.SendLine("2")
277-
// c.ExpectString("What is Netflix backwards?")
278-
// c.SendLine("xilfteN")
279-
// c.Expect(EOF, PTSClosed, WithTimeout(time.Second))
280-
281-
// wg.Wait()
282-
// }
283-
284-
// func TestEditor(t *testing.T) {
285-
// if _, err := exec.LookPath("vi"); err != nil {
286-
// t.Skip("vi not found in PATH")
287-
// }
288-
// t.Parallel()
289-
290-
// c, err := NewConsole(expectNoError(t), sendNoError(t))
291-
// if err != nil {
292-
// t.Errorf("Expected no error but got '%s'", err)
293-
// }
294-
// defer testCloser(t, c)
295-
296-
// file, err := ioutil.TempFile("", "")
297-
// if err != nil {
298-
// t.Errorf("Expected no error but got '%s'", err)
299-
// }
300-
301-
// cmd := exec.Command("vi", file.Name())
302-
// cmd.Stdin = c.InTty()
303-
// cmd.Stdout = c.OutTty()
304-
// cmd.Stderr = c.OutTty()
305-
306-
// var wg sync.WaitGroup
307-
// wg.Add(1)
308-
// go func() {
309-
// defer wg.Done()
310-
// c.Send("iHello world\x1b")
311-
// c.SendLine(":wq!")
312-
// c.ExpectEOF()
313-
// }()
314-
315-
// err = cmd.Run()
316-
// if err != nil {
317-
// t.Errorf("Expected no error but got '%s'", err)
318-
// }
319-
320-
// testCloser(t, c)
321-
// wg.Wait()
322-
323-
// data, err := ioutil.ReadFile(file.Name())
324-
// if err != nil {
325-
// t.Errorf("Expected no error but got '%s'", err)
326-
// }
327-
// if string(data) != "Hello world\n" {
328-
// t.Errorf("Expected '%s' to equal '%s'", string(data), "Hello world\n")
329-
// }
330-
// }
331-
332-
// func ExampleConsole_echo() {
333-
// c, err := NewConsole(WithStdout(os.Stdout))
334-
// if err != nil {
335-
// log.Fatal(err)
336-
// }
337-
// defer c.Close()
338-
339-
// cmd := exec.Command("echo")
340-
// cmd.Stdin = c.InTty()
341-
// cmd.Stdout = c.OutTty()
342-
// cmd.Stderr = c.OutTty()
343-
344-
// err = cmd.Start()
345-
// if err != nil {
346-
// log.Fatal(err)
347-
// }
348-
349-
// c.Send("Hello world")
350-
// c.ExpectString("Hello world")
351-
// c.Close()
352-
// c.ExpectEOF()
353-
354-
// err = cmd.Wait()
355-
// if err != nil {
356-
// log.Fatal(err)
357-
// }
358-
359-
// Output: Hello world
360-
// }

0 commit comments

Comments
 (0)