From 37d2bbcc407a8d2a4f5eb47a567dc19e2ffcb09b Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Thu, 14 Aug 2025 09:56:51 +0000 Subject: [PATCH 1/5] chore: add readme --- README.md | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..a5e1f9b --- /dev/null +++ b/README.md @@ -0,0 +1,60 @@ +# paralleltestctx + +A Go static analysis tool that warns when timeout contexts are used after `t.Parallel()` calls in tests. + +## Problem + +See [our blog post](https://coder.com/blog/go-testing-contexts-and-t-parallel) for an in-depth explanation of the problem. + +## Installation + +```bash +go install github.com/coder/paralleltestctx@latest +``` + +## Usage + +```bash +go run github.com/coder/paralleltestctx/cmd/paralleltestctx@latest ./... +``` + +### Custom functions that produce contexts with timeouts + +By default, detects `context.WithTimeout` and `context.WithDeadline`. +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(), 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) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second * 5) + t.Cleanup(cancel) + t.Run("sub", func(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithTimeout(context.Background(), time.Second * 5) + t.Cleanup(cancel) + doSomething(ctx) + }) +} +``` From 1816b29c10fb35f9ca718445dd7affd70f8dc97a Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Thu, 14 Aug 2025 10:10:45 +0000 Subject: [PATCH 2/5] wip --- README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.md b/README.md index a5e1f9b..eee8b6f 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,6 @@ A Go static analysis tool that warns when timeout contexts are used after `t.Par See [our blog post](https://coder.com/blog/go-testing-contexts-and-t-parallel) for an in-depth explanation of the problem. -## Installation - -```bash -go install github.com/coder/paralleltestctx@latest -``` - ## Usage ```bash From 5ab2b26840f57ffcc1e7502d125588f0e85bb387 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Thu, 14 Aug 2025 10:11:34 +0000 Subject: [PATCH 3/5] wip --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index eee8b6f..3a63d33 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,8 @@ go run github.com/coder/paralleltestctx/cmd/paralleltestctx@latest ./... ### Custom functions that produce contexts with timeouts -By default, detects `context.WithTimeout` and `context.WithDeadline`. +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. From 7387dd63a6ab3f8dc98382a4d7d59ac0ca11ac28 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Fri, 15 Aug 2025 02:03:30 +0000 Subject: [PATCH 4/5] review --- LICENSE | 20 ++++++++++++++++++++ README.md | 7 ++++++- 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 LICENSE 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 index 3a63d33..fe6b26e 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,11 @@ # paralleltestctx -A Go static analysis tool that warns when timeout contexts are used after `t.Parallel()` calls in tests. +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. +tl;dr - Always call `t.Parallel()` before `context.WithTimeout()` ## Usage @@ -53,3 +54,7 @@ func TestGood(t *testing.T) { }) } ``` + +## License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. From 2a09d10d822de4eec1ce6be2eb61f9627cdceef9 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Fri, 15 Aug 2025 05:12:57 +0000 Subject: [PATCH 5/5] review --- .prettierrc | 14 ++++++++++++++ README.md | 38 +++++++++++++++++++++++++++++++------- 2 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 .prettierrc 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/README.md b/README.md index fe6b26e..b964486 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ # paralleltestctx -A Go static analysis tool that warns when contexts with a deadline are used in parallel subtests. +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. -tl;dr - Always call `t.Parallel()` before `context.WithTimeout()` +See [our blog post](https://coder.com/blog/go-testing-contexts-and-t-parallel) +for an in-depth explanation of the problem. ## Usage @@ -16,9 +17,9 @@ 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. +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" ./... @@ -30,6 +31,26 @@ go run github.com/coder/paralleltestctx/cmd/paralleltestctx@latest -custom-funcs ```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) { @@ -44,10 +65,12 @@ func TestBad(t *testing.T) { ```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) @@ -57,4 +80,5 @@ func TestGood(t *testing.T) { ## License -This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file +for details.