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  

View as plain text