diff --git a/go.mod b/go.mod index 5f6c87e513..f080e8b435 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ replace cloud.google.com/go => cloud.google.com/go v0.110.0 require ( github.com/99designs/gqlgen v0.17.19 github.com/ActiveState/go-ogle-analytics v0.0.0-20170510030904-9b3f14901527 - github.com/ActiveState/termtest v0.7.3-0.20231006191111-13d903a6f2de + github.com/ActiveState/termtest v0.7.3-0.20240522153407-fcd066736664 github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 github.com/alecthomas/participle/v2 v2.0.0 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 diff --git a/go.sum b/go.sum index 32285a27ea..595893613f 100644 --- a/go.sum +++ b/go.sum @@ -349,6 +349,10 @@ github.com/ActiveState/pty v0.0.0-20230628221854-6fb90eb08a14 h1:RdhhSiwmgyUaaF2 github.com/ActiveState/pty v0.0.0-20230628221854-6fb90eb08a14/go.mod h1:5mM6vNRQwshCjlkOnVpwC//4ZpkiC6nmZr8lPOxJdXs= github.com/ActiveState/termtest v0.7.3-0.20231006191111-13d903a6f2de h1:KqSmWBIEQracF6ixlQ0eNeKN+wYxDHNJWbrShI6F8NE= github.com/ActiveState/termtest v0.7.3-0.20231006191111-13d903a6f2de/go.mod h1:RyWp2NaaTrVAa+XjMHpKAqwBFWbL6wE12HQxiZNGAqU= +github.com/ActiveState/termtest v0.7.3-0.20240516194214-af40d395ed40 h1:YDXHYDjYNJRlH1Qr2PD/NnYIR38PhhRATcxlgwcQjpY= +github.com/ActiveState/termtest v0.7.3-0.20240516194214-af40d395ed40/go.mod h1:RyWp2NaaTrVAa+XjMHpKAqwBFWbL6wE12HQxiZNGAqU= +github.com/ActiveState/termtest v0.7.3-0.20240522153407-fcd066736664 h1:0W+6cvjhkhF7AtCQJDGooMpOhDF4wqZpZyO2loIqtgY= +github.com/ActiveState/termtest v0.7.3-0.20240522153407-fcd066736664/go.mod h1:RyWp2NaaTrVAa+XjMHpKAqwBFWbL6wE12HQxiZNGAqU= github.com/AlecAivazis/survey/v2 v2.0.5/go.mod h1:WYBhg6f0y/fNYUuesWQc0PKbJcEliGcYHB9sNT3Bg74= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= diff --git a/vendor/github.com/ActiveState/termtest/expect.go b/vendor/github.com/ActiveState/termtest/expect.go index d340777a80..ee26707432 100644 --- a/vendor/github.com/ActiveState/termtest/expect.go +++ b/vendor/github.com/ActiveState/termtest/expect.go @@ -86,7 +86,7 @@ func (tt *TermTest) ExpectCustom(consumer consumer, opts ...SetExpectOpt) (rerr return fmt.Errorf("could not create expect options: %w", err) } - cons, err := tt.outputProducer.addConsumer(consumer, expectOpts.ToConsumerOpts()...) + cons, err := tt.outputProducer.addConsumer(tt, consumer, expectOpts.ToConsumerOpts()...) if err != nil { return fmt.Errorf("could not add consumer: %w", err) } @@ -180,11 +180,11 @@ func (tt *TermTest) expectExitCode(exitCode int, match bool, opts ...SetExpectOp select { case <-time.After(timeoutV): return fmt.Errorf("after %s: %w", timeoutV, TimeoutError) - case err := <-waitChan(tt.cmd.Wait): - if err != nil && (tt.cmd.ProcessState == nil || tt.cmd.ProcessState.ExitCode() == 0) { - return fmt.Errorf("cmd wait failed: %w", err) + case state := <-tt.Exited(false): // do not wait for unread output since it's not read by this select{} + if state.Err != nil && (state.ProcessState == nil || state.ProcessState.ExitCode() == 0) { + return fmt.Errorf("cmd wait failed: %w", state.Err) } - if err := tt.assertExitCode(tt.cmd.ProcessState.ExitCode(), exitCode, match); err != nil { + if err := tt.assertExitCode(state.ProcessState.ExitCode(), exitCode, match); err != nil { return err } } diff --git a/vendor/github.com/ActiveState/termtest/helpers.go b/vendor/github.com/ActiveState/termtest/helpers.go index 3144ad2b1a..9092a7577f 100644 --- a/vendor/github.com/ActiveState/termtest/helpers.go +++ b/vendor/github.com/ActiveState/termtest/helpers.go @@ -24,11 +24,11 @@ type cmdExit struct { } // waitForCmdExit turns process.wait() into a channel so that it can be used within a select{} statement -func waitForCmdExit(cmd *exec.Cmd) chan cmdExit { - exit := make(chan cmdExit, 1) +func waitForCmdExit(cmd *exec.Cmd) chan *cmdExit { + exit := make(chan *cmdExit, 1) go func() { err := cmd.Wait() - exit <- cmdExit{ProcessState: cmd.ProcessState, Err: err} + exit <- &cmdExit{ProcessState: cmd.ProcessState, Err: err} }() return exit } @@ -36,7 +36,7 @@ func waitForCmdExit(cmd *exec.Cmd) chan cmdExit { func waitChan[T any](wait func() T) chan T { done := make(chan T) go func() { - wait() + done <- wait() close(done) }() return done diff --git a/vendor/github.com/ActiveState/termtest/outputconsumer.go b/vendor/github.com/ActiveState/termtest/outputconsumer.go index 1665e4b1a6..5e51c26a4d 100644 --- a/vendor/github.com/ActiveState/termtest/outputconsumer.go +++ b/vendor/github.com/ActiveState/termtest/outputconsumer.go @@ -15,6 +15,7 @@ type outputConsumer struct { opts *OutputConsumerOpts isalive bool mutex *sync.Mutex + tt *TermTest } type OutputConsumerOpts struct { @@ -36,7 +37,7 @@ func OptsConsTimeout(timeout time.Duration) func(o *OutputConsumerOpts) { } } -func newOutputConsumer(consume consumer, opts ...SetConsOpt) *outputConsumer { +func newOutputConsumer(tt *TermTest, consume consumer, opts ...SetConsOpt) *outputConsumer { oc := &outputConsumer{ consume: consume, opts: &OutputConsumerOpts{ @@ -46,6 +47,7 @@ func newOutputConsumer(consume consumer, opts ...SetConsOpt) *outputConsumer { waiter: make(chan error, 1), isalive: true, mutex: &sync.Mutex{}, + tt: tt, } for _, optSetter := range opts { @@ -101,5 +103,11 @@ func (e *outputConsumer) wait() error { e.mutex.Lock() e.opts.Logger.Println("Encountered timeout") return fmt.Errorf("after %s: %w", e.opts.Timeout, TimeoutError) + case state := <-e.tt.Exited(true): // allow for output to be read first by first case in this select{} + e.mutex.Lock() + if state.Err != nil { + e.opts.Logger.Println("Encountered error waiting for process to exit: %s\n", state.Err.Error()) + } + return fmt.Errorf("process exited (status: %d)", state.ProcessState.ExitCode()) } } diff --git a/vendor/github.com/ActiveState/termtest/outputproducer.go b/vendor/github.com/ActiveState/termtest/outputproducer.go index 34ae967d60..8c68b7ae6e 100644 --- a/vendor/github.com/ActiveState/termtest/outputproducer.go +++ b/vendor/github.com/ActiveState/termtest/outputproducer.go @@ -238,12 +238,12 @@ func (o *outputProducer) flushConsumers() error { return nil } -func (o *outputProducer) addConsumer(consume consumer, opts ...SetConsOpt) (*outputConsumer, error) { +func (o *outputProducer) addConsumer(tt *TermTest, consume consumer, opts ...SetConsOpt) (*outputConsumer, error) { o.opts.Logger.Printf("adding consumer") defer o.opts.Logger.Printf("added consumer") opts = append(opts, OptConsInherit(o.opts)) - listener := newOutputConsumer(consume, opts...) + listener := newOutputConsumer(tt, consume, opts...) o.consumers = append(o.consumers, listener) if err := o.flushConsumers(); err != nil { diff --git a/vendor/github.com/ActiveState/termtest/termtest.go b/vendor/github.com/ActiveState/termtest/termtest.go index 7677bcebdc..d363ad0ff4 100644 --- a/vendor/github.com/ActiveState/termtest/termtest.go +++ b/vendor/github.com/ActiveState/termtest/termtest.go @@ -24,6 +24,7 @@ type TermTest struct { outputProducer *outputProducer listenError chan error opts *Opts + exited *cmdExit } type ErrorHandler func(*TermTest, error) error @@ -50,6 +51,9 @@ type SetOpt func(o *Opts) error const DefaultCols = 140 const DefaultRows = 10 +var processExitPollInterval = 10 * time.Millisecond +var processExitExtraWait = 500 * time.Millisecond + func NewOpts() *Opts { return &Opts{ Logger: VoidLogger, @@ -234,6 +238,10 @@ func (tt *TermTest) start() (rerr error) { }() wg.Wait() + go func() { + tt.exited = <-waitForCmdExit(tt.cmd) + }() + return nil } @@ -316,6 +324,28 @@ func (tt *TermTest) SendCtrlC() { tt.Send(string([]byte{0x03})) // 0x03 is ASCII character for ^C } +// Exited returns a channel that sends the given termtest's command cmdExit info when available. +// This can be used within a select{} statement. +// If waitExtra is given, waits a little bit before sending cmdExit info. This allows any fellow +// switch cases with output consumers to handle unprocessed stdout. If there are no such cases +// (e.g. ExpectExit(), where we want to catch an exit ASAP), waitExtra should be false. +func (tt *TermTest) Exited(waitExtra bool) chan *cmdExit { + return waitChan(func() *cmdExit { + ticker := time.NewTicker(processExitPollInterval) + for { + select { + case <-ticker.C: + if tt.exited != nil { + if waitExtra { // allow sibling output consumer cases to handle their output + time.Sleep(processExitExtraWait) + } + return tt.exited + } + } + } + }) +} + func (tt *TermTest) errorHandler(rerr *error) { err := *rerr if err == nil { diff --git a/vendor/modules.txt b/vendor/modules.txt index ed261c9bf7..4a59038ccf 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -22,7 +22,7 @@ github.com/ActiveState/graphql # github.com/ActiveState/pty v0.0.0-20230628221854-6fb90eb08a14 ## explicit; go 1.13 github.com/ActiveState/pty -# github.com/ActiveState/termtest v0.7.3-0.20231006191111-13d903a6f2de +# github.com/ActiveState/termtest v0.7.3-0.20240522153407-fcd066736664 ## explicit; go 1.18 github.com/ActiveState/termtest # github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78