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