From 3b618f6042d5a0870fa9e4aac1b1bd9bcea708ae Mon Sep 17 00:00:00 2001 From: Jay Pipes Date: Wed, 26 Jun 2024 07:31:20 -0400 Subject: [PATCH] catch innermost spec context timeout In order to prevent the panic() that occurs if a context timeout cancel() function fires in the Go testing tool, we execute each test spec in its own goroutine and catch the individual cancel() function firing in order to properly output a failed assertion instead of a panic. Issue #37 Signed-off-by: Jay Pipes --- scenario/run.go | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/scenario/run.go b/scenario/run.go index a6e37fb..be256af 100644 --- a/scenario/run.go +++ b/scenario/run.go @@ -93,7 +93,19 @@ func (s *Scenario) Run(ctx context.Context, t *testing.T) error { defer cancel() } - res, rterr := s.runSpec(specCtx, rt, to, idx, spec) + var res *api.Result + ch := make(chan runSpecRes, 1) + + go s.runSpec(specCtx, ch, rt, to, idx, spec) + + select { + case <-specCtx.Done(): + t.Fatalf("assertion failed: timeout exceeded (%s)", to.After) + break + case runres := <-ch: + res = runres.r + rterr = runres.err + } if rterr != nil { break } @@ -115,14 +127,20 @@ func (s *Scenario) Run(ctx context.Context, t *testing.T) error { return rterr } +type runSpecRes struct { + r *api.Result + err error +} + // runSpec executes an individual test spec func (s *Scenario) runSpec( ctx context.Context, + ch chan runSpecRes, retry *api.Retry, timeout *api.Timeout, idx int, spec api.Evaluable, -) (*api.Result, error) { +) { sb := spec.Base() specTraceMsg := strconv.Itoa(idx) if sb.Name != "" { @@ -136,13 +154,15 @@ func (s *Scenario) runSpec( // Just evaluate the test spec once res, err := spec.Eval(ctx) if err != nil { - return nil, err + ch <- runSpecRes{nil, err} + return } debug.Println( ctx, "run: single-shot (no retries) ok: %v", !res.Failed(), ) - return res, nil + ch <- runSpecRes{res, nil} + return } // retry the action and test the assertions until they succeed, @@ -187,7 +207,8 @@ func (s *Scenario) runSpec( res, err = spec.Eval(ctx) if err != nil { - return nil, err + ch <- runSpecRes{nil, err} + return } success = !res.Failed() debug.Println( @@ -206,7 +227,7 @@ func (s *Scenario) runSpec( } attempts++ } - return res, nil + ch <- runSpecRes{res, nil} } // getTimeout returns the timeout configuration for the test spec. We check for