--- /dev/null
+pkg testing/synctest, func Test(*testing.T, func(*testing.T)) #67434
+pkg testing/synctest, func Wait() #67434
--- /dev/null
+### New testing/synctest package
+
+The new [testing/synctest](/pkg/testing/synctest) package
+provides support for testing concurrent code.
+
+The [synctest.Test] function runs a test function in an isolated
+"bubble". Within the bubble, [time](/pkg/time) package functions
+operate on a fake clock.
+
+The [synctest.Wait] function waits for all goroutines in the
+current bubble to block.
--- /dev/null
+<!-- testing/synctest -->
unicode !< path;
RUNTIME
- < internal/synctest
- < testing/synctest;
+ < internal/synctest;
# SYSCALL is RUNTIME plus the packages necessary for basic system calls.
RUNTIME, unicode/utf8, unicode/utf16, internal/synctest
FMT
< internal/txtar;
+ internal/synctest, testing
+ < testing/synctest;
+
testing
< internal/testhash;
+++ /dev/null
-// 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.
-
-//go:build goexperiment.synctest
-
-package synctest_test
-
-import (
- "context"
- "fmt"
- "testing/synctest"
- "time"
-)
-
-// This example demonstrates testing the context.AfterFunc function.
-//
-// AfterFunc registers a function to execute in a new goroutine
-// after a context is canceled.
-//
-// The test verifies that the function is not run before the context is canceled,
-// and is run after the context is canceled.
-func Example_contextAfterFunc() {
- synctest.Run(func() {
- // Create a context.Context which can be canceled.
- ctx, cancel := context.WithCancel(context.Background())
-
- // context.AfterFunc registers a function to be called
- // when a context is canceled.
- afterFuncCalled := false
- context.AfterFunc(ctx, func() {
- afterFuncCalled = true
- })
-
- // The context has not been canceled, so the AfterFunc is not called.
- synctest.Wait()
- fmt.Printf("before context is canceled: afterFuncCalled=%v\n", afterFuncCalled)
-
- // Cancel the context and wait for the AfterFunc to finish executing.
- // Verify that the AfterFunc ran.
- cancel()
- synctest.Wait()
- fmt.Printf("after context is canceled: afterFuncCalled=%v\n", afterFuncCalled)
-
- // Output:
- // before context is canceled: afterFuncCalled=false
- // after context is canceled: afterFuncCalled=true
- })
-}
-
-// This example demonstrates testing the context.WithTimeout function.
-//
-// WithTimeout creates a context which is canceled after a timeout.
-//
-// The test verifies that the context is not canceled before the timeout expires,
-// and is canceled after the timeout expires.
-func Example_contextWithTimeout() {
- synctest.Run(func() {
- // Create a context.Context which is canceled after a timeout.
- const timeout = 5 * time.Second
- ctx, cancel := context.WithTimeout(context.Background(), timeout)
- defer cancel()
-
- // Wait just less than the timeout.
- time.Sleep(timeout - time.Nanosecond)
- synctest.Wait()
- fmt.Printf("before timeout: ctx.Err() = %v\n", ctx.Err())
-
- // Wait the rest of the way until the timeout.
- time.Sleep(time.Nanosecond)
- synctest.Wait()
- fmt.Printf("after timeout: ctx.Err() = %v\n", ctx.Err())
-
- // Output:
- // before timeout: ctx.Err() = <nil>
- // after timeout: ctx.Err() = context deadline exceeded
- })
-}
--- /dev/null
+// Copyright 2024 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 (
+ "bufio"
+ "bytes"
+ "context"
+ "io"
+ "net"
+ "net/http"
+ "strings"
+ "testing"
+ "testing/synctest"
+ "time"
+)
+
+// Keep the following tests in sync with the package documentation.
+
+func TestTime(t *testing.T) {
+ synctest.Test(t, func(t *testing.T) {
+ start := time.Now() // always midnight UTC 2001-01-01
+ go func() {
+ time.Sleep(1 * time.Nanosecond)
+ t.Log(time.Since(start)) // always logs "1ns"
+ }()
+ time.Sleep(2 * time.Nanosecond) // the AfterFunc will run before this Sleep returns
+ t.Log(time.Since(start)) // always logs "2ns"
+ })
+}
+
+func TestWait(t *testing.T) {
+ synctest.Test(t, func(t *testing.T) {
+ done := false
+ go func() {
+ done = true
+ }()
+ // Wait will block until the goroutine above has finished.
+ synctest.Wait()
+ t.Log(done) // always logs "true"
+ })
+}
+
+func TestContextAfterFunc(t *testing.T) {
+ synctest.Test(t, func(t *testing.T) {
+ // Create a context.Context which can be canceled.
+ ctx, cancel := context.WithCancel(t.Context())
+
+ // context.AfterFunc registers a function to be called
+ // when a context is canceled.
+ afterFuncCalled := false
+ context.AfterFunc(ctx, func() {
+ afterFuncCalled = true
+ })
+
+ // The context has not been canceled, so the AfterFunc is not called.
+ synctest.Wait()
+ if afterFuncCalled {
+ t.Fatalf("before context is canceled: AfterFunc called")
+ }
+
+ // Cancel the context and wait for the AfterFunc to finish executing.
+ // Verify that the AfterFunc ran.
+ cancel()
+ synctest.Wait()
+ if !afterFuncCalled {
+ t.Fatalf("before context is canceled: AfterFunc not called")
+ }
+ })
+}
+
+func TestContextWithTimeout(t *testing.T) {
+ synctest.Test(t, func(t *testing.T) {
+ // Create a context.Context which is canceled after a timeout.
+ const timeout = 5 * time.Second
+ ctx, cancel := context.WithTimeout(t.Context(), timeout)
+ defer cancel()
+
+ // Wait just less than the timeout.
+ time.Sleep(timeout - time.Nanosecond)
+ synctest.Wait()
+ if err := ctx.Err(); err != nil {
+ t.Fatalf("before timeout: ctx.Err() = %v, want nil\n", err)
+ }
+
+ // Wait the rest of the way until the timeout.
+ time.Sleep(time.Nanosecond)
+ synctest.Wait()
+ if err := ctx.Err(); err != context.DeadlineExceeded {
+ t.Fatalf("after timeout: ctx.Err() = %v, want DeadlineExceeded\n", err)
+ }
+ })
+}
+
+func TestHTTPTransport100Continue(t *testing.T) {
+ synctest.Test(t, func(*testing.T) {
+ // Create an in-process fake network connection.
+ // We cannot use a loopback network connection for this test,
+ // because goroutines blocked on network I/O prevent a synctest
+ // bubble from becoming idle.
+ srvConn, cliConn := net.Pipe()
+ defer cliConn.Close()
+ defer srvConn.Close()
+
+ tr := &http.Transport{
+ // Use the fake network connection created above.
+ DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
+ return cliConn, nil
+ },
+ // Enable "Expect: 100-continue" handling.
+ ExpectContinueTimeout: 5 * time.Second,
+ }
+
+ // Send a request with the "Expect: 100-continue" header set.
+ // Send it in a new goroutine, since it won't complete until the end of the test.
+ body := "request body"
+ go func() {
+ req, _ := http.NewRequest("PUT", "http://test.tld/", strings.NewReader(body))
+ req.Header.Set("Expect", "100-continue")
+ resp, err := tr.RoundTrip(req)
+ if err != nil {
+ t.Errorf("RoundTrip: unexpected error %v\n", err)
+ } else {
+ resp.Body.Close()
+ }
+ }()
+
+ // Read the request headers sent by the client.
+ req, err := http.ReadRequest(bufio.NewReader(srvConn))
+ if err != nil {
+ t.Fatalf("ReadRequest: %v\n", err)
+ }
+
+ // Start a new goroutine copying the body sent by the client into a buffer.
+ // Wait for all goroutines in the bubble to block and verify that we haven't
+ // read anything from the client yet.
+ var gotBody bytes.Buffer
+ go io.Copy(&gotBody, req.Body)
+ synctest.Wait()
+ if got, want := gotBody.String(), ""; got != want {
+ t.Fatalf("before sending 100 Continue, read body: %q, want %q\n", got, want)
+ }
+
+ // Write a "100 Continue" response to the client and verify that
+ // it sends the request body.
+ srvConn.Write([]byte("HTTP/1.1 100 Continue\r\n\r\n"))
+ synctest.Wait()
+ if got, want := gotBody.String(), body; got != want {
+ t.Fatalf("after sending 100 Continue, read body: %q, want %q\n", got, want)
+ }
+
+ // Finish up by sending the "200 OK" response to conclude the request.
+ srvConn.Write([]byte("HTTP/1.1 200 OK\r\n\r\n"))
+
+ // We started several goroutines during the test.
+ // The synctest.Test call will wait for all of them to exit before returning.
+ })
+}
+++ /dev/null
-// 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.
-
-//go:build goexperiment.synctest
-
-package synctest_test
-
-import (
- "bufio"
- "bytes"
- "context"
- "fmt"
- "io"
- "net"
- "net/http"
- "strings"
- "testing/synctest"
- "time"
-)
-
-// This example demonstrates testing [http.Transport]'s 100 Continue handling.
-//
-// An HTTP client sending a request can include an "Expect: 100-continue" header
-// to tell the server that the client has additional data to send.
-// The server may then respond with an 100 Continue information response
-// to request the data, or some other status to tell the client the data is not needed.
-// For example, a client uploading a large file might use this feature to confirm
-// that the server is willing to accept the file before sending it.
-//
-// This test confirms that when sending an "Expect: 100-continue" header
-// the HTTP client does not send a request's content before the server requests it,
-// and that it does send the content after receiving a 100 Continue response.
-func Example_httpTransport100Continue() {
- synctest.Run(func() {
- // Create an in-process fake network connection.
- // We cannot use a loopback network connection for this test,
- // because goroutines blocked on network I/O prevent a synctest
- // bubble from becoming idle.
- srvConn, cliConn := net.Pipe()
- defer cliConn.Close()
- defer srvConn.Close()
-
- tr := &http.Transport{
- // Use the fake network connection created above.
- DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
- return cliConn, nil
- },
- // Enable "Expect: 100-continue" handling.
- ExpectContinueTimeout: 5 * time.Second,
- }
-
- // Send a request with the "Expect: 100-continue" header set.
- // Send it in a new goroutine, since it won't complete until the end of the test.
- body := "request body"
- go func() {
- req, err := http.NewRequest("PUT", "http://test.tld/", strings.NewReader(body))
- if err != nil {
- panic(err)
- }
- req.Header.Set("Expect", "100-continue")
- resp, err := tr.RoundTrip(req)
- if err != nil {
- fmt.Printf("RoundTrip: unexpected error %v\n", err)
- } else {
- resp.Body.Close()
- }
- }()
-
- // Read the request headers sent by the client.
- req, err := http.ReadRequest(bufio.NewReader(srvConn))
- if err != nil {
- fmt.Printf("ReadRequest: %v\n", err)
- return
- }
-
- // Start a new goroutine copying the body sent by the client into a buffer.
- // Wait for all goroutines in the bubble to block and verify that we haven't
- // read anything from the client yet.
- var gotBody bytes.Buffer
- go io.Copy(&gotBody, req.Body)
- synctest.Wait()
- fmt.Printf("before sending 100 Continue, read body: %q\n", gotBody.String())
-
- // Write a "100 Continue" response to the client and verify that
- // it sends the request body.
- srvConn.Write([]byte("HTTP/1.1 100 Continue\r\n\r\n"))
- synctest.Wait()
- fmt.Printf("after sending 100 Continue, read body: %q\n", gotBody.String())
-
- // Finish up by sending the "200 OK" response to conclude the request.
- srvConn.Write([]byte("HTTP/1.1 200 OK\r\n\r\n"))
-
- // We started several goroutines during the test.
- // The synctest.Run call will wait for all of them to exit before returning.
- })
-
- // Output:
- // before sending 100 Continue, read body: ""
- // after sending 100 Continue, read body: "request body"
-}
--- /dev/null
+// 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.
+
+//go:build goexperiment.synctest
+
+package synctest
+
+import "internal/synctest"
+
+// Run is deprecated.
+//
+// Deprecated: Use Test instead.
+func Run(f func()) {
+ synctest.Run(f)
+}
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-//go:build goexperiment.synctest
-
// Package synctest provides support for testing concurrent code.
//
-// This package only exists when using Go compiled with GOEXPERIMENT=synctest.
-// It is experimental, and not subject to the Go 1 compatibility promise.
+// The [Test] function runs a function in an isolated "bubble".
+// Any goroutines started within the bubble are also part of the bubble.
+//
+// # Time
+//
+// Within a bubble, the [time] package uses a fake clock.
+// Each bubble has its own clock.
+// The initial time is midnight UTC 2000-01-01.
+//
+// Time in a bubble only advances when every goroutine in the
+// bubble is durably blocked.
+// See below for the exact definition of "durably blocked".
+//
+// For example, this test runs immediately rather than taking
+// two seconds:
+//
+// func TestTime(t *testing.T) {
+// synctest.Test(t, func(t *testing.T) {
+// start := time.Now() // always midnight UTC 2001-01-01
+// go func() {
+// time.Sleep(1 * time.Nanosecond)
+// t.Log(time.Since(start)) // always logs "1ns"
+// }()
+// time.Sleep(2 * time.Nanosecond) // the goroutine above will run before this Sleep returns
+// t.Log(time.Since(start)) // always logs "2ns"
+// })
+// }
+//
+// Time stops advancing when the root goroutine of the bubble exits.
+//
+// # Blocking
+//
+// A goroutine in a bubble is "durably blocked" when it is blocked
+// and can only be unblocked by another goroutine in the same bubble.
+// A goroutine which can be unblocked by an event from outside its
+// bubble is not durably blocked.
+//
+// The [Wait] function blocks until all other goroutines in the
+// bubble are durably blocked.
+//
+// For example:
+//
+// func TestWait(t *testing.T) {
+// synctest.Test(t, func(t *testing.T) {
+// done := false
+// go func() {
+// done = true
+// }()
+// // Wait will block until the goroutine above has finished.
+// synctest.Wait()
+// t.Log(done) // always logs "true"
+// })
+// }
+//
+// When every goroutine in a bubble is durably blocked:
+//
+// - [Wait] returns, if it has been called.
+// - Otherwise, time advances to the next time that will
+// unblock at least one goroutine, if there is such a time
+// and the root goroutine of the bubble has not exited.
+// - Otherwise, there is a deadlock and [Test] panics.
+//
+// The following operations durably block a goroutine:
+//
+// - a blocking send or receive on a channel created within the bubble
+// - a blocking select statement where every case is a channel created
+// within the bubble
+// - [sync.Cond.Wait]
+// - [sync.WaitGroup.Wait]
+// - [time.Sleep]
+//
+// Locking a [sync.Mutex] or [sync.RWMutex] is not durably blocking.
+//
+// # Isolation
+//
+// A channel, [time.Timer], or [time.Ticker] created within a bubble
+// is associated with it. Operating on a bubbled channel, timer, or
+// ticker from outside the bubble panics.
+//
+// # Example: Context.AfterFunc
+//
+// This example demonstrates testing the [context.AfterFunc] function.
+//
+// AfterFunc registers a function to execute in a new goroutine
+// after a context is canceled.
+//
+// The test verifies that the function is not run before the context is canceled,
+// and is run after the context is canceled.
+//
+// func TestContextAfterFunc(t *testing.T) {
+// synctest.Test(t, func(t *testing.T) {
+// // Create a context.Context which can be canceled.
+// ctx, cancel := context.WithCancel(t.Context())
+//
+// // context.AfterFunc registers a function to be called
+// // when a context is canceled.
+// afterFuncCalled := false
+// context.AfterFunc(ctx, func() {
+// afterFuncCalled = true
+// })
+//
+// // The context has not been canceled, so the AfterFunc is not called.
+// synctest.Wait()
+// if afterFuncCalled {
+// t.Fatalf("before context is canceled: AfterFunc called")
+// }
+//
+// // Cancel the context and wait for the AfterFunc to finish executing.
+// // Verify that the AfterFunc ran.
+// cancel()
+// synctest.Wait()
+// if !afterFuncCalled {
+// t.Fatalf("before context is canceled: AfterFunc not called")
+// }
+// })
+// }
+//
+// # Example: Context.WithTimeout
+//
+// This example demonstrates testing the [context.WithTimeout] function.
+//
+// WithTimeout creates a context which is canceled after a timeout.
+//
+// The test verifies that the context is not canceled before the timeout expires,
+// and is canceled after the timeout expires.
+//
+// func TestContextWithTimeout(t *testing.T) {
+// synctest.Test(t, func(t *testing.T) {
+// // Create a context.Context which is canceled after a timeout.
+// const timeout = 5 * time.Second
+// ctx, cancel := context.WithTimeout(t.Context(), timeout)
+// defer cancel()
+//
+// // Wait just less than the timeout.
+// time.Sleep(timeout - time.Nanosecond)
+// synctest.Wait()
+// if err := ctx.Err(); err != nil {
+// t.Fatalf("before timeout: ctx.Err() = %v, want nil\n", err)
+// }
+//
+// // Wait the rest of the way until the timeout.
+// time.Sleep(time.Nanosecond)
+// synctest.Wait()
+// if err := ctx.Err(); err != context.DeadlineExceeded {
+// t.Fatalf("after timeout: ctx.Err() = %v, want DeadlineExceeded\n", err)
+// }
+// })
+// }
+//
+// # Example: HTTP 100 Continue
+//
+// This example demonstrates testing [http.Transport]'s 100 Continue handling.
+//
+// An HTTP client sending a request can include an "Expect: 100-continue" header
+// to tell the server that the client has additional data to send.
+// The server may then respond with an 100 Continue information response
+// to request the data, or some other status to tell the client the data is not needed.
+// For example, a client uploading a large file might use this feature to confirm
+// that the server is willing to accept the file before sending it.
+//
+// This test confirms that when sending an "Expect: 100-continue" header
+// the HTTP client does not send a request's content before the server requests it,
+// and that it does send the content after receiving a 100 Continue response.
+//
+// func TestHTTPTransport100Continue(t *testing.T) {
+// synctest.Test(t, func(*testing.T) {
+// // Create an in-process fake network connection.
+// // We cannot use a loopback network connection for this test,
+// // because goroutines blocked on network I/O prevent a synctest
+// // bubble from becoming idle.
+// srvConn, cliConn := net.Pipe()
+// defer cliConn.Close()
+// defer srvConn.Close()
+//
+// tr := &http.Transport{
+// // Use the fake network connection created above.
+// DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
+// return cliConn, nil
+// },
+// // Enable "Expect: 100-continue" handling.
+// ExpectContinueTimeout: 5 * time.Second,
+// }
+//
+// // Send a request with the "Expect: 100-continue" header set.
+// // Send it in a new goroutine, since it won't complete until the end of the test.
+// body := "request body"
+// go func() {
+// req, _ := http.NewRequest("PUT", "http://test.tld/", strings.NewReader(body))
+// req.Header.Set("Expect", "100-continue")
+// resp, err := tr.RoundTrip(req)
+// if err != nil {
+// t.Errorf("RoundTrip: unexpected error %v\n", err)
+// } else {
+// resp.Body.Close()
+// }
+// }()
+//
+// // Read the request headers sent by the client.
+// req, err := http.ReadRequest(bufio.NewReader(srvConn))
+// if err != nil {
+// t.Fatalf("ReadRequest: %v\n", err)
+// }
+//
+// // Start a new goroutine copying the body sent by the client into a buffer.
+// // Wait for all goroutines in the bubble to block and verify that we haven't
+// // read anything from the client yet.
+// var gotBody bytes.Buffer
+// go io.Copy(&gotBody, req.Body)
+// synctest.Wait()
+// if got, want := gotBody.String(), ""; got != want {
+// t.Fatalf("before sending 100 Continue, read body: %q, want %q\n", got, want)
+// }
+//
+// // Write a "100 Continue" response to the client and verify that
+// // it sends the request body.
+// srvConn.Write([]byte("HTTP/1.1 100 Continue\r\n\r\n"))
+// synctest.Wait()
+// if got, want := gotBody.String(), body; got != want {
+// t.Fatalf("after sending 100 Continue, read body: %q, want %q\n", got, want)
+// }
+//
+// // Finish up by sending the "200 OK" response to conclude the request.
+// srvConn.Write([]byte("HTTP/1.1 200 OK\r\n\r\n"))
+//
+// // We started several goroutines during the test.
+// // The synctest.Test call will wait for all of them to exit before returning.
+// })
+// }
package synctest
import (
"internal/synctest"
+ "testing"
+ _ "unsafe" // for linkname
)
-// Run executes f in a new goroutine.
-//
-// The new goroutine and any goroutines transitively started by it form
-// an isolated "bubble".
-// Run waits for all goroutines in the bubble to exit before returning.
-//
-// Goroutines in the bubble use a synthetic time implementation.
-// The initial time is midnight UTC 2000-01-01.
+// Test executes f in a new bubble.
//
-// Time advances when every goroutine in the bubble is blocked.
-// For example, a call to time.Sleep will block until all other
-// goroutines are blocked and return after the bubble's clock has
-// advanced. See [Wait] for the specific definition of blocked.
+// Test waits for all goroutines in the bubble to exit before returning.
+// If the goroutines in the bubble become deadlocked, the test fails.
//
-// Time stops advancing when f returns.
+// Test must not be called from within a bubble.
//
-// If every goroutine is blocked and either
-// no timers are scheduled or f has returned,
-// Run panics.
+// The [*testing.T] provided to f has the following properties:
//
-// Channels, time.Timers, and time.Tickers created within the bubble
-// are associated with it. Operating on a bubbled channel, timer, or ticker
-// from outside the bubble panics.
-func Run(f func()) {
- synctest.Run(f)
+// - T.Cleanup functions run inside the bubble,
+// immediately before Test returns.
+// - T.Context returns a [context.Context] with a Done channel
+// associated with the bubble.
+// - T.Run, T.Parallel, and T.Deadline must not be called.
+func Test(t *testing.T, f func(*testing.T)) {
+ synctest.Run(func() {
+ testingSynctestTest(t, f)
+ })
}
+//go:linkname testingSynctestTest testing/synctest.testingSynctestTest
+func testingSynctestTest(t *testing.T, f func(*testing.T))
+
// Wait blocks until every goroutine within the current bubble,
// other than the current goroutine, is durably blocked.
-// It panics if called from a non-bubbled goroutine,
-// or if two goroutines in the same bubble call Wait at the same time.
-//
-// A goroutine is durably blocked if can only be unblocked by another
-// goroutine in its bubble. The following operations durably block
-// a goroutine:
-// - a send or receive on a channel from within the bubble
-// - a select statement where every case is a channel within the bubble
-// - sync.Cond.Wait
-// - time.Sleep
-//
-// A goroutine executing a system call or waiting for an external event
-// such as a network operation is not durably blocked.
-// For example, a goroutine blocked reading from an network connection
-// is not durably blocked even if no data is currently available on the
-// connection, because it may be unblocked by data written from outside
-// the bubble or may be in the process of receiving data from a kernel
-// network buffer.
-//
-// A goroutine is not durably blocked when blocked on a send or receive
-// on a channel that was not created within its bubble, because it may
-// be unblocked by a channel receive or send from outside its bubble.
+//
+// Wait must not be called from outside a bubble.
+// Wait must not be called concurrently by multiple goroutines
+// in the same bubble.
func Wait() {
synctest.Wait()
}
--- /dev/null
+// 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)
+ }
+}
"time"
"unicode"
"unicode/utf8"
+ _ "unsafe" // for linkname
)
var initRan bool
cleanupPc []uintptr // The stack trace at the point where Cleanup was called.
finished bool // Test function has completed.
inFuzzFn bool // Whether the fuzz target, if this is one, is running.
+ isSynctest bool
chatty *chattyPrinter // A copy of chattyPrinter, if the chatty flag is set.
bench bool // Whether the current test is a benchmark.
if t.isParallel {
panic("testing: t.Parallel called multiple times")
}
+ if t.isSynctest {
+ panic("testing: t.Parallel called inside synctest bubble")
+ }
if t.denyParallel {
panic(parallelConflict)
}
// Run may be called simultaneously from multiple goroutines, but all such calls
// must return before the outer test function for t returns.
func (t *T) Run(name string, f func(t *T)) bool {
+ if t.isSynctest {
+ panic("testing: t.Run called inside synctest bubble")
+ }
if t.cleanupStarted.Load() {
panic("testing: t.Run called during t.Cleanup")
}
return !t.failed
}
+// testingSynctestTest runs f within a synctest bubble.
+// It is called by synctest.Test, from within an already-created bubble.
+//
+//go:linkname testingSynctestTest testing/synctest.testingSynctestTest
+func testingSynctestTest(t *T, f func(*T)) {
+ if t.cleanupStarted.Load() {
+ panic("testing: synctest.Run called during t.Cleanup")
+ }
+
+ var pc [maxStackLen]uintptr
+ n := runtime.Callers(2, pc[:])
+
+ ctx, cancelCtx := context.WithCancel(context.Background())
+ t2 := &T{
+ common: common{
+ barrier: make(chan bool),
+ signal: make(chan bool, 1),
+ name: t.name,
+ parent: &t.common,
+ level: t.level + 1,
+ creator: pc[:n],
+ chatty: t.chatty,
+ ctx: ctx,
+ cancelCtx: cancelCtx,
+ isSynctest: true,
+ },
+ tstate: t.tstate,
+ }
+ t2.setOutputWriter()
+
+ go tRunner(t2, f)
+ if !<-t2.signal {
+ // At this point, it is likely that FailNow was called on one of the
+ // parent tests by one of the subtests. Continue aborting up the chain.
+ runtime.Goexit()
+ }
+}
+
// Deadline reports the time at which the test binary will have
// exceeded the timeout specified by the -timeout flag.
//
// The ok result is false if the -timeout flag indicates “no timeout” (0).
func (t *T) Deadline() (deadline time.Time, ok bool) {
+ if t.isSynctest {
+ // There's no point in returning a real-clock deadline to
+ // a test using a fake clock. We could return "no timeout",
+ // but panicking makes it easier for users to catch the error.
+ panic("testing: t.Deadline called inside synctest bubble")
+ }
deadline = t.tstate.deadline
return deadline, !deadline.IsZero()
}
if t.parent == nil {
return
}
+ if t.isSynctest {
+ return // t.parent will handle reporting
+ }
dstr := fmtDuration(t.duration)
format := "--- %s: %s (%s)\n"
if t.Failed() {