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  

View as plain text