// 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. }) }