A Go linter that detects usage of context.Background()
and context.TODO()
in test functions and suggests using t.Context()
or b.Context()
instead. The linter detects problematic context usage in test functions, their subtests, and goroutines launched from tests.
Built using Go's analysis framework, testctxlint integrates seamlessly with existing Go tooling and provides automatic fixes for detected issues.
Go 1.24 introduced t.Context()
and b.Context()
methods for *testing.T
and *testing.B
respectively (see Go 1.24 changelog). These provide contexts that are automatically cancelled when the test finishes, making them more appropriate for test scenarios than context.Background()
or context.TODO()
.
go install github.com/icedream/testctxlint/cmd/testctxlint@latest
Download the appropriate binary for your platform from the releases page.
- Go 1.24 or later (required for
t.Context()
andb.Context()
methods)
Basic usage:
testctxlint ./...
Check a specific package:
testctxlint ./pkg/mypackage
Apply suggested fixes automatically:
testctxlint -fix ./...
Show fixes as a diff without applying them:
testctxlint -diff ./...
Get help:
testctxlint -help
When testctxlint finds issues, it provides clear messages and suggestions:
/path/to/file.go:15:9: call to context.Background from a test routine
/path/to/file.go:23:11: call to context.TODO from a test routine
With the -fix
flag, it can automatically apply the suggested fixes:
// Before
ctx := context.Background()
// After
ctx := t.Context()
package main
import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/singlechecker"
"github.com/icedream/testctxlint"
)
func main() {
// Use the analyzer directly
singlechecker.Main(testctxlint.Analyzer)
}
Or integrate with other analyzers:
package main
import (
"golang.org/x/tools/go/analysis/multichecker"
"github.com/icedream/testctxlint"
// ... other analyzers
)
func main() {
multichecker.Main(
testctxlint.Analyzer,
// ... other analyzers
)
}
func TestSomething(t *testing.T) {
// BAD: Using context.Background() in test
ctx := context.Background()
doSomething(ctx)
// BAD: Using context.TODO() in test
doSomethingElse(context.TODO())
// BAD: In subroutines
t.Run("subtest", func(t *testing.T) {
ctx := context.Background() // This will be flagged
doSomething(ctx)
})
// BAD: In goroutines
go func() {
ctx := context.TODO() // This will be flagged
doSomething(ctx)
}()
}
func BenchmarkSomething(b *testing.B) {
// BAD: Using context.Background() in benchmark
ctx := context.Background()
for i := 0; i < b.N; i++ {
doSomething(ctx)
}
}
func TestSomething(t *testing.T) {
// GOOD: Using t.Context() in test
ctx := t.Context()
doSomething(ctx)
// GOOD: Direct usage
doSomethingElse(t.Context())
// GOOD: In subroutines
t.Run("subtest", func(t *testing.T) {
ctx := t.Context() // Properly scoped test context
doSomething(ctx)
})
// GOOD: In goroutines (though be careful with goroutines in tests)
go func() {
ctx := t.Context() // Uses the test context
doSomething(ctx)
}()
}
func BenchmarkSomething(b *testing.B) {
// GOOD: Using b.Context() in benchmark
ctx := b.Context()
for i := 0; i < b.N; i++ {
doSomething(ctx)
}
}
Currently, testctxlint is not included in golangci-lint by default. You can run it separately or create a custom linter configuration.
Most Go IDEs that support the Go analysis framework should be able to use testctxlint. The exact integration method depends on your IDE.
# Install and run in CI
go install github.com/icedream/testctxlint/cmd/testctxlint@latest
testctxlint ./...
# Download and extract pre-built binary
curl -L -o testctxlint.tar.gz https://github.com/icedream/testctxlint/releases/latest/download/testctxlint_linux_x86_64.tar.gz
tar -xzf testctxlint.tar.gz
./testctxlint ./...
The project includes performance benchmarks that can be used to monitor performance regressions:
# Run benchmarks
go test -bench=. -benchmem
# Run benchmarks multiple times for statistical analysis
go test -bench=. -benchmem -count=5
# Compare benchmark results using benchstat
go install golang.org/x/perf/cmd/benchstat@latest
go test -bench=. -benchmem -count=5 | tee new.txt
# (make changes to code)
go test -bench=. -benchmem -count=5 | tee old.txt
benchstat old.txt new.txt
The repository includes a GitHub Actions workflow that automatically compares benchmark performance between the main branch and pull requests. When you open a PR:
- Benchmarks are run on both the main branch and your PR branch
- Performance differences are calculated using
benchstat
- Results are posted as a comment on the PR
- Benchmark data is stored as artifacts for historical analysis
The benchmark comparison helps identify performance regressions and improvements, providing metrics for:
- Execution time (ns/op): How long operations take
- Memory usage (B/op): Bytes allocated per operation
- Allocations (allocs/op): Number of memory allocations per operation
You can run the usual go test
commands to run the tests in this repository.
Additionally, we lint our code using golangci-lint v2.
Contributions are welcome! Please feel free to submit issues or pull requests.
See LICENSE.txt for details on this software's license.
This software incorporates material from third parties. For these portions, see NOTICE.txt for details.