From 49a660e22cb349cf13ef0a2f6214c6fdd75afda0 Mon Sep 17 00:00:00 2001 From: Damien Neil Date: Mon, 12 May 2025 11:15:08 -0700 Subject: testing/synctest: add Test Add a synctest.Test function, superseding the experimental synctest.Run function. Promote the testing/synctest package out of experimental status. For #67434 For #73567 Change-Id: I3c5ba030860d90fe2ddb517a2f3536efd60181a9 Reviewed-on: https://go-review.googlesource.com/c/go/+/671961 Auto-Submit: Damien Neil LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Pratt --- src/testing/synctest/synctest_test.go | 149 ++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 src/testing/synctest/synctest_test.go (limited to 'src/testing/synctest/synctest_test.go') diff --git a/src/testing/synctest/synctest_test.go b/src/testing/synctest/synctest_test.go new file mode 100644 index 0000000000..4897df999e --- /dev/null +++ b/src/testing/synctest/synctest_test.go @@ -0,0 +1,149 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package synctest_test + +import ( + "fmt" + "internal/testenv" + "os" + "regexp" + "testing" + "testing/synctest" +) + +// Tests for interactions between synctest bubbles and the testing package. +// Other bubble behaviors are tested in internal/synctest. + +func TestSuccess(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + }) +} + +func TestFatal(t *testing.T) { + runTest(t, func() { + synctest.Test(t, func(t *testing.T) { + t.Fatal("fatal") + }) + }, `^=== RUN TestFatal + synctest_test.go:.* fatal +--- FAIL: TestFatal.* +FAIL +$`) +} + +func TestError(t *testing.T) { + runTest(t, func() { + synctest.Test(t, func(t *testing.T) { + t.Error("error") + }) + }, `^=== RUN TestError + synctest_test.go:.* error +--- FAIL: TestError.* +FAIL +$`) +} + +func TestSkip(t *testing.T) { + runTest(t, func() { + synctest.Test(t, func(t *testing.T) { + t.Skip("skip") + }) + }, `^=== RUN TestSkip + synctest_test.go:.* skip +--- PASS: TestSkip.* +PASS +$`) +} + +func TestCleanup(t *testing.T) { + done := false + synctest.Test(t, func(t *testing.T) { + ch := make(chan struct{}) + t.Cleanup(func() { + // This cleanup function should execute inside the test's bubble. + // (If it doesn't the runtime will panic.) + close(ch) + }) + // synctest.Test will wait for this goroutine to exit before returning. + // The cleanup function signals the goroutine to exit before the wait starts. + go func() { + <-ch + done = true + }() + }) + if !done { + t.Fatalf("background goroutine did not return") + } +} + +func TestContext(t *testing.T) { + state := "not started" + synctest.Test(t, func(t *testing.T) { + go func() { + state = "waiting on context" + <-t.Context().Done() + state = "done" + }() + // Wait blocks until the goroutine above is blocked on t.Context().Done(). + synctest.Wait() + if got, want := state, "waiting on context"; got != want { + t.Fatalf("state = %q, want %q", got, want) + } + }) + // t.Context() is canceled before the test completes, + // and synctest.Test does not return until the goroutine has set its state to "done". + if got, want := state, "done"; got != want { + t.Fatalf("state = %q, want %q", got, want) + } +} + +func TestDeadline(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + defer wantPanic(t, "testing: t.Deadline called inside synctest bubble") + _, _ = t.Deadline() + }) +} + +func TestParallel(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + defer wantPanic(t, "testing: t.Parallel called inside synctest bubble") + t.Parallel() + }) +} + +func TestRun(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + defer wantPanic(t, "testing: t.Run called inside synctest bubble") + t.Run("subtest", func(t *testing.T) { + }) + }) +} + +func wantPanic(t *testing.T, want string) { + if e := recover(); e != nil { + if got := fmt.Sprint(e); got != want { + t.Errorf("got panic message %q, want %q", got, want) + } + } else { + t.Errorf("got no panic, want one") + } +} + +func runTest(t *testing.T, f func(), pattern string) { + if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" { + f() + return + } + t.Helper() + re := regexp.MustCompile(pattern) + testenv.MustHaveExec(t) + cmd := testenv.Command(t, testenv.Executable(t), "-test.run=^"+t.Name()+"$", "-test.v", "-test.count=1") + cmd = testenv.CleanCmdEnv(cmd) + cmd.Env = append(cmd.Env, "GO_WANT_HELPER_PROCESS=1") + out, _ := cmd.CombinedOutput() + if !re.Match(out) { + t.Errorf("got output:\n%s\nwant matching:\n%s", out, pattern) + } +} -- cgit v1.3