// 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 provides support for testing concurrent code.
//
// The [Test] function runs a function in an isolated "bubble".
// Any goroutines started within the bubble are also part of the bubble.
//
// # Time
//
// Within a bubble, the [time] package uses a fake clock.
// Each bubble has its own clock.
// The initial time is midnight UTC 2000-01-01.
//
// Time in a bubble only advances when every goroutine in the
// bubble is durably blocked.
// See below for the exact definition of "durably blocked".
//
// For example, this test runs immediately rather than taking
// two seconds:
//
//	func TestTime(t *testing.T) {
//		synctest.Test(t, func(t *testing.T) {
//			start := time.Now() // always midnight UTC 2000-01-01
//			go func() {
//				time.Sleep(1 * time.Second)
//				t.Log(time.Since(start)) // always logs "1s"
//			}()
//			time.Sleep(2 * time.Second) // the goroutine above will run before this Sleep returns
//			t.Log(time.Since(start))    // always logs "2s"
//		})
//	}
//
// Time stops advancing when the root goroutine of the bubble exits.
//
// # Blocking
//
// A goroutine in a bubble is "durably blocked" when it is blocked
// and can only be unblocked by another goroutine in the same bubble.
// A goroutine which can be unblocked by an event from outside its
// bubble is not durably blocked.
//
// The [Wait] function blocks until all other goroutines in the
// bubble are durably blocked.
//
// For example:
//
//	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"
//		})
//	}
//
// When every goroutine in a bubble is durably blocked:
//
//   - [Wait] returns, if it has been called.
//   - Otherwise, time advances to the next time that will
//     unblock at least one goroutine, if there is such a time
//     and the root goroutine of the bubble has not exited.
//   - Otherwise, there is a deadlock and [Test] panics.
//
// The following operations durably block a goroutine:
//
//   - a blocking send or receive on a channel created within the bubble
//   - a blocking select statement where every case is a channel created
//     within the bubble
//   - [sync.Cond.Wait]
//   - [sync.WaitGroup.Wait], when [sync.WaitGroup.Add] was called within the bubble
//   - [time.Sleep]
//
// Operations not in the above list are not durably blocking.
// In particular, the following operations may block a goroutine,
// but are not durably blocking because the goroutine can be unblocked
// by an event occurring outside its bubble:
//
//   - locking a [sync.Mutex] or [sync.RWMutex]
//   - blocking on I/O, such as reading from a network socket
//   - system calls
//
// # Isolation
//
// A channel, [time.Timer], or [time.Ticker] created within a bubble
// is associated with it. Operating on a bubbled channel, timer, or
// ticker from outside the bubble panics.
//
// A [sync.WaitGroup] becomes associated with a bubble on the first
// call to Add or Go. Once a WaitGroup is associated with a bubble,
// calling Add or Go from outside that bubble is a fatal error.
// (As a technical limitation, a WaitGroup defined as a package
// variable, such as "var wg sync.WaitGroup", cannot be associated
// with a bubble and operations on it may not be durably blocking.
// This limitation does not apply to a *WaitGroup stored in a
// package variable, such as "var wg = new(sync.WaitGroup)".)
//
// [sync.Cond.Wait] is durably blocking. Waking a goroutine in a bubble
// blocked on Cond.Wait from outside the bubble is a fatal error.
//
// Cleanup functions and finalizers registered with
// [runtime.AddCleanup] and [runtime.SetFinalizer]
// run outside of any bubble.
//
// # Example: Context.AfterFunc
//
// This example demonstrates testing the [context.AfterFunc] function.
//
// AfterFunc registers a function to execute in a new goroutine
// after a context is canceled.
//
// The test verifies that the function is not run before the context is canceled,
// and is run after the context is canceled.
//
//	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")
//			}
//		})
//	}
//
// # Example: Context.WithTimeout
//
// This example demonstrates testing the [context.WithTimeout] function.
//
// WithTimeout creates a context which is canceled after a timeout.
//
// The test verifies that the context is not canceled before the timeout expires,
// and is canceled after the timeout expires.
//
//	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)
//			}
//		})
//	}
//
// # Example: HTTP 100 Continue
//
// This example demonstrates testing [http.Transport]'s 100 Continue handling.
//
// An HTTP client sending a request can include an "Expect: 100-continue" header
// to tell the server that the client has additional data to send.
// The server may then respond with an 100 Continue information response
// to request the data, or some other status to tell the client the data is not needed.
// For example, a client uploading a large file might use this feature to confirm
// that the server is willing to accept the file before sending it.
//
// This test confirms that when sending an "Expect: 100-continue" header
// the HTTP client does not send a request's content before the server requests it,
// and that it does send the content after receiving a 100 Continue response.
//
//	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.
//		})
//	}
package synctest

import (
	"internal/synctest"
	"testing"
	_ "unsafe" // for linkname
)

// Test executes f in a new bubble.
//
// Test waits for all goroutines in the bubble to exit before returning.
// If the goroutines in the bubble become deadlocked, the test fails.
//
// Test must not be called from within a bubble.
//
// The [*testing.T] provided to f has the following properties:
//
//   - T.Cleanup functions run inside the bubble,
//     immediately before Test returns.
//   - T.Context returns a [context.Context] with a Done channel
//     associated with the bubble.
//   - T.Run, T.Parallel, and T.Deadline must not be called.
func Test(t *testing.T, f func(*testing.T)) {
	var ok bool
	synctest.Run(func() {
		ok = testingSynctestTest(t, f)
	})
	if !ok {
		// Fail the test outside the bubble,
		// so test durations get set using real time.
		t.FailNow()
	}
}

//go:linkname testingSynctestTest testing/synctest.testingSynctestTest
func testingSynctestTest(t *testing.T, f func(*testing.T)) bool

// Wait blocks until every goroutine within the current bubble,
// other than the current goroutine, is durably blocked.
//
// Wait must not be called from outside a bubble.
// Wait must not be called concurrently by multiple goroutines
// in the same bubble.
func Wait() {
	synctest.Wait()
}
