Skip to content

coder/paralleltestctx

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

paralleltestctx

A Go static analysis tool that warns when contexts with a deadline are used in parallel subtests.

Problem

See our blog post for an in-depth explanation of the problem.

Usage

go run github.com/coder/paralleltestctx/cmd/paralleltestctx@latest ./...

Custom functions that produce contexts with timeouts

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" ./...

Examples

❌ Outer-scope variable reassigned in a parallel subtest

Sibling parallel subtests can race on a variable declared in an enclosing scope. The fix is almost always to shadow with :=.

func TestBad(t *testing.T) {
   t.Parallel()
   var err error
   for _, tc := range cases {
      t.Run(tc.name, func(t *testing.T) {
         t.Parallel()
         // WARNING: `err` is shared across parallel subtests.
         _, err = doThing(tc)
         require.NoError(t, err)
      })
   }
}
func TestGood(t *testing.T) {
   t.Parallel()
   for _, tc := range cases {
      t.Run(tc.name, func(t *testing.T) {
         t.Parallel()
         _, err := doThing(tc) // shadowed
         require.NoError(t, err)
      })
   }
}

❌ Potentially flakey test

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)
   }
}

❌ Potentially flakey parallel subtest

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
    })
}

✅ Fixed test - create a new context

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)
    })
}

License

This project is licensed under the MIT License - see the LICENSE file for details.

About

Go linter for finding usages of contexts with timeouts in parallel subtests.

Resources

License

Code of conduct

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors