| // 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" | |
| "time" | |
| ) | |
| // 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, nil, func() { | |
| synctest.Test(t, func(t *testing.T) { | |
| t.Fatal("fatal") | |
| }) | |
| }, `^--- FAIL: TestFatal.* | |
| synctest_test.go:.* fatal | |
| FAIL | |
| $`) | |
| } | |
| func TestError(t *testing.T) { | |
| runTest(t, nil, func() { | |
| synctest.Test(t, func(t *testing.T) { | |
| t.Error("error") | |
| }) | |
| }, `^--- FAIL: TestError.* | |
| synctest_test.go:.* error | |
| FAIL | |
| $`) | |
| } | |
| func TestVerboseError(t *testing.T) { | |
| runTest(t, []string{"-test.v"}, func() { | |
| synctest.Test(t, func(t *testing.T) { | |
| t.Error("error") | |
| }) | |
| }, `^=== RUN TestVerboseError | |
| synctest_test.go:.* error | |
| --- FAIL: TestVerboseError.* | |
| FAIL | |
| $`) | |
| } | |
| func TestSkip(t *testing.T) { | |
| runTest(t, nil, func() { | |
| synctest.Test(t, func(t *testing.T) { | |
| t.Skip("skip") | |
| }) | |
| }, `^PASS | |
| $`) | |
| } | |
| func TestVerboseSkip(t *testing.T) { | |
| runTest(t, []string{"-test.v"}, func() { | |
| synctest.Test(t, func(t *testing.T) { | |
| t.Skip("skip") | |
| }) | |
| }, `^=== RUN TestVerboseSkip | |
| synctest_test.go:.* skip | |
| --- PASS: TestVerboseSkip.* | |
| 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 TestHelper(t *testing.T) { | |
| runTest(t, []string{"-test.v"}, func() { | |
| synctest.Test(t, func(t *testing.T) { | |
| helperLog(t, "log in helper") | |
| }) | |
| }, `^=== RUN TestHelper | |
| synctest_test.go:.* log in helper | |
| --- PASS: TestHelper.* | |
| PASS | |
| $`) | |
| } | |
| 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, args []string, 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=^"+regexp.QuoteMeta(t.Name())+"$", "-test.count=1") | |
| cmd.Args = append(cmd.Args, args...) | |
| 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) | |
| } | |
| } | |
| func TestNow(t *testing.T) { | |
| synctest.Test(t, func(t *testing.T) { | |
| if got, want := time.Now(), time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC); !got.Equal(want) { | |
| t.Errorf("time.Now() = %v, want %v", got, want) | |
| } | |
| }) | |
| } | |
| func TestSynctestTimerRaceCtxCrash(t *testing.T) { | |
| for range 20 { | |
| t.Run("", func(t *testing.T) { | |
| synctest.Test(t, func(t *testing.T) { | |
| for range 100 { | |
| time.AfterFunc(1*time.Second, func() { | |
| time.AfterFunc(0, func() {}) | |
| }) | |
| } | |
| time.Sleep(2 * time.Second) | |
| }) | |
| }) | |
| } | |
| } |