// 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 provides support for testing concurrent code. // // 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.Second) // t.Log(time.Since(start)) // always logs "1s" // }() // time.Sleep(2 * time.Second) // the goroutine above will run before this Sleep returns // t.Log(time.Since(start)) // always logs "2s" // }) // } // // 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], when [sync.WaitGroup.Add] was called within the bubble // - [time.Sleep] // // Operations not in the above list are not durably blocking. // In particular, the following operations may block a goroutine, // but are not durably blocking because the goroutine can be unblocked // by an event occurring outside its bubble: // // - locking a [sync.Mutex] or [sync.RWMutex] // - blocking on I/O, such as reading from a network socket // - system calls // // # 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. // // A [sync.WaitGroup] becomes associated with a bubble on the first // call to Add or Go. Once a WaitGroup is associated with a bubble, // calling Add or Go from outside that bubble is a fatal error. // (As a technical limitation, a WaitGroup defined as a package // variable, such as "var wg sync.WaitGroup", cannot be associated // with a bubble and operations on it may not be durably blocking. // This limitation does not apply to a *WaitGroup stored in a // package variable, such as "var wg = new(sync.WaitGroup)".) // // [sync.Cond.Wait] is durably blocking. Waking a goroutine in a bubble // blocked on Cond.Wait from outside the bubble is a fatal error. // // Cleanup functions and finalizers registered with // [runtime.AddCleanup] and [runtime.SetFinalizer] // run outside of any bubble. // // # 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 ( _ // for linkname ) // Test executes f in a new bubble. // // Test waits for all goroutines in the bubble to exit before returning. // If the goroutines in the bubble become deadlocked, the test fails. // // Test must not be called from within a bubble. // // The [*testing.T] provided to f has the following properties: // // - 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 ( *testing.T, func(*testing.T)) { var bool synctest.Run(func() { = testingSynctestTest(, ) }) if ! { // Fail the test outside the bubble, // so test durations get set using real time. .FailNow() } } //go:linkname testingSynctestTest testing/synctest.testingSynctestTest func testingSynctestTest( *testing.T, func(*testing.T)) bool // Wait blocks until every goroutine within the current bubble, // other than the current goroutine, is durably blocked. // // Wait must not be called from outside a bubble. // Wait must not be called concurrently by multiple goroutines // in the same bubble. func () { synctest.Wait() }