diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..edb389b --- /dev/null +++ b/.prettierrc @@ -0,0 +1,14 @@ +{ + "printWidth": 120, + "semi": false, + "trailingComma": "all", + "overrides": [ + { + "files": ["./*.md", "./**/*.md"], + "options": { + "printWidth": 80, + "proseWrap": "always" + } + } + ] +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8bb9ce0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +The MIT License + +Copyright (c) 2025 Coder Technologies Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..b964486 --- /dev/null +++ b/README.md @@ -0,0 +1,84 @@ +# paralleltestctx + +A Go static analysis tool that warns when contexts with a deadline are used in +parallel subtests. + +## Problem + +See [our blog post](https://coder.com/blog/go-testing-contexts-and-t-parallel) +for an in-depth explanation of the problem. + +## Usage + +```bash +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. + +```bash +go run github.com/coder/paralleltestctx/cmd/paralleltestctx@latest -custom-funcs="testutil.Context" ./... +``` + +## Examples + +### ❌ Potentially flakey test + +```go +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 + +```go +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 + +```go +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](LICENSE) file +for details.