refactor: Rename 'expect' package to 'console' (#297)

Fixes #291 - renames the `expect` go package to `console`, and changes the api from `expect.NewTestConsole` to `console.New`, and a few other small changes to support the linter (ie, `ConsoleOpts` -> `Opts`)
This commit is contained in:
Bryan
2022-02-15 16:47:33 -08:00
committed by GitHub
parent 35291d358a
commit 6009c90d1d
16 changed files with 71 additions and 70 deletions

View File

@ -3,11 +3,12 @@ package clitest_test
import ( import (
"testing" "testing"
"github.com/coder/coder/cli/clitest"
"github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/expect"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.uber.org/goleak" "go.uber.org/goleak"
"github.com/coder/coder/cli/clitest"
"github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/console"
) )
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
@ -20,11 +21,11 @@ func TestCli(t *testing.T) {
client := coderdtest.New(t) client := coderdtest.New(t)
cmd, config := clitest.New(t) cmd, config := clitest.New(t)
clitest.SetupConfig(t, client, config) clitest.SetupConfig(t, client, config)
console := expect.NewTestConsole(t, cmd) cons := console.New(t, cmd)
go func() { go func() {
err := cmd.Execute() err := cmd.Execute()
require.NoError(t, err) require.NoError(t, err)
}() }()
_, err := console.ExpectString("coder") _, err := cons.ExpectString("coder")
require.NoError(t, err) require.NoError(t, err)
} }

View File

@ -4,8 +4,8 @@ import (
"testing" "testing"
"github.com/coder/coder/cli/clitest" "github.com/coder/coder/cli/clitest"
"github.com/coder/coder/expect"
"github.com/coder/coder/coderd/coderdtest" "github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/console"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -26,7 +26,7 @@ func TestLogin(t *testing.T) {
// accurately detect Windows ptys when they are not attached to a process: // accurately detect Windows ptys when they are not attached to a process:
// https://github.com/mattn/go-isatty/issues/59 // https://github.com/mattn/go-isatty/issues/59
root, _ := clitest.New(t, "login", client.URL.String(), "--force-tty") root, _ := clitest.New(t, "login", client.URL.String(), "--force-tty")
console := expect.NewTestConsole(t, root) cons := console.New(t, root)
go func() { go func() {
err := root.Execute() err := root.Execute()
require.NoError(t, err) require.NoError(t, err)
@ -42,12 +42,12 @@ func TestLogin(t *testing.T) {
for i := 0; i < len(matches); i += 2 { for i := 0; i < len(matches); i += 2 {
match := matches[i] match := matches[i]
value := matches[i+1] value := matches[i+1]
_, err := console.ExpectString(match) _, err := cons.ExpectString(match)
require.NoError(t, err) require.NoError(t, err)
_, err = console.SendLine(value) _, err = cons.SendLine(value)
require.NoError(t, err) require.NoError(t, err)
} }
_, err := console.ExpectString("Welcome to Coder") _, err := cons.ExpectString("Welcome to Coder")
require.NoError(t, err) require.NoError(t, err)
}) })
} }

View File

