Source file src/testing/synctest/synctest.go
1 // Copyright 2024 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package synctest provides support for testing concurrent code. 6 // 7 // The [Test] function runs a function in an isolated "bubble". 8 // Any goroutines started within the bubble are also part of the bubble. 9 // 10 // # Time 11 // 12 // Within a bubble, the [time] package uses a fake clock. 13 // Each bubble has its own clock. 14 // The initial time is midnight UTC 2000-01-01. 15 // 16 // Time in a bubble only advances when every goroutine in the 17 // bubble is durably blocked. 18 // See below for the exact definition of "durably blocked". 19 // 20 // For example, this test runs immediately rather than taking 21 // two seconds: 22 // 23 // func TestTime(t *testing.T) { 24 // synctest.Test(t, func(t *testing.T) { 25 // start := time.Now() // always midnight UTC 2000-01-01 26 // go func() { 27 // time.Sleep(1 * time.Second) 28 // t.Log(time.Since(start)) // always logs "1s" 29 // }() 30 // time.Sleep(2 * time.Second) // the goroutine above will run before this Sleep returns 31 // t.Log(time.Since(start)) // always logs "2s" 32 // }) 33 // } 34 // 35 // Time stops advancing when the root goroutine of the bubble exits. 36 // 37 // # Blocking 38 // 39 // A goroutine in a bubble is "durably blocked" when it is blocked 40 // and can only be unblocked by another goroutine in the same bubble. 41 // A goroutine which can be unblocked by an event from outside its 42 // bubble is not durably blocked. 43 // 44 // The [Wait] function blocks until all other goroutines in the 45 // bubble are durably blocked. 46 // 47 // For example: 48 // 49 // func TestWait(t *testing.T) { 50 // synctest.Test(t, func(t *testing.T) { 51 // done := false 52 // go func() { 53 // done = true 54 // }() 55 // // Wait will block until the goroutine above has finished. 56 // synctest.Wait() 57 // t.Log(done) // always logs "true" 58 // }) 59 // } 60 // 61 // When every goroutine in a bubble is durably blocked: 62 // 63 // - [Wait] returns, if it has been called. 64 // - Otherwise, time advances to the next time that will 65 // unblock at least one goroutine, if there is such a time 66 // and the root goroutine of the bubble has not exited. 67 // - Otherwise, there is a deadlock and [Test] panics. 68 // 69 // The following operations durably block a goroutine: 70 // 71 // - a blocking send or receive on a channel created within the bubble 72 // - a blocking select statement where every case is a channel created 73 // within the bubble 74 // - [sync.Cond.Wait] 75 // - [sync.WaitGroup.Wait], when [sync.WaitGroup.Add] was called within the bubble 76 // - [time.Sleep] 77 // 78 // Operations not in the above list are not durably blocking. 79 // In particular, the following operations may block a goroutine, 80 // but are not durably blocking because the goroutine can be unblocked 81 // by an event occurring outside its bubble: 82 // 83 // - locking a [sync.Mutex] or [sync.RWMutex] 84 // - blocking on I/O, such as reading from a network socket 85 // - system calls 86 // 87 // # Isolation 88 // 89 // A channel, [time.Timer], or [time.Ticker] created within a bubble 90 // is associated with it. Operating on a bubbled channel, timer, or 91 // ticker from outside the bubble panics. 92 // 93 // A [sync.WaitGroup] becomes associated with a bubble on the first 94 // call to Add or Go. Once a WaitGroup is associated with a bubble, 95 // calling Add or Go from outside that bubble is a fatal error. 96 // (As a technical limitation, a WaitGroup defined as a package 97 // variable, such as "var wg sync.WaitGroup", cannot be associated 98 // with a bubble and operations on it may not be durably blocking. 99 // This limitation does not apply to a *WaitGroup stored in a 100 // package variable, such as "var wg = new(sync.WaitGroup)".) 101 // 102 // [sync.Cond.Wait] is durably blocking. Waking a goroutine in a bubble 103 // blocked on Cond.Wait from outside the bubble is a fatal error. 104 // 105 // Cleanup functions and finalizers registered with 106 // [runtime.AddCleanup] and [runtime.SetFinalizer] 107 // run outside of any bubble. 108 // 109 // # Example: Context.AfterFunc 110 // 111 // This example demonstrates testing the [context.AfterFunc] function. 112 // 113 // AfterFunc registers a function to execute in a new goroutine 114 // after a context is canceled. 115 // 116 // The test verifies that the function is not run before the context is canceled, 117 // and is run after the context is canceled. 118 // 119 // func TestContextAfterFunc(t *testing.T) { 120 // synctest.Test(t, func(t *testing.T) { 121 // // Create a context.Context which can be canceled. 122 // ctx, cancel := context.WithCancel(t.Context()) 123 // 124 // // context.AfterFunc registers a function to be called 125 // // when a context is canceled. 126 // afterFuncCalled := false 127 // context.AfterFunc(ctx, func() { 128 // afterFuncCalled = true 129 // }) 130 // 131 // // The context has not been canceled, so the AfterFunc is not called. 132 // synctest.Wait() 133 // if afterFuncCalled { 134 // t.Fatalf("before context is canceled: AfterFunc called") 135 // } 136 // 137 // // Cancel the context and wait for the AfterFunc to finish executing. 138 // // Verify that the AfterFunc ran. 139 // cancel() 140 // synctest.Wait() 141 // if !afterFuncCalled { 142 // t.Fatalf("before context is canceled: AfterFunc not called") 143 // } 144 // }) 145 // } 146 // 147 // # Example: Context.WithTimeout 148 // 149 // This example demonstrates testing the [context.WithTimeout] function. 150 // 151 // WithTimeout creates a context which is canceled after a timeout. 152 // 153 // The test verifies that the context is not canceled before the timeout expires, 154 // and is canceled after the timeout expires. 155 // 156 // func TestContextWithTimeout(t *testing.T) { 157 // synctest.Test(t, func(t *testing.T) { 158 // // Create a context.Context which is canceled after a timeout. 159 // const timeout = 5 * time.Second 160 // ctx, cancel := context.WithTimeout(t.Context(), timeout) 161 // defer cancel() 162 // 163 // // Wait just less than the timeout. 164 // time.Sleep(timeout - time.Nanosecond) 165 // synctest.Wait() 166 // if err := ctx.Err(); err != nil { 167 // t.Fatalf("before timeout: ctx.Err() = %v, want nil\n", err) 168 // } 169 // 170 // // Wait the rest of the way until the timeout. 171 // time.Sleep(time.Nanosecond) 172 // synctest.Wait() 173 // if err := ctx.Err(); err != context.DeadlineExceeded { 174 // t.Fatalf("after timeout: ctx.Err() = %v, want DeadlineExceeded\n", err) 175 // } 176 // }) 177 // } 178 // 179 // # Example: HTTP 100 Continue 180 // 181 // This example demonstrates testing [http.Transport]'s 100 Continue handling. 182 // 183 // An HTTP client sending a request can include an "Expect: 100-continue" header 184 // to tell the server that the client has additional data to send. 185 // The server may then respond with an 100 Continue information response 186 // to request the data, or some other status to tell the client the data is not needed. 187 // For example, a client uploading a large file might use this feature to confirm 188 // that the server is willing to accept the file before sending it. 189 // 190 // This test confirms that when sending an "Expect: 100-continue" header 191 // the HTTP client does not send a request's content before the server requests it, 192 // and that it does send the content after receiving a 100 Continue response. 193 // 194 // func TestHTTPTransport100Continue(t *testing.T) { 195 // synctest.Test(t, func(*testing.T) { 196 // // Create an in-process fake network connection. 197 // // We cannot use a loopback network connection for this test, 198 // // because goroutines blocked on network I/O prevent a synctest 199 // // bubble from becoming idle. 200 // srvConn, cliConn := net.Pipe() 201 // defer cliConn.Close() 202 // defer srvConn.Close() 203 // 204 // tr := &http.Transport{ 205 // // Use the fake network connection created above. 206 // DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { 207 // return cliConn, nil 208 // }, 209 // // Enable "Expect: 100-continue" handling. 210 // ExpectContinueTimeout: 5 * time.Second, 211 // } 212 // 213 // // Send a request with the "Expect: 100-continue" header set. 214 // // Send it in a new goroutine, since it won't complete until the end of the test. 215 // body := "request body" 216 // go func() { 217 // req, _ := http.NewRequest("PUT", "http://test.tld/", strings.NewReader(body)) 218 // req.Header.Set("Expect", "100-continue") 219 // resp, err := tr.RoundTrip(req) 220 // if err != nil { 221 // t.Errorf("RoundTrip: unexpected error %v\n", err) 222 // } else { 223 // resp.Body.Close() 224 // } 225 // }() 226 // 227 // // Read the request headers sent by the client. 228 // req, err := http.ReadRequest(bufio.NewReader(srvConn)) 229 // if err != nil { 230 // t.Fatalf("ReadRequest: %v\n", err) 231 // } 232 // 233 // // Start a new goroutine copying the body sent by the client into a buffer. 234 // // Wait for all goroutines in the bubble to block and verify that we haven't 235 // // read anything from the client yet. 236 // var gotBody bytes.Buffer 237 // go io.Copy(&gotBody, req.Body) 238 // synctest.Wait() 239 // if got, want := gotBody.String(), ""; got != want { 240 // t.Fatalf("before sending 100 Continue, read body: %q, want %q\n", got, want) 241 // } 242 // 243 // // Write a "100 Continue" response to the client and verify that 244 // // it sends the request body. 245 // srvConn.Write([]byte("HTTP/1.1 100 Continue\r\n\r\n")) 246 // synctest.Wait() 247 // if got, want := gotBody.String(), body; got != want { 248 // t.Fatalf("after sending 100 Continue, read body: %q, want %q\n", got, want) 249 // } 250 // 251 // // Finish up by sending the "200 OK" response to conclude the request. 252 // srvConn.Write([]byte("HTTP/1.1 200 OK\r\n\r\n")) 253 // 254 // // We started several goroutines during the test. 255 // // The synctest.Test call will wait for all of them to exit before returning. 256 // }) 257 // } 258 package synctest 259 260 import ( 261 "internal/synctest" 262 "testing" 263 _ "unsafe" // for linkname 264 ) 265 266 // Test executes f in a new bubble. 267 // 268 // Test waits for all goroutines in the bubble to exit before returning. 269 // If the goroutines in the bubble become deadlocked, the test fails. 270 // 271 // Test must not be called from within a bubble. 272 // 273 // The [*testing.T] provided to f has the following properties: 274 // 275 // - T.Cleanup functions run inside the bubble, 276 // immediately before Test returns. 277 // - T.Context returns a [context.Context] with a Done channel 278 // associated with the bubble. 279 // - T.Run, T.Parallel, and T.Deadline must not be called. 280 func Test(t *testing.T, f func(*testing.T)) { 281 var ok bool 282 synctest.Run(func() { 283 ok = testingSynctestTest(t, f) 284 }) 285 if !ok { 286 // Fail the test outside the bubble, 287 // so test durations get set using real time. 288 t.FailNow() 289 } 290 } 291 292 //go:linkname testingSynctestTest testing/synctest.testingSynctestTest 293 func testingSynctestTest(t *testing.T, f func(*testing.T)) bool 294 295 // Wait blocks until every goroutine within the current bubble, 296 // other than the current goroutine, is durably blocked. 297 // 298 // Wait must not be called from outside a bubble. 299 // Wait must not be called concurrently by multiple goroutines 300 // in the same bubble. 301 func Wait() { 302 synctest.Wait() 303 } 304