aboutsummaryrefslogtreecommitdiff
path: root/src/testing/synctest/example_test.go
diff options
context:
space:
mode:
authorDamien Neil <dneil@google.com>2025-05-12 11:15:08 -0700
committerGopher Robot <gobot@golang.org>2025-05-20 15:46:03 -0700
commit49a660e22cb349cf13ef0a2f6214c6fdd75afda0 (patch)
treeef5e4c8825712a119dc37929cb372747b8d21230 /src/testing/synctest/example_test.go
parent609197b406ce8d9efd39bd3984b2cade74df35a6 (diff)
downloadgo-49a660e22cb349cf13ef0a2f6214c6fdd75afda0.tar.xz
testing/synctest: add Test
Add a synctest.Test function, superseding the experimental synctest.Run function. Promote the testing/synctest package out of experimental status. For #67434 For #73567 Change-Id: I3c5ba030860d90fe2ddb517a2f3536efd60181a9 Reviewed-on: https://go-review.googlesource.com/c/go/+/671961 Auto-Submit: Damien Neil <dneil@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Michael Pratt <mpratt@google.com>
Diffstat (limited to 'src/testing/synctest/example_test.go')
-rw-r--r--src/testing/synctest/example_test.go160
1 files changed, 160 insertions, 0 deletions
diff --git a/src/testing/synctest/example_test.go b/src/testing/synctest/example_test.go
new file mode 100644
index 0000000000..9ecd28d3dd
--- /dev/null
+++ b/src/testing/synctest/example_test.go
@@ -0,0 +1,160 @@
+// 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_test
+
+import (
+ "bufio"
+ "bytes"
+ "context"
+ "io"
+ "net"
+ "net/http"
+ "strings"
+ "testing"
+ "testing/synctest"
+ "time"
+)
+
+// Keep the following tests in sync with the package documentation.
+
+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.Nanosecond)
+ t.Log(time.Since(start)) // always logs "1ns"
+ }()
+ time.Sleep(2 * time.Nanosecond) // the AfterFunc will run before this Sleep returns
+ t.Log(time.Since(start)) // always logs "2ns"
+ })
+}
+
+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"
+ })
+}
+
+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")
+ }
+ })
+}
+
+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)
+ }
+ })
+}
+
+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.
+ })
+}