Skip to content

Commit af40d39

Browse files
committed
Test output expectations should fail if the process has prematurely exited.
1 parent 13d903a commit af40d39

5 files changed

Lines changed: 49 additions & 12 deletions

File tree

expect.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ func (tt *TermTest) ExpectCustom(consumer consumer, opts ...SetExpectOpt) (rerr
8686
return fmt.Errorf("could not create expect options: %w", err)
8787
}
8888

89-
cons, err := tt.outputProducer.addConsumer(consumer, expectOpts.ToConsumerOpts()...)
89+
cons, err := tt.outputProducer.addConsumer(tt, consumer, expectOpts.ToConsumerOpts()...)
9090
if err != nil {
9191
return fmt.Errorf("could not add consumer: %w", err)
9292
}
@@ -180,11 +180,11 @@ func (tt *TermTest) expectExitCode(exitCode int, match bool, opts ...SetExpectOp
180180
select {
181181
case <-time.After(timeoutV):
182182
return fmt.Errorf("after %s: %w", timeoutV, TimeoutError)
183-
case err := <-waitChan(tt.cmd.Wait):
184-
if err != nil && (tt.cmd.ProcessState == nil || tt.cmd.ProcessState.ExitCode() == 0) {
185-
return fmt.Errorf("cmd wait failed: %w", err)
183+
case state := <-ttExited(tt, false):
184+
if state.Err != nil && (state.ProcessState == nil || state.ProcessState.ExitCode() == 0) {
185+
return fmt.Errorf("cmd wait failed: %w", state.Err)
186186
}
187-
if err := tt.assertExitCode(tt.cmd.ProcessState.ExitCode(), exitCode, match); err != nil {
187+
if err := tt.assertExitCode(state.ProcessState.ExitCode(), exitCode, match); err != nil {
188188
return err
189189
}
190190
}

helpers.go

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,49 @@ var neverGonnaHappen = time.Hour * 24 * 365 * 100
1818
var lineSepPosix = "\n"
1919
var lineSepWindows = "\r\n"
2020

21+
var processExitPollInterval = 10 * time.Millisecond
22+
var processExitExtraWait = 500 * time.Millisecond
23+
2124
type cmdExit struct {
2225
ProcessState *os.ProcessState
2326
Err error
2427
}
2528

2629
// waitForCmdExit turns process.wait() into a channel so that it can be used within a select{} statement
27-
func waitForCmdExit(cmd *exec.Cmd) chan cmdExit {
28-
exit := make(chan cmdExit, 1)
30+
func waitForCmdExit(cmd *exec.Cmd) chan *cmdExit {
31+
exit := make(chan *cmdExit, 1)
2932
go func() {
3033
err := cmd.Wait()
31-
exit <- cmdExit{ProcessState: cmd.ProcessState, Err: err}
34+
exit <- &cmdExit{ProcessState: cmd.ProcessState, Err: err}
3235
}()
3336
return exit
3437
}
3538

39+
// ttExited returns a channel that sends the given termtest's command cmdExit info when available.
40+
// This can be used within a select{} statement.
41+
// If waitExtra is given, waits a little bit before sending cmdExit info. This allows any fellow
42+
// switch cases to handle unprocessed stdout.
43+
func ttExited(tt *TermTest, waitExtra bool) chan *cmdExit {
44+
return waitChan(func() *cmdExit {
45+
ticker := time.NewTicker(processExitPollInterval)
46+
for {
47+
select {
48+
case <-ticker.C:
49+
if tt.exited != nil {
50+
if waitExtra {
51+
time.Sleep(processExitExtraWait)
52+
}
53+
return tt.exited
54+
}
55+
}
56+
}
57+
})
58+
}
59+
3660
func waitChan[T any](wait func() T) chan T {
3761
done := make(chan T)
3862
go func() {
39-
wait()
63+
done <- wait()
4064
close(done)
4165
}()
4266
return done

outputconsumer.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ type outputConsumer struct {
1515
opts *OutputConsumerOpts
1616
isalive bool
1717
mutex *sync.Mutex
18+
tt *TermTest
1819
}
1920

2021
type OutputConsumerOpts struct {
@@ -36,7 +37,7 @@ func OptsConsTimeout(timeout time.Duration) func(o *OutputConsumerOpts) {
3637
}
3738
}
3839

39-
func newOutputConsumer(consume consumer, opts ...SetConsOpt) *outputConsumer {
40+
func newOutputConsumer(tt *TermTest, consume consumer, opts ...SetConsOpt) *outputConsumer {
4041
oc := &outputConsumer{
4142
consume: consume,
4243
opts: &OutputConsumerOpts{
@@ -46,6 +47,7 @@ func newOutputConsumer(consume consumer, opts ...SetConsOpt) *outputConsumer {
4647
waiter: make(chan error, 1),
4748
isalive: true,
4849
mutex: &sync.Mutex{},
50+
tt: tt,
4951
}
5052

5153
for _, optSetter := range opts {
@@ -101,5 +103,11 @@ func (e *outputConsumer) wait() error {
101103
e.mutex.Lock()
102104
e.opts.Logger.Println("Encountered timeout")
103105
return fmt.Errorf("after %s: %w", e.opts.Timeout, TimeoutError)
106+
case state := <-ttExited(e.tt, true):
107+
e.mutex.Lock()
108+
if state.Err != nil {
109+
e.opts.Logger.Println("Encountered error waiting for process to exit: %s\n", state.Err.Error())
110+
}
111+
return fmt.Errorf("process exited (status: %d)", state.ProcessState.ExitCode())
104112
}
105113
}

outputproducer.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,12 +238,12 @@ func (o *outputProducer) flushConsumers() error {
238238
return nil
239239
}
240240

241-
func (o *outputProducer) addConsumer(consume consumer, opts ...SetConsOpt) (*outputConsumer, error) {
241+
func (o *outputProducer) addConsumer(tt *TermTest, consume consumer, opts ...SetConsOpt) (*outputConsumer, error) {
242242
o.opts.Logger.Printf("adding consumer")
243243
defer o.opts.Logger.Printf("added consumer")
244244

245245
opts = append(opts, OptConsInherit(o.opts))
246-
listener := newOutputConsumer(consume, opts...)
246+
listener := newOutputConsumer(tt, consume, opts...)
247247
o.consumers = append(o.consumers, listener)
248248

249249
if err := o.flushConsumers(); err != nil {

termtest.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type TermTest struct {
2424
outputProducer *outputProducer
2525
listenError chan error
2626
opts *Opts
27+
exited *cmdExit
2728
}
2829

2930
type ErrorHandler func(*TermTest, error) error
@@ -234,6 +235,10 @@ func (tt *TermTest) start() (rerr error) {
234235
}()
235236
wg.Wait()
236237

238+
go func() {
239+
tt.exited = <-waitForCmdExit(tt.cmd)
240+
}()
241+
237242
return nil
238243
}
239244

0 commit comments

Comments
 (0)