A Go static analysis tool that warns when contexts with a deadline are used in parallel subtests.
See our blog post for an in-depth explanation of the problem.
go run github.com/coder/paralleltestctx/cmd/paralleltestctx@latest ./...
By default, the linter detects context.WithTimeout
and context.WithDeadline
as producing contexts with timeouts or deadlines. Additional functions that
create a context with a deadline or timeout can be specified using the
-custom-funcs
flag.
go run github.com/coder/paralleltestctx/cmd/paralleltestctx@latest -custom-funcs="testutil.Context" ./...
func TestBad(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
// WARNING: do not do this!
t.Parallel()
uut := newUnitUnderTest()
// Danger! Context may have timed out by this point
writeCtx(ctx, t, uut.Input, 5)
output := readCtx(ctx, t, uut.Output)
if output != 25 {
t.Errorf("expected 25 got %d", output)
}
}
func TestBad(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), time.Second * 5)
t.Cleanup(cancel)
t.Run("sub", func(t *testing.T) {
t.Parallel()
// Danger! Context may have timed out by this point
doSomething(ctx) // Warning: timeout context used after t.Parallel call
})
}
func TestGood(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), time.Second * 5)
t.Cleanup(cancel)
t.Run("sub", func(t *testing.T) {
t.Parallel()
// We're shadowing the parent context with a new deadline, so this is fine
ctx, cancel := context.WithTimeout(context.Background(), time.Second * 5)
t.Cleanup(cancel)
doSomething(ctx)
})
}
This project is licensed under the MIT License - see the LICENSE file for details.