diff --git a/util/testenv/testenv.go b/util/testenv/testenv.go index 12ada9003..3e23baef4 100644 --- a/util/testenv/testenv.go +++ b/util/testenv/testenv.go @@ -6,6 +6,7 @@ package testenv import ( + "context" "flag" "tailscale.com/types/lazy" @@ -19,3 +20,41 @@ func InTest() bool { return flag.Lookup("test.v") != nil }) } + +// TB is testing.TB, to avoid importing "testing" in non-test code. +type TB interface { + Cleanup(func()) + Error(args ...any) + Errorf(format string, args ...any) + Fail() + FailNow() + Failed() bool + Fatal(args ...any) + Fatalf(format string, args ...any) + Helper() + Log(args ...any) + Logf(format string, args ...any) + Name() string + Setenv(key, value string) + Chdir(dir string) + Skip(args ...any) + SkipNow() + Skipf(format string, args ...any) + Skipped() bool + TempDir() string + Context() context.Context +} + +// InParallelTest reports whether t is running as a parallel test. +// +// Use of this function taints t such that its Parallel method (assuming t is an +// actual *testing.T) will panic if called after this function. +func InParallelTest(t TB) (isParallel bool) { + defer func() { + if r := recover(); r != nil { + isParallel = true + } + }() + t.Chdir(".") // panics in a t.Parallel test + return false +} diff --git a/util/testenv/testenv_test.go b/util/testenv/testenv_test.go index 43c332b26..c647d9aec 100644 --- a/util/testenv/testenv_test.go +++ b/util/testenv/testenv_test.go @@ -16,3 +16,16 @@ func TestDeps(t *testing.T) { }, }.Check(t) } + +func TestInParallelTestTrue(t *testing.T) { + t.Parallel() + if !InParallelTest(t) { + t.Fatal("InParallelTest should return true once t.Parallel has been called") + } +} + +func TestInParallelTestFalse(t *testing.T) { + if InParallelTest(t) { + t.Fatal("InParallelTest should return false before t.Parallel has been called") + } +}