-
Notifications
You must be signed in to change notification settings - Fork 582
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: Run expect tests on Windows with conpty pseudo-terminal (#276)
This brings together a bunch of random, partially implemented packages for support of the new(ish) Windows [`conpty`](https://devblogs.microsoft.com/commandline/windows-command-line-introducing-the-windows-pseudo-console-conpty/) API - such that we can leverage the `expect` style of CLI tests, but in a way that works in Linux/OSX `pty`s and Windows `conpty`. These include: - Vendoring the `go-expect` library from Netflix w/ some tweaks to work cross-platform - Vendoring the `pty` cross-platform implementation from [waypoint-plugin-sdk](https://github.com/hashicorp/waypoint-plugin-sdk/tree/b55c787a65ff9b7d2b32cfae80681b78f8f2275e/internal/pkg/pty) - Vendoring the `conpty` Windows-specific implementation from [waypoint-plugin-sdk](https://github.com/hashicorp/waypoint-plugin-sdk/tree/b55c787a65ff9b7d2b32cfae80681b78f8f2275e/internal/pkg/conpty) - Adjusting the `pty` interface to work with `go-expect` + the cross-plat version There were several limitations with the current packages: - `go-expect` requires the same `os.File` (TTY) for input / output, but `conhost` requires separate file handles - `conpty` does not handle input, only output - The cross-platform `pty` didn't expose the full set of primitives needed for `console` Therefore, the following changes were made: - Handling of `stdin` was added to the `conpty` interface - We weren't using the full extent of the `go-expect` interface, so some portions were removed (ie, exec'ing a process) to simplify our implementation and make it easier to extend cross-platform - Instead of `console` exposing just a `Tty`, it exposes an `InTty` and `OutTty`, to help encapsulate the difference on Windows (on Linux, these point to the same pipe) Future improvements: - The `isatty` implementation doesn't support accurate detection of `conhost` pty's without an associated process. In lieu of a more robust check, I've added a `--force-tty` flag intended for test case use - that forces the CLI to run in tty mode. - It seems the windows implementation doesn't support setting a deadline. This is needed for the expect.Timeout API, but isn't used by us yet. Fixes #241
- Loading branch information
1 parent
64c14de
commit c9c0312
Showing
19 changed files
with
1,173 additions
and
50 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
//go:build windows | ||
// +build windows | ||
|
||
// Original copyright 2020 ActiveState Software. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file | ||
|
||
package conpty | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"os" | ||
|
||
"golang.org/x/sys/windows" | ||
) | ||
|
||
// ConPty represents a windows pseudo console. | ||
type ConPty struct { | ||
hpCon windows.Handle | ||
outPipePseudoConsoleSide windows.Handle | ||
outPipeOurSide windows.Handle | ||
inPipeOurSide windows.Handle | ||
inPipePseudoConsoleSide windows.Handle | ||
consoleSize uintptr | ||
outFilePseudoConsoleSide *os.File | ||
outFileOurSide *os.File | ||
inFilePseudoConsoleSide *os.File | ||
inFileOurSide *os.File | ||
closed bool | ||
} | ||
|
||
// New returns a new ConPty pseudo terminal device | ||
func New(columns int16, rows int16) (*ConPty, error) { | ||
c := &ConPty{ | ||
consoleSize: uintptr(columns) + (uintptr(rows) << 16), | ||
} | ||
|
||
return c, c.createPseudoConsoleAndPipes() | ||
} | ||
|
||
// Close closes the pseudo-terminal and cleans up all attached resources | ||
func (c *ConPty) Close() error { | ||
// Trying to close these pipes multiple times will result in an | ||
// access violation | ||
if c.closed { | ||
return nil | ||
} | ||
|
||
err := closePseudoConsole(c.hpCon) | ||
c.outFilePseudoConsoleSide.Close() | ||
c.outFileOurSide.Close() | ||
c.inFilePseudoConsoleSide.Close() | ||
c.inFileOurSide.Close() | ||
c.closed = true | ||
return err | ||
} | ||
|
||
// OutPipe returns the output pipe of the pseudo terminal | ||
func (c *ConPty) OutPipe() *os.File { | ||
return c.outFilePseudoConsoleSide | ||
} | ||
|
||
func (c *ConPty) Reader() io.Reader { | ||
return c.outFileOurSide | ||
} | ||
|
||
// InPipe returns input pipe of the pseudo terminal | ||
// Note: It is safer to use the Write method to prevent partially-written VT sequences | ||
// from corrupting the terminal | ||
func (c *ConPty) InPipe() *os.File { | ||
return c.inFilePseudoConsoleSide | ||
} | ||
|
||
func (c *ConPty) WriteString(str string) (int, error) { | ||
return c.inFileOurSide.WriteString(str) | ||
} | ||
|
||
func (c *ConPty) createPseudoConsoleAndPipes() error { | ||
// Create the stdin pipe | ||
if err := windows.CreatePipe(&c.inPipePseudoConsoleSide, &c.inPipeOurSide, nil, 0); err != nil { | ||
return err | ||
} | ||
|
||
// Create the stdout pipe | ||
if err := windows.CreatePipe(&c.outPipeOurSide, &c.outPipePseudoConsoleSide, nil, 0); err != nil { | ||
return err | ||
} | ||
|
||
// Create the pty with our stdin/stdout | ||
if err := createPseudoConsole(c.consoleSize, c.inPipePseudoConsoleSide, c.outPipePseudoConsoleSide, &c.hpCon); err != nil { | ||
return fmt.Errorf("failed to create pseudo console: %d, %v", uintptr(c.hpCon), err) | ||
} | ||
|
||
c.outFilePseudoConsoleSide = os.NewFile(uintptr(c.outPipePseudoConsoleSide), "|0") | ||
c.outFileOurSide = os.NewFile(uintptr(c.outPipeOurSide), "|1") | ||
|
||
c.inFilePseudoConsoleSide = os.NewFile(uintptr(c.inPipePseudoConsoleSide), "|2") | ||
c.inFileOurSide = os.NewFile(uintptr(c.inPipeOurSide), "|3") | ||
c.closed = false | ||
|
||
return nil | ||
} | ||
|
||
func (c *ConPty) Resize(cols uint16, rows uint16) error { | ||
return resizePseudoConsole(c.hpCon, uintptr(cols)+(uintptr(rows)<<16)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
//go:build windows | ||
// +build windows | ||
|
||
// Copyright 2020 ActiveState Software. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file | ||
|
||
package conpty | ||
|
||
import ( | ||
"unsafe" | ||
|
||
"golang.org/x/sys/windows" | ||
) | ||
|
||
var ( | ||
kernel32 = windows.NewLazySystemDLL("kernel32.dll") | ||
procResizePseudoConsole = kernel32.NewProc("ResizePseudoConsole") | ||
procCreatePseudoConsole = kernel32.NewProc("CreatePseudoConsole") | ||
procClosePseudoConsole = kernel32.NewProc("ClosePseudoConsole") | ||
) | ||
|
||
func createPseudoConsole(consoleSize uintptr, ptyIn windows.Handle, ptyOut windows.Handle, hpCon *windows.Handle) (err error) { | ||
r1, _, e1 := procCreatePseudoConsole.Call( | ||
consoleSize, | ||
uintptr(ptyIn), | ||
uintptr(ptyOut), | ||
0, | ||
uintptr(unsafe.Pointer(hpCon)), | ||
) | ||
|
||
if r1 != 0 { // !S_OK | ||
err = e1 | ||
} | ||
return | ||
} | ||
|
||
func resizePseudoConsole(handle windows.Handle, consoleSize uintptr) (err error) { | ||
r1, _, e1 := procResizePseudoConsole.Call(uintptr(handle), consoleSize) | ||
if r1 != 0 { // !S_OK | ||
err = e1 | ||
} | ||
return | ||
} | ||
|
||
func closePseudoConsole(handle windows.Handle) (err error) { | ||
r1, _, e1 := procClosePseudoConsole.Call(uintptr(handle)) | ||
if r1 == 0 { | ||
err = e1 | ||
} | ||
|
||
return | ||
} |
Oops, something went wrong.