From 4cff4c59fb1f4ce42f4b2d9a30be84a964b5f481 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 | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/scenario/run.go b/scenario/run.go index a6e37fb..94b9542 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( @@ -200,13 +221,13 @@ func (s *Scenario) runSpec( } for _, f := range res.Failures() { debug.Println( - ctx, "run: attempt %d after %s failure: %s", - attempts, after, f, + ctx, "run: attempt %d failure: %s", + attempts, f, ) } attempts++ } - return res, nil + ch <- runSpecRes{res, nil} } // getTimeout returns the timeout configuration for the test spec. We check for