@ -7,8 +7,8 @@ import (
"github.com/coder/coder/cli/clitest" "github.com/coder/coder/cli/clitest"
"github.com/coder/coder/coderd/coderdtest" "github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/console"
"github.com/coder/coder/database" "github.com/coder/coder/database"
"github.com/coder/coder/expect"
"github.com/coder/coder/provisioner/echo" "github.com/coder/coder/provisioner/echo"
"github.com/coder/coder/provisionersdk/proto" "github.com/coder/coder/provisionersdk/proto"
) )
@ -26,7 +26,7 @@ func TestProjectCreate(t *testing.T) {
cmd, root := clitest.New(t, "projects", "create", "--directory", source, "--provisioner", string(database.ProvisionerTypeEcho)) cmd, root := clitest.New(t, "projects", "create", "--directory", source, "--provisioner", string(database.ProvisionerTypeEcho))
clitest.SetupConfig(t, client, root) clitest.SetupConfig(t, client, root)
_ = coderdtest.NewProvisionerDaemon(t, client) _ = coderdtest.NewProvisionerDaemon(t, client)
console := expect.NewTestConsole(t, cmd) console := console.New(t, cmd)
closeChan := make(chan struct{}) closeChan := make(chan struct{})
go func() { go func() {
err := cmd.Execute() err := cmd.Execute()
@ -73,7 +73,7 @@ func TestProjectCreate(t *testing.T) {
cmd, root := clitest.New(t, "projects", "create", "--directory", source, "--provisioner", string(database.ProvisionerTypeEcho)) cmd, root := clitest.New(t, "projects", "create", "--directory", source, "--provisioner", string(database.ProvisionerTypeEcho))
clitest.SetupConfig(t, client, root) clitest.SetupConfig(t, client, root)
coderdtest.NewProvisionerDaemon(t, client) coderdtest.NewProvisionerDaemon(t, client)
console := expect.NewTestConsole(t, cmd) cons := console.New(t, cmd)
closeChan := make(chan struct{}) closeChan := make(chan struct{})
go func() { go func() {
err := cmd.Execute() err := cmd.Execute()
@ -91,9 +91,9 @@ func TestProjectCreate(t *testing.T) {
for i := 0; i < len(matches); i += 2 { for i := 0; i < len(matches); i += 2 {
match := matches[i] match := matches[i]
value := matches[i+1] value := matches[i+1]
_, err := console.ExpectString(match) _, err := cons.ExpectString(match)
require.NoError(t, err) require.NoError(t, err)
_, err = console.SendLine(value) _, err = cons.SendLine(value)
require.NoError(t, err) require.NoError(t, err)
} }
<-closeChan <-closeChan

View File

@ -5,7 +5,7 @@ import (
"github.com/coder/coder/cli/clitest" "github.com/coder/coder/cli/clitest"
"github.com/coder/coder/coderd/coderdtest" "github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/expect" "github.com/coder/coder/console"
"github.com/coder/coder/provisioner/echo" "github.com/coder/coder/provisioner/echo"
"github.com/coder/coder/provisionersdk/proto" "github.com/coder/coder/provisionersdk/proto"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -36,7 +36,7 @@ func TestWorkspaceCreate(t *testing.T) {
cmd, root := clitest.New(t, "workspaces", "create", project.Name) cmd, root := clitest.New(t, "workspaces", "create", project.Name)
clitest.SetupConfig(t, client, root) clitest.SetupConfig(t, client, root)
console := expect.NewTestConsole(t, cmd) cons := console.New(t, cmd)
closeChan := make(chan struct{}) closeChan := make(chan struct{})
go func() { go func() {
err := cmd.Execute() err := cmd.Execute()
@ -51,12 +51,12 @@ func TestWorkspaceCreate(t *testing.T) {
for i := 0; i < len(matches); i += 2 { for i := 0; i < len(matches); i += 2 {
match := matches[i] match := matches[i]
value := matches[i+1] value := matches[i+1]
_, err := console.ExpectString(match) _, err := cons.ExpectString(match)
require.NoError(t, err) require.NoError(t, err)
_, err = console.SendLine(value) _, err = cons.SendLine(value)
require.NoError(t, err) require.NoError(t, err)
} }
_, err := console.ExpectString("Create") _, err := cons.ExpectString("Create")
require.NoError(t, err) require.NoError(t, err)
<-closeChan <-closeChan
}) })

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package expect package console
import ( import (
"bufio" "bufio"
@ -23,7 +23,7 @@ import (
"os" "os"
"unicode/utf8" "unicode/utf8"
"github.com/coder/coder/expect/pty" "github.com/coder/coder/console/pty"
) )
// Console is an interface to automate input and output for interactive // Console is an interface to automate input and output for interactive
@ -31,17 +31,17 @@ import (
// input back on it's tty. Console can also multiplex other sources of input // input back on it's tty. Console can also multiplex other sources of input
// and multiplex its output to other writers. // and multiplex its output to other writers.
type Console struct { type Console struct {
opts ConsoleOpts opts Opts
pty pty.Pty pty pty.Pty
runeReader *bufio.Reader runeReader *bufio.Reader
closers []io.Closer closers []io.Closer
} }
// ConsoleOpt allows setting Console options. // Opt allows setting Console options.
type ConsoleOpt func(*ConsoleOpts) error type Opt func(*Opts) error
// ConsoleOpts provides additional options on creating a Console. // Opts provides additional options on creating a Console.
type ConsoleOpts struct { type Opts struct {
Logger *log.Logger Logger *log.Logger
Stdouts []io.Writer Stdouts []io.Writer
ExpectObservers []Observer ExpectObservers []Observer
@ -62,8 +62,8 @@ type Observer func(matchers []Matcher, buf string, err error)
// last writer, writing to it's internal buffer for matching expects. // last writer, writing to it's internal buffer for matching expects.
// If a listed writer returns an error, that overall write operation stops and // If a listed writer returns an error, that overall write operation stops and
// returns the error; it does not continue down the list. // returns the error; it does not continue down the list.
func WithStdout(writers ...io.Writer) ConsoleOpt { func WithStdout(writers ...io.Writer) Opt {
return func(opts *ConsoleOpts) error { return func(opts *Opts) error {
opts.Stdouts = append(opts.Stdouts, writers...) opts.Stdouts = append(opts.Stdouts, writers...)
return nil return nil
} }
@ -71,24 +71,24 @@ func WithStdout(writers ...io.Writer) ConsoleOpt {
// WithLogger adds a logger for Console to log debugging information to. By // WithLogger adds a logger for Console to log debugging information to. By
// default Console will discard logs. // default Console will discard logs.
func WithLogger(logger *log.Logger) ConsoleOpt { func WithLogger(logger *log.Logger) Opt {
return func(opts *ConsoleOpts) error { return func(opts *Opts) error {
opts.Logger = logger opts.Logger = logger
return nil return nil
} }
} }
// WithExpectObserver adds an ExpectObserver to allow monitoring Expect operations. // WithExpectObserver adds an ExpectObserver to allow monitoring Expect operations.
func WithExpectObserver(observers ...Observer) ConsoleOpt { func WithExpectObserver(observers ...Observer) Opt {
return func(opts *ConsoleOpts) error { return func(opts *Opts) error {
opts.ExpectObservers = append(opts.ExpectObservers, observers...) opts.ExpectObservers = append(opts.ExpectObservers, observers...)
return nil return nil
} }
} }
// NewConsole returns a new Console with the given options. // NewConsole returns a new Console with the given options.
func NewConsole(opts ...ConsoleOpt) (*Console, error) { func NewConsole(opts ...Opt) (*Console, error) {
options := ConsoleOpts{ options := Opts{
Logger: log.New(ioutil.Discard, "", 0), Logger: log.New(ioutil.Discard, "", 0),
} }
@ -105,14 +105,14 @@ func NewConsole(opts ...ConsoleOpt) (*Console, error) {
closers := []io.Closer{consolePty} closers := []io.Closer{consolePty}
reader := consolePty.Reader() reader := consolePty.Reader()
console := &Console{ cons := &Console{
opts: options, opts: options,
pty: consolePty, pty: consolePty,
runeReader: bufio.NewReaderSize(reader, utf8.UTFMax), runeReader: bufio.NewReaderSize(reader, utf8.UTFMax),
closers: closers, closers: closers,
} }
return console, nil return cons, nil
} }
// Tty returns an input Tty for accepting input // Tty returns an input Tty for accepting input

View File

@ -16,4 +16,4 @@
// applications. It is unlike expect in that it does not spawn or manage // applications. It is unlike expect in that it does not spawn or manage
// process lifecycle. This package only focuses on expecting output and sending // process lifecycle. This package only focuses on expecting output and sending
// input through it's psuedoterminal. // input through it's psuedoterminal.
package expect package console

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package expect package console
import ( import (
"bufio" "bufio"
@ -40,8 +40,8 @@ func (c *Console) ExpectString(s string) (string, error) {
// expecting input yet, it will be blocked. Sends are queued up in tty's // expecting input yet, it will be blocked. Sends are queued up in tty's
// internal buffer so that the next Expect will read the remaining bytes (i.e. // internal buffer so that the next Expect will read the remaining bytes (i.e.
// rest of prompt) as well as its conditions. // rest of prompt) as well as its conditions.
func (c *Console) Expect(opts ...Opt) (string, error) { func (c *Console) Expect(opts ...ExpectOpt) (string, error) {
var options Opts var options ExpectOpts
for _, opt := range opts { for _, opt := range opts {
if err := opt(&options); err != nil { if err := opt(&options); err != nil {
return "", err return "", err

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package expect package console
import ( import (
"bytes" "bytes"
@ -20,22 +20,22 @@ import (
"time" "time"
) )
// Opt allows settings Expect options. // ExpectOpt allows settings Expect options.
type Opt func(*Opts) error type ExpectOpt func(*ExpectOpts) error
// ConsoleCallback is a callback function to execute if a match is found for // Callback is a callback function to execute if a match is found for
// the chained matcher. // the chained matcher.
type ConsoleCallback func(buf *bytes.Buffer) error type Callback func(buf *bytes.Buffer) error
// Opts provides additional options on Expect. // ExpectOpts provides additional options on Expect.
type Opts struct { type ExpectOpts struct {
Matchers []Matcher Matchers []Matcher
ReadTimeout *time.Duration ReadTimeout *time.Duration
} }
// Match sequentially calls Match on all matchers in ExpectOpts and returns the // Match sequentially calls Match on all matchers in ExpectOpts and returns the
// first matcher if a match exists, otherwise nil. // first matcher if a match exists, otherwise nil.
func (eo Opts) Match(v interface{}) Matcher { func (eo ExpectOpts) Match(v interface{}) Matcher {
for _, matcher := range eo.Matchers { for _, matcher := range eo.Matchers {
if matcher.Match(v) { if matcher.Match(v) {
return matcher return matcher
@ -83,7 +83,7 @@ func (sm *stringMatcher) Criteria() interface{} {
// allMatcher fulfills the Matcher interface to match a group of ExpectOpt // allMatcher fulfills the Matcher interface to match a group of ExpectOpt
// against any value. // against any value.
type allMatcher struct { type allMatcher struct {
options Opts options ExpectOpts
} }
func (am *allMatcher) Match(v interface{}) bool { func (am *allMatcher) Match(v interface{}) bool {
@ -109,9 +109,9 @@ func (am *allMatcher) Criteria() interface{} {
// All adds an Expect condition to exit if the content read from Console's tty // All adds an Expect condition to exit if the content read from Console's tty
// matches all of the provided ExpectOpt, in any order. // matches all of the provided ExpectOpt, in any order.
func All(expectOpts ...Opt) Opt { func All(expectOpts ...ExpectOpt) ExpectOpt {
return func(opts *Opts) error { return func(opts *ExpectOpts) error {
var options Opts var options ExpectOpts
for _, opt := range expectOpts { for _, opt := range expectOpts {
if err := opt(&options); err != nil { if err := opt(&options); err != nil {
return err return err
@ -127,8 +127,8 @@ func All(expectOpts ...Opt) Opt {
// String adds an Expect condition to exit if the content read from Console's // String adds an Expect condition to exit if the content read from Console's
// tty contains any of the given strings. // tty contains any of the given strings.
func String(strs ...string) Opt { func String(strs ...string) ExpectOpt {
return func(opts *Opts) error { return func(opts *ExpectOpts) error {
for _, str := range strs { for _, str := range strs {
opts.Matchers = append(opts.Matchers, &stringMatcher{ opts.Matchers = append(opts.Matchers, &stringMatcher{
str: str, str: str,

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package expect_test package console_test
import ( import (
"bytes" "bytes"
@ -20,7 +20,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
. "github.com/coder/coder/expect" . "github.com/coder/coder/console"
) )
func TestExpectOptString(t *testing.T) { func TestExpectOptString(t *testing.T) {
@ -28,7 +28,7 @@ func TestExpectOptString(t *testing.T) {
tests := []struct { tests := []struct {
title string title string
opt Opt opt ExpectOpt
data string data string
expected bool expected bool
}{ }{
@ -63,7 +63,7 @@ func TestExpectOptString(t *testing.T) {
t.Run(test.title, func(t *testing.T) { t.Run(test.title, func(t *testing.T) {
t.Parallel() t.Parallel()
var options Opts var options ExpectOpts
err := test.opt(&options) err := test.opt(&options)
require.Nil(t, err) require.Nil(t, err)
@ -86,7 +86,7 @@ func TestExpectOptAll(t *testing.T) {
tests := []struct { tests := []struct {
title string title string
opt Opt opt ExpectOpt
data string data string
expected bool expected bool
}{ }{
@ -144,7 +144,7 @@ func TestExpectOptAll(t *testing.T) {
test := test test := test
t.Run(test.title, func(t *testing.T) { t.Run(test.title, func(t *testing.T) {
t.Parallel() t.Parallel()
var options Opts var options ExpectOpts
err := test.opt(&options) err := test.opt(&options)
require.Nil(t, err) require.Nil(t, err)

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package expect_test package console_test
import ( import (
"bufio" "bufio"
@ -26,7 +26,7 @@ import (
"golang.org/x/xerrors" "golang.org/x/xerrors"
. "github.com/coder/coder/expect" . "github.com/coder/coder/console"
) )
var ( var (
@ -71,14 +71,14 @@ func Prompt(in io.Reader, out io.Writer) error {
return nil return nil
} }
func newTestConsole(t *testing.T, opts ...ConsoleOpt) (*Console, error) { func newTestConsole(t *testing.T, opts ...Opt) (*Console, error) {
opts = append([]ConsoleOpt{ opts = append([]Opt{
expectNoError(t), expectNoError(t),
}, opts...) }, opts...)
return NewConsole(opts...) return NewConsole(opts...)
} }
func expectNoError(t *testing.T) ConsoleOpt { func expectNoError(t *testing.T) Opt {
return WithExpectObserver( return WithExpectObserver(
func(matchers []Matcher, buf string, err error) { func(matchers []Matcher, buf string, err error) {
if err == nil { if err == nil {

View File

@ -9,7 +9,7 @@ import (
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
"github.com/coder/coder/expect/conpty" "github.com/coder/coder/console/conpty"
) )
func newPty() (Pty, error) { func newPty() (Pty, error) {

View File

@ -1,4 +1,4 @@
package expect package console
import ( import (
"bufio" "bufio"
@ -16,9 +16,9 @@ var (
stripAnsi = regexp.MustCompile("[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))") stripAnsi = regexp.MustCompile("[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))")
) )
// NewTestConsole creates a new TTY bound to the command provided. // New creates a new TTY bound to the command provided.
// All ANSI escape codes are stripped to provide clean output. // All ANSI escape codes are stripped to provide clean output.
func NewTestConsole(t *testing.T, cmd *cobra.Command) *Console { func New(t *testing.T, cmd *cobra.Command) *Console {
reader, writer := io.Pipe() reader, writer := io.Pipe()
scanner := bufio.NewScanner(reader) scanner := bufio.NewScanner(reader)
t.Cleanup(func() { t.Cleanup(func() {