quic: use testing/synctest
Replace bespoke fake time and synchronization with testing/synctest.
Change-Id: Ic3fe9635dbad36c890783c38e00708c6cb7a15f8
Reviewed-on: https://go-review.googlesource.com/c/net/+/714482
Reviewed-by: Nicholas Husin <nsh@golang.org>
Reviewed-by: Nicholas Husin <husin@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Damien Neil <dneil@google.com>
diff --git a/quic/bench_test.go b/quic/bench_test.go
index 9d8e5d2..002b40e 100644
--- a/quic/bench_test.go
+++ b/quic/bench_test.go
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.25
+
package quic
import (
diff --git a/quic/config_test.go b/quic/config_test.go
index 3511cd4..df878da 100644
--- a/quic/config_test.go
+++ b/quic/config_test.go
@@ -2,11 +2,19 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.25
+
package quic
-import "testing"
+import (
+ "testing"
+ "testing/synctest"
+)
func TestConfigTransportParameters(t *testing.T) {
+ synctest.Test(t, testConfigTransportParameters)
+}
+func testConfigTransportParameters(t *testing.T) {
const (
wantInitialMaxData = int64(1)
wantInitialMaxStreamData = int64(2)
diff --git a/quic/conn.go b/quic/conn.go
index 40bdddc..fd812b8 100644
--- a/quic/conn.go
+++ b/quic/conn.go
@@ -69,23 +69,12 @@
// init is called after a conn is created.
init(first bool)
- // nextMessage is called to request the next event from msgc.
- // Used to give tests control of the connection event loop.
- nextMessage(msgc chan any, nextTimeout time.Time) (now time.Time, message any)
-
// handleTLSEvent is called with each TLS event.
handleTLSEvent(tls.QUICEvent)
// newConnID is called to generate a new connection ID.
// Permits tests to generate consistent connection IDs rather than random ones.
newConnID(seq int64) ([]byte, error)
-
- // waitUntil blocks until the until func returns true or the context is done.
- // Used to synchronize asynchronous blocking operations in tests.
- waitUntil(ctx context.Context, until func() bool) error
-
- // timeNow returns the current time.
- timeNow() time.Time
}
// newServerConnIDs is connection IDs associated with a new server connection.
@@ -102,7 +91,6 @@
endpoint: e,
config: config,
peerAddr: unmapAddrPort(peerAddr),
- msgc: make(chan any, 1),
donec: make(chan struct{}),
peerAckDelayExponent: -1,
}
@@ -299,17 +287,12 @@
// The connection timer sends a message to the connection loop on expiry.
// We need to give it an expiry when creating it, so set the initial timeout to
// an arbitrary large value. The timer will be reset before this expires (and it
- // isn't a problem if it does anyway). Skip creating the timer in tests which
- // take control of the connection message loop.
- var timer *time.Timer
+ // isn't a problem if it does anyway).
var lastTimeout time.Time
- hooks := c.testHooks
- if hooks == nil {
- timer = time.AfterFunc(1*time.Hour, func() {
- c.sendMsg(timerEvent{})
- })
- defer timer.Stop()
- }
+ timer := time.AfterFunc(1*time.Hour, func() {
+ c.sendMsg(timerEvent{})
+ })
+ defer timer.Stop()
for c.lifetime.state != connStateDone {
sendTimeout := c.maybeSend(now) // try sending
@@ -326,10 +309,7 @@
}
var m any
- if hooks != nil {
- // Tests only: Wait for the test to tell us to continue.
- now, m = hooks.nextMessage(c.msgc, nextTimeout)
- } else if !nextTimeout.IsZero() && nextTimeout.Before(now) {
+ if !nextTimeout.IsZero() && nextTimeout.Before(now) {
// A connection timer has expired.
now = time.Now()
m = timerEvent{}
@@ -372,6 +352,9 @@
case func(time.Time, *Conn):
// Send a func to msgc to run it on the main Conn goroutine
m(now, c)
+ case func(now, next time.Time, _ *Conn):
+ // Send a func to msgc to run it on the main Conn goroutine
+ m(now, nextTimeout, c)
default:
panic(fmt.Sprintf("quic: unrecognized conn message %T", m))
}
@@ -410,31 +393,7 @@
defer close(donec)
f(now, c)
}
- if c.testHooks != nil {
- // In tests, we can't rely on being able to send a message immediately:
- // c.msgc might be full, and testConnHooks.nextMessage might be waiting
- // for us to block before it processes the next message.
- // To avoid a deadlock, we send the message in waitUntil.
- // If msgc is empty, the message is buffered.
- // If msgc is full, we block and let nextMessage process the queue.
- msgc := c.msgc
- c.testHooks.waitUntil(ctx, func() bool {
- for {
- select {
- case msgc <- msg:
- msgc = nil // send msg only once
- case <-donec:
- return true
- case <-c.donec:
- return true
- default:
- return false
- }
- }
- })
- } else {
- c.sendMsg(msg)
- }
+ c.sendMsg(msg)
select {
case <-donec:
case <-c.donec:
@@ -444,16 +403,6 @@
}
func (c *Conn) waitOnDone(ctx context.Context, ch <-chan struct{}) error {
- if c.testHooks != nil {
- return c.testHooks.waitUntil(ctx, func() bool {
- select {
- case <-ch:
- return true
- default:
- }
- return false
- })
- }
// Check the channel before the context.
// We always prefer to return results when available,
// even when provided with an already-canceled context.
diff --git a/quic/conn_async_test.go b/quic/conn_async_test.go
index f261e90..08cc7d3 100644
--- a/quic/conn_async_test.go
+++ b/quic/conn_async_test.go
@@ -2,44 +2,21 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.25
+
package quic
import (
"context"
"errors"
"fmt"
- "path/filepath"
- "runtime"
- "sync"
+ "testing/synctest"
)
-// asyncTestState permits handling asynchronous operations in a synchronous test.
-//
-// For example, a test may want to write to a stream and observe that
-// STREAM frames are sent with the contents of the write in response
-// to MAX_STREAM_DATA frames received from the peer.
-// The Stream.Write is an asynchronous operation, but the test is simpler
-// if we can start the write, observe the first STREAM frame sent,
-// send a MAX_STREAM_DATA frame, observe the next STREAM frame sent, etc.
-//
-// We do this by instrumenting points where operations can block.
-// We start async operations like Write in a goroutine,
-// and wait for the operation to either finish or hit a blocking point.
-// When the connection event loop is idle, we check a list of
-// blocked operations to see if any can be woken.
-type asyncTestState struct {
- mu sync.Mutex
- notify chan struct{}
- blocked map[*blockedAsync]struct{}
-}
-
// An asyncOp is an asynchronous operation that results in (T, error).
type asyncOp[T any] struct {
- v T
- err error
-
- caller string
- tc *testConn
+ v T
+ err error
donec chan struct{}
cancelFunc context.CancelFunc
}
@@ -47,17 +24,18 @@
// cancel cancels the async operation's context, and waits for
// the operation to complete.
func (a *asyncOp[T]) cancel() {
+ synctest.Wait()
select {
case <-a.donec:
return // already done
default:
}
a.cancelFunc()
- <-a.tc.asyncTestState.notify
+ synctest.Wait()
select {
case <-a.donec:
default:
- panic(fmt.Errorf("%v: async op failed to finish after being canceled", a.caller))
+ panic(fmt.Errorf("async op failed to finish after being canceled"))
}
}
@@ -71,115 +49,30 @@
// control over the progress of operations, an asyncOp can only
// become done in reaction to the test taking some action.
func (a *asyncOp[T]) result() (v T, err error) {
- a.tc.wait()
+ synctest.Wait()
select {
case <-a.donec:
return a.v, a.err
default:
- return v, errNotDone
+ return a.v, errNotDone
}
}
-// A blockedAsync is a blocked async operation.
-type blockedAsync struct {
- until func() bool // when this returns true, the operation is unblocked
- donec chan struct{} // closed when the operation is unblocked
-}
-
-type asyncContextKey struct{}
-
// runAsync starts an asynchronous operation.
//
// The function f should call a blocking function such as
// Stream.Write or Conn.AcceptStream and return its result.
// It must use the provided context.
func runAsync[T any](tc *testConn, f func(context.Context) (T, error)) *asyncOp[T] {
- as := &tc.asyncTestState
- if as.notify == nil {
- as.notify = make(chan struct{})
- as.mu.Lock()
- as.blocked = make(map[*blockedAsync]struct{})
- as.mu.Unlock()
- }
- _, file, line, _ := runtime.Caller(1)
- ctx := context.WithValue(context.Background(), asyncContextKey{}, true)
- ctx, cancel := context.WithCancel(ctx)
+ ctx, cancel := context.WithCancel(tc.t.Context())
a := &asyncOp[T]{
- tc: tc,
- caller: fmt.Sprintf("%v:%v", filepath.Base(file), line),
donec: make(chan struct{}),
cancelFunc: cancel,
}
go func() {
+ defer close(a.donec)
a.v, a.err = f(ctx)
- close(a.donec)
- as.notify <- struct{}{}
}()
- tc.t.Cleanup(func() {
- if _, err := a.result(); err == errNotDone {
- tc.t.Errorf("%v: async operation is still executing at end of test", a.caller)
- a.cancel()
- }
- })
- // Wait for the operation to either finish or block.
- <-as.notify
- tc.wait()
+ synctest.Wait()
return a
}
-
-// waitUntil waits for a blocked async operation to complete.
-// The operation is complete when the until func returns true.
-func (as *asyncTestState) waitUntil(ctx context.Context, until func() bool) error {
- if until() {
- return nil
- }
- if err := ctx.Err(); err != nil {
- // Context has already expired.
- return err
- }
- if ctx.Value(asyncContextKey{}) == nil {
- // Context is not one that we've created, and hasn't expired.
- // This probably indicates that we've tried to perform a
- // blocking operation without using the async test harness here,
- // which may have unpredictable results.
- panic("blocking async point with unexpected Context")
- }
- b := &blockedAsync{
- until: until,
- donec: make(chan struct{}),
- }
- // Record this as a pending blocking operation.
- as.mu.Lock()
- as.blocked[b] = struct{}{}
- as.mu.Unlock()
- // Notify the creator of the operation that we're blocked,
- // and wait to be woken up.
- as.notify <- struct{}{}
- select {
- case <-b.donec:
- case <-ctx.Done():
- return ctx.Err()
- }
- return nil
-}
-
-// wakeAsync tries to wake up a blocked async operation.
-// It returns true if one was woken, false otherwise.
-func (as *asyncTestState) wakeAsync() bool {
- as.mu.Lock()
- var woken *blockedAsync
- for w := range as.blocked {
- if w.until() {
- woken = w
- delete(as.blocked, w)
- break
- }
- }
- as.mu.Unlock()
- if woken == nil {
- return false
- }
- close(woken.donec)
- <-as.notify // must not hold as.mu while blocked here
- return true
-}
diff --git a/quic/conn_close_test.go b/quic/conn_close_test.go
index 0b37b3e..472a8f2 100644
--- a/quic/conn_close_test.go
+++ b/quic/conn_close_test.go
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.25
+
package quic
import (
@@ -9,10 +11,14 @@
"crypto/tls"
"errors"
"testing"
+ "testing/synctest"
"time"
)
func TestConnCloseResponseBackoff(t *testing.T) {
+ synctest.Test(t, testConnCloseResponseBackoff)
+}
+func testConnCloseResponseBackoff(t *testing.T) {
tc := newTestConn(t, clientSide, func(c *Config) {
clear(c.StatelessResetKey[:])
})
@@ -34,18 +40,18 @@
tc.writeFrames(packetType1RTT, debugFramePing{})
tc.wantIdle("packets received immediately after CONN_CLOSE receive no response")
- tc.advance(1100 * time.Microsecond)
+ time.Sleep(1100 * time.Microsecond)
tc.writeFrames(packetType1RTT, debugFramePing{})
tc.wantFrame("receiving packet 1.1ms after CONN_CLOSE generates another CONN_CLOSE",
packetType1RTT, debugFrameConnectionCloseTransport{
code: errNo,
})
- tc.advance(1100 * time.Microsecond)
+ time.Sleep(1100 * time.Microsecond)
tc.writeFrames(packetType1RTT, debugFramePing{})
tc.wantIdle("no response to packet, because CONN_CLOSE backoff is now 2ms")
- tc.advance(1000 * time.Microsecond)
+ time.Sleep(1000 * time.Microsecond)
tc.writeFrames(packetType1RTT, debugFramePing{})
tc.wantFrame("2ms since last CONN_CLOSE, receiving a packet generates another CONN_CLOSE",
packetType1RTT, debugFrameConnectionCloseTransport{
@@ -55,7 +61,7 @@
t.Errorf("conn.Wait() = %v, want still waiting", err)
}
- tc.advance(100000 * time.Microsecond)
+ time.Sleep(100000 * time.Microsecond)
tc.writeFrames(packetType1RTT, debugFramePing{})
tc.wantIdle("drain timer expired, no more responses")
@@ -68,6 +74,9 @@
}
func TestConnCloseWithPeerResponse(t *testing.T) {
+ synctest.Test(t, testConnCloseWithPeerResponse)
+}
+func testConnCloseWithPeerResponse(t *testing.T) {
qr := &qlogRecord{}
tc := newTestConn(t, clientSide, qr.config)
tc.handshake()
@@ -99,7 +108,7 @@
t.Errorf("non-blocking conn.Wait() = %v, want %v", err, wantErr)
}
- tc.advance(1 * time.Second) // long enough to exit the draining state
+ time.Sleep(1 * time.Second) // long enough to exit the draining state
qr.wantEvents(t, jsonEvent{
"name": "connectivity:connection_closed",
"data": map[string]any{
@@ -109,6 +118,9 @@
}
func TestConnClosePeerCloses(t *testing.T) {
+ synctest.Test(t, testConnClosePeerCloses)
+}
+func testConnClosePeerCloses(t *testing.T) {
qr := &qlogRecord{}
tc := newTestConn(t, clientSide, qr.config)
tc.handshake()
@@ -137,7 +149,7 @@
reason: "because",
})
- tc.advance(1 * time.Second) // long enough to exit the draining state
+ time.Sleep(1 * time.Second) // long enough to exit the draining state
qr.wantEvents(t, jsonEvent{
"name": "connectivity:connection_closed",
"data": map[string]any{
@@ -147,6 +159,9 @@
}
func TestConnCloseReceiveInInitial(t *testing.T) {
+ synctest.Test(t, testConnCloseReceiveInInitial)
+}
+func testConnCloseReceiveInInitial(t *testing.T) {
tc := newTestConn(t, clientSide)
tc.wantFrame("client sends Initial CRYPTO frame",
packetTypeInitial, debugFrameCrypto{
@@ -171,6 +186,9 @@
}
func TestConnCloseReceiveInHandshake(t *testing.T) {
+ synctest.Test(t, testConnCloseReceiveInHandshake)
+}
+func testConnCloseReceiveInHandshake(t *testing.T) {
tc := newTestConn(t, clientSide)
tc.ignoreFrame(frameTypeAck)
tc.wantFrame("client sends Initial CRYPTO frame",
@@ -204,6 +222,9 @@
}
func TestConnCloseClosedByEndpoint(t *testing.T) {
+ synctest.Test(t, testConnCloseClosedByEndpoint)
+}
+func testConnCloseClosedByEndpoint(t *testing.T) {
ctx := canceledContext()
tc := newTestConn(t, clientSide)
tc.handshake()
@@ -231,6 +252,9 @@
}
func TestConnCloseUnblocksAcceptStream(t *testing.T) {
+ synctest.Test(t, testConnCloseUnblocksAcceptStream)
+}
+func testConnCloseUnblocksAcceptStream(t *testing.T) {
testConnCloseUnblocks(t, func(ctx context.Context, tc *testConn) error {
_, err := tc.conn.AcceptStream(ctx)
return err
@@ -238,6 +262,9 @@
}
func TestConnCloseUnblocksNewStream(t *testing.T) {
+ synctest.Test(t, testConnCloseUnblocksNewStream)
+}
+func testConnCloseUnblocksNewStream(t *testing.T) {
testConnCloseUnblocks(t, func(ctx context.Context, tc *testConn) error {
_, err := tc.conn.NewStream(ctx)
return err
@@ -245,6 +272,9 @@
}
func TestConnCloseUnblocksStreamRead(t *testing.T) {
+ synctest.Test(t, testConnCloseUnblocksStreamRead)
+}
+func testConnCloseUnblocksStreamRead(t *testing.T) {
testConnCloseUnblocks(t, func(ctx context.Context, tc *testConn) error {
s := newLocalStream(t, tc, bidiStream)
s.SetReadContext(ctx)
@@ -255,6 +285,9 @@
}
func TestConnCloseUnblocksStreamWrite(t *testing.T) {
+ synctest.Test(t, testConnCloseUnblocksStreamWrite)
+}
+func testConnCloseUnblocksStreamWrite(t *testing.T) {
testConnCloseUnblocks(t, func(ctx context.Context, tc *testConn) error {
s := newLocalStream(t, tc, bidiStream)
s.SetWriteContext(ctx)
@@ -267,6 +300,9 @@
}
func TestConnCloseUnblocksStreamClose(t *testing.T) {
+ synctest.Test(t, testConnCloseUnblocksStreamClose)
+}
+func testConnCloseUnblocksStreamClose(t *testing.T) {
testConnCloseUnblocks(t, func(ctx context.Context, tc *testConn) error {
s := newLocalStream(t, tc, bidiStream)
s.SetWriteContext(ctx)
diff --git a/quic/conn_flow_test.go b/quic/conn_flow_test.go
index 52ecf92..d8d3ae7 100644
--- a/quic/conn_flow_test.go
+++ b/quic/conn_flow_test.go
@@ -2,14 +2,20 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.25
+
package quic
import (
"context"
"testing"
+ "testing/synctest"
)
func TestConnInflowReturnOnRead(t *testing.T) {
+ synctest.Test(t, testConnInflowReturnOnRead)
+}
+func testConnInflowReturnOnRead(t *testing.T) {
tc, s := newTestConnAndRemoteStream(t, serverSide, uniStream, func(c *Config) {
c.MaxConnReadBufferSize = 64
})
@@ -41,6 +47,9 @@
}
func TestConnInflowReturnOnRacingReads(t *testing.T) {
+ synctest.Test(t, testConnInflowReturnOnRacingReads)
+}
+func testConnInflowReturnOnRacingReads(t *testing.T) {
// Perform two reads at the same time,
// one for half of MaxConnReadBufferSize
// and one for one byte.
@@ -91,6 +100,9 @@
}
func TestConnInflowReturnOnClose(t *testing.T) {
+ synctest.Test(t, testConnInflowReturnOnClose)
+}
+func testConnInflowReturnOnClose(t *testing.T) {
tc, s := newTestConnAndRemoteStream(t, serverSide, uniStream, func(c *Config) {
c.MaxConnReadBufferSize = 64
})
@@ -107,6 +119,9 @@
}
func TestConnInflowReturnOnReset(t *testing.T) {
+ synctest.Test(t, testConnInflowReturnOnReset)
+}
+func testConnInflowReturnOnReset(t *testing.T) {
tc, s := newTestConnAndRemoteStream(t, serverSide, uniStream, func(c *Config) {
c.MaxConnReadBufferSize = 64
})
@@ -127,6 +142,9 @@
}
func TestConnInflowStreamViolation(t *testing.T) {
+ synctest.Test(t, testConnInflowStreamViolation)
+}
+func testConnInflowStreamViolation(t *testing.T) {
tc := newTestConn(t, serverSide, func(c *Config) {
c.MaxConnReadBufferSize = 100
})
@@ -169,6 +187,9 @@
}
func TestConnInflowResetViolation(t *testing.T) {
+ synctest.Test(t, testConnInflowResetViolation)
+}
+func testConnInflowResetViolation(t *testing.T) {
tc := newTestConn(t, serverSide, func(c *Config) {
c.MaxConnReadBufferSize = 100
})
@@ -197,6 +218,9 @@
}
func TestConnInflowMultipleStreams(t *testing.T) {
+ synctest.Test(t, testConnInflowMultipleStreams)
+}
+func testConnInflowMultipleStreams(t *testing.T) {
tc := newTestConn(t, serverSide, func(c *Config) {
c.MaxConnReadBufferSize = 128
})
@@ -247,6 +271,9 @@
}
func TestConnOutflowBlocked(t *testing.T) {
+ synctest.Test(t, testConnOutflowBlocked)
+}
+func testConnOutflowBlocked(t *testing.T) {
tc, s := newTestConnAndLocalStream(t, clientSide, uniStream,
permissiveTransportParameters,
func(p *transportParameters) {
@@ -291,6 +318,9 @@
}
func TestConnOutflowMaxDataDecreases(t *testing.T) {
+ synctest.Test(t, testConnOutflowMaxDataDecreases)
+}
+func testConnOutflowMaxDataDecreases(t *testing.T) {
tc, s := newTestConnAndLocalStream(t, clientSide, uniStream,
permissiveTransportParameters,
func(p *transportParameters) {
@@ -318,6 +348,9 @@
}
func TestConnOutflowMaxDataRoundRobin(t *testing.T) {
+ synctest.Test(t, testConnOutflowMaxDataRoundRobin)
+}
+func testConnOutflowMaxDataRoundRobin(t *testing.T) {
ctx := canceledContext()
tc := newTestConn(t, clientSide, permissiveTransportParameters,
func(p *transportParameters) {
@@ -370,6 +403,9 @@
}
func TestConnOutflowMetaAndData(t *testing.T) {
+ synctest.Test(t, testConnOutflowMetaAndData)
+}
+func testConnOutflowMetaAndData(t *testing.T) {
tc, s := newTestConnAndLocalStream(t, clientSide, bidiStream,
permissiveTransportParameters,
func(p *transportParameters) {
@@ -398,6 +434,9 @@
}
func TestConnOutflowResentData(t *testing.T) {
+ synctest.Test(t, testConnOutflowResentData)
+}
+func testConnOutflowResentData(t *testing.T) {
tc, s := newTestConnAndLocalStream(t, clientSide, bidiStream,
permissiveTransportParameters,
func(p *transportParameters) {
diff --git a/quic/conn_id_test.go b/quic/conn_id_test.go
index c9da0eb..4b4da67 100644
--- a/quic/conn_id_test.go
+++ b/quic/conn_id_test.go
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.25
+
package quic
import (
@@ -11,9 +13,13 @@
"net/netip"
"strings"
"testing"
+ "testing/synctest"
)
func TestConnIDClientHandshake(t *testing.T) {
+ synctest.Test(t, testConnIDClientHandshake)
+}
+func testConnIDClientHandshake(t *testing.T) {
tc := newTestConn(t, clientSide)
// On initialization, the client chooses local and remote IDs.
//
@@ -57,6 +63,9 @@
}
func TestConnIDServerHandshake(t *testing.T) {
+ synctest.Test(t, testConnIDServerHandshake)
+}
+func testConnIDServerHandshake(t *testing.T) {
tc := newTestConn(t, serverSide)
// On initialization, the server is provided with the client-chosen
// transient connection ID, and allocates an ID of its own.
@@ -178,6 +187,9 @@
}
func TestConnIDPeerRequestsManyIDs(t *testing.T) {
+ synctest.Test(t, testConnIDPeerRequestsManyIDs)
+}
+func testConnIDPeerRequestsManyIDs(t *testing.T) {
// "An endpoint SHOULD ensure that its peer has a sufficient number
// of available and unused connection IDs."
// https://www.rfc-editor.org/rfc/rfc9000#section-5.1.1-4
@@ -220,6 +232,9 @@
}
func TestConnIDPeerProvidesTooManyIDs(t *testing.T) {
+ synctest.Test(t, testConnIDPeerProvidesTooManyIDs)
+}
+func testConnIDPeerProvidesTooManyIDs(t *testing.T) {
// "An endpoint MUST NOT provide more connection IDs than the peer's limit."
// https://www.rfc-editor.org/rfc/rfc9000#section-5.1.1-4
tc := newTestConn(t, serverSide)
@@ -238,6 +253,9 @@
}
func TestConnIDPeerTemporarilyExceedsActiveConnIDLimit(t *testing.T) {
+ synctest.Test(t, testConnIDPeerTemporarilyExceedsActiveConnIDLimit)
+}
+func testConnIDPeerTemporarilyExceedsActiveConnIDLimit(t *testing.T) {
// "An endpoint MAY send connection IDs that temporarily exceed a peer's limit
// if the NEW_CONNECTION_ID frame also requires the retirement of any excess [...]"
// https://www.rfc-editor.org/rfc/rfc9000#section-5.1.1-4
@@ -272,7 +290,7 @@
clientSide,
serverSide,
} {
- t.Run(side.String(), func(t *testing.T) {
+ synctestSubtest(t, side.String(), func(t *testing.T) {
tc := newTestConn(t, side)
tc.handshake()
tc.ignoreFrame(frameTypeAck)
@@ -293,6 +311,9 @@
}
func TestConnIDPeerWithZeroLengthConnIDSendsNewConnectionID(t *testing.T) {
+ synctest.Test(t, testConnIDPeerWithZeroLengthConnIDSendsNewConnectionID)
+}
+func testConnIDPeerWithZeroLengthConnIDSendsNewConnectionID(t *testing.T) {
// "An endpoint that selects a zero-length connection ID during the handshake
// cannot issue a new connection ID."
// https://www.rfc-editor.org/rfc/rfc9000#section-5.1.1-8
@@ -315,6 +336,9 @@
}
func TestConnIDPeerRequestsRetirement(t *testing.T) {
+ synctest.Test(t, testConnIDPeerRequestsRetirement)
+}
+func testConnIDPeerRequestsRetirement(t *testing.T) {
// "Upon receipt of an increased Retire Prior To field, the peer MUST
// stop using the corresponding connection IDs and retire them with
// RETIRE_CONNECTION_ID frames [...]"
@@ -339,6 +363,9 @@
}
func TestConnIDPeerDoesNotAcknowledgeRetirement(t *testing.T) {
+ synctest.Test(t, testConnIDPeerDoesNotAcknowledgeRetirement)
+}
+func testConnIDPeerDoesNotAcknowledgeRetirement(t *testing.T) {
// "An endpoint SHOULD limit the number of connection IDs it has retired locally
// for which RETIRE_CONNECTION_ID frames have not yet been acknowledged."
// https://www.rfc-editor.org/rfc/rfc9000#section-5.1.2-6
@@ -364,6 +391,9 @@
}
func TestConnIDRepeatedNewConnectionIDFrame(t *testing.T) {
+ synctest.Test(t, testConnIDRepeatedNewConnectionIDFrame)
+}
+func testConnIDRepeatedNewConnectionIDFrame(t *testing.T) {
// "Receipt of the same [NEW_CONNECTION_ID] frame multiple times
// MUST NOT be treated as a connection error.
// https://www.rfc-editor.org/rfc/rfc9000#section-19.15-7
@@ -387,6 +417,9 @@
}
func TestConnIDForSequenceNumberChanges(t *testing.T) {
+ synctest.Test(t, testConnIDForSequenceNumberChanges)
+}
+func testConnIDForSequenceNumberChanges(t *testing.T) {
// "[...] if a sequence number is used for different connection IDs,
// the endpoint MAY treat that receipt as a connection error
// of type PROTOCOL_VIOLATION."
@@ -415,6 +448,9 @@
}
func TestConnIDRetirePriorToAfterNewConnID(t *testing.T) {
+ synctest.Test(t, testConnIDRetirePriorToAfterNewConnID)
+}
+func testConnIDRetirePriorToAfterNewConnID(t *testing.T) {
// "Receiving a value in the Retire Prior To field that is greater than
// that in the Sequence Number field MUST be treated as a connection error
// of type FRAME_ENCODING_ERROR.
@@ -436,6 +472,9 @@
}
func TestConnIDAlreadyRetired(t *testing.T) {
+ synctest.Test(t, testConnIDAlreadyRetired)
+}
+func testConnIDAlreadyRetired(t *testing.T) {
// "An endpoint that receives a NEW_CONNECTION_ID frame with a
// sequence number smaller than the Retire Prior To field of a
// previously received NEW_CONNECTION_ID frame MUST send a
@@ -472,6 +511,9 @@
}
func TestConnIDRepeatedRetireConnectionIDFrame(t *testing.T) {
+ synctest.Test(t, testConnIDRepeatedRetireConnectionIDFrame)
+}
+func testConnIDRepeatedRetireConnectionIDFrame(t *testing.T) {
tc := newTestConn(t, clientSide)
tc.handshake()
tc.ignoreFrame(frameTypeAck)
@@ -493,6 +535,9 @@
}
func TestConnIDRetiredUnsent(t *testing.T) {
+ synctest.Test(t, testConnIDRetiredUnsent)
+}
+func testConnIDRetiredUnsent(t *testing.T) {
// "Receipt of a RETIRE_CONNECTION_ID frame containing a sequence number
// greater than any previously sent to the peer MUST be treated as a
// connection error of type PROTOCOL_VIOLATION."
@@ -512,6 +557,9 @@
}
func TestConnIDUsePreferredAddressConnID(t *testing.T) {
+ synctest.Test(t, testConnIDUsePreferredAddressConnID)
+}
+func testConnIDUsePreferredAddressConnID(t *testing.T) {
// Peer gives us a connection ID in the preferred address transport parameter.
// We don't use the preferred address at this time, but we should use the
// connection ID. (It isn't tied to any specific address.)
@@ -543,6 +591,9 @@
}
func TestConnIDPeerProvidesPreferredAddrAndTooManyConnIDs(t *testing.T) {
+ synctest.Test(t, testConnIDPeerProvidesPreferredAddrAndTooManyConnIDs)
+}
+func testConnIDPeerProvidesPreferredAddrAndTooManyConnIDs(t *testing.T) {
// Peer gives us more conn ids than our advertised limit,
// including a conn id in the preferred address transport parameter.
cid := testPeerConnID(10)
@@ -568,6 +619,9 @@
}
func TestConnIDPeerWithZeroLengthIDProvidesPreferredAddr(t *testing.T) {
+ synctest.Test(t, testConnIDPeerWithZeroLengthIDProvidesPreferredAddr)
+}
+func testConnIDPeerWithZeroLengthIDProvidesPreferredAddr(t *testing.T) {
// Peer gives us more conn ids than our advertised limit,
// including a conn id in the preferred address transport parameter.
tc := newTestConn(t, serverSide, func(p *transportParameters) {
@@ -596,7 +650,7 @@
// "Endpoints MUST validate that received [initial_source_connection_id]
// parameters match received connection ID values."
// https://www.rfc-editor.org/rfc/rfc9000#section-7.3-3
- testSides(t, "", func(t *testing.T, side connSide) {
+ testSidesSynctest(t, "", func(t *testing.T, side connSide) {
tc := newTestConn(t, side, func(p *transportParameters) {
p.initialSrcConnID = []byte("invalid")
})
@@ -621,7 +675,7 @@
}
func TestConnIDsCleanedUpAfterClose(t *testing.T) {
- testSides(t, "", func(t *testing.T, side connSide) {
+ testSidesSynctest(t, "", func(t *testing.T, side connSide) {
tc := newTestConn(t, side, func(p *transportParameters) {
if side == clientSide {
token := testPeerStatelessResetToken(0)
@@ -664,6 +718,9 @@
}
func TestConnIDRetiredConnIDResent(t *testing.T) {
+ synctest.Test(t, testConnIDRetiredConnIDResent)
+}
+func testConnIDRetiredConnIDResent(t *testing.T) {
tc := newTestConn(t, serverSide)
tc.handshake()
tc.ignoreFrame(frameTypeAck)
diff --git a/quic/conn_loss_test.go b/quic/conn_loss_test.go
index f13ea13..49c794f 100644
--- a/quic/conn_loss_test.go
+++ b/quic/conn_loss_test.go
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.25
+
package quic
import (
@@ -9,6 +11,8 @@
"crypto/tls"
"fmt"
"testing"
+ "testing/synctest"
+ "time"
)
// Frames may be retransmitted either when the packet containing the frame is lost, or on PTO.
@@ -22,6 +26,16 @@
})
}
+func lostFrameTestSynctest(t *testing.T, f func(t *testing.T, pto bool)) {
+ t.Helper()
+ lostFrameTest(t, func(t *testing.T, pto bool) {
+ t.Helper()
+ synctest.Test(t, func(t *testing.T) {
+ f(t, pto)
+ })
+ })
+}
+
// triggerLossOrPTO causes the conn to declare the last sent packet lost,
// or advances to the PTO timer.
func (tc *testConn) triggerLossOrPTO(ptype packetType, pto bool) {
@@ -33,7 +47,11 @@
if *testVV {
tc.t.Logf("advancing to PTO timer")
}
- tc.advanceTo(tc.conn.loss.timer)
+ var when time.Time
+ tc.conn.runOnLoop(tc.t.Context(), func(now time.Time, conn *Conn) {
+ when = conn.loss.timer
+ })
+ time.Sleep(time.Until(when))
return
}
if *testVV {
@@ -77,7 +95,7 @@
// "Cancellation of stream transmission, as carried in a RESET_STREAM frame,
// is sent until acknowledged or until all stream data is acknowledged by the peer [...]"
// https://www.rfc-editor.org/rfc/rfc9000.html#section-13.3-3.4
- lostFrameTest(t, func(t *testing.T, pto bool) {
+ lostFrameTestSynctest(t, func(t *testing.T, pto bool) {
tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, permissiveTransportParameters)
tc.ignoreFrame(frameTypeAck)
@@ -106,7 +124,7 @@
// Technically, we can stop sending a STOP_SENDING frame if the peer sends
// us all the data for the stream or resets it. We don't bother tracking this,
// however, so we'll keep sending the frame until it is acked. This is harmless.
- lostFrameTest(t, func(t *testing.T, pto bool) {
+ lostFrameTestSynctest(t, func(t *testing.T, pto bool) {
tc, s := newTestConnAndRemoteStream(t, serverSide, uniStream, permissiveTransportParameters)
tc.ignoreFrame(frameTypeAck)
@@ -127,7 +145,7 @@
func TestLostCryptoFrame(t *testing.T) {
// "Data sent in CRYPTO frames is retransmitted [...] until all data has been acknowledged."
// https://www.rfc-editor.org/rfc/rfc9000.html#section-13.3-3.1
- lostFrameTest(t, func(t *testing.T, pto bool) {
+ lostFrameTestSynctest(t, func(t *testing.T, pto bool) {
tc := newTestConn(t, clientSide)
tc.ignoreFrame(frameTypeAck)
@@ -171,7 +189,7 @@
func TestLostStreamFrameEmpty(t *testing.T) {
// A STREAM frame opening a stream, but containing no stream data, should
// be retransmitted if lost.
- lostFrameTest(t, func(t *testing.T, pto bool) {
+ lostFrameTestSynctest(t, func(t *testing.T, pto bool) {
ctx := canceledContext()
tc := newTestConn(t, clientSide, permissiveTransportParameters)
tc.handshake()
@@ -203,7 +221,7 @@
// https://www.rfc-editor.org/rfc/rfc9000#section-13.3-3.2
//
// TODO: Lost stream frame after RESET_STREAM
- lostFrameTest(t, func(t *testing.T, pto bool) {
+ lostFrameTestSynctest(t, func(t *testing.T, pto bool) {
data := []byte{0, 1, 2, 3, 4, 5, 6, 7}
tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, func(p *transportParameters) {
p.initialMaxStreamsUni = 1
@@ -247,6 +265,9 @@
}
func TestLostStreamPartialLoss(t *testing.T) {
+ synctest.Test(t, testLostStreamPartialLoss)
+}
+func testLostStreamPartialLoss(t *testing.T) {
// Conn sends four STREAM packets.
// ACKs are received for the packets containing bytes 0 and 2.
// The remaining packets are declared lost.
@@ -295,7 +316,7 @@
// "An updated value is sent in a MAX_DATA frame if the packet
// containing the most recently sent MAX_DATA frame is declared lost [...]"
// https://www.rfc-editor.org/rfc/rfc9000#section-13.3-3.7
- lostFrameTest(t, func(t *testing.T, pto bool) {
+ lostFrameTestSynctest(t, func(t *testing.T, pto bool) {
const maxWindowSize = 32
buf := make([]byte, maxWindowSize)
tc, s := newTestConnAndRemoteStream(t, serverSide, uniStream, func(c *Config) {
@@ -340,7 +361,7 @@
// "[...] an updated value is sent when the packet containing
// the most recent MAX_STREAM_DATA frame for a stream is lost"
// https://www.rfc-editor.org/rfc/rfc9000#section-13.3-3.8
- lostFrameTest(t, func(t *testing.T, pto bool) {
+ lostFrameTestSynctest(t, func(t *testing.T, pto bool) {
const maxWindowSize = 32
buf := make([]byte, maxWindowSize)
tc, s := newTestConnAndRemoteStream(t, serverSide, uniStream, func(c *Config) {
@@ -387,7 +408,7 @@
// "An endpoint SHOULD stop sending MAX_STREAM_DATA frames when
// the receiving part of the stream enters a "Size Known" or "Reset Recvd" state."
// https://www.rfc-editor.org/rfc/rfc9000#section-13.3-3.8
- lostFrameTest(t, func(t *testing.T, pto bool) {
+ lostFrameTestSynctest(t, func(t *testing.T, pto bool) {
const maxWindowSize = 10
buf := make([]byte, maxWindowSize)
tc, s := newTestConnAndRemoteStream(t, serverSide, uniStream, func(c *Config) {
@@ -425,7 +446,7 @@
// most recent MAX_STREAMS for a stream type frame is declared lost [...]"
// https://www.rfc-editor.org/rfc/rfc9000#section-13.3-3.9
testStreamTypes(t, "", func(t *testing.T, styp streamType) {
- lostFrameTest(t, func(t *testing.T, pto bool) {
+ lostFrameTestSynctest(t, func(t *testing.T, pto bool) {
ctx := canceledContext()
tc := newTestConn(t, serverSide, func(c *Config) {
c.MaxUniRemoteStreams = 1
@@ -469,6 +490,9 @@
}
func TestLostMaxStreamsFrameNotMostRecent(t *testing.T) {
+ synctest.Test(t, testLostMaxStreamsFrameNotMostRecent)
+}
+func testLostMaxStreamsFrameNotMostRecent(t *testing.T) {
// Send two MAX_STREAMS frames, lose the first one.
//
// No PTO mode for this test: The ack that causes the first frame
@@ -514,7 +538,7 @@
// "A new [STREAM_DATA_BLOCKED] frame is sent if a packet containing
// the most recent frame for a scope is lost [...]"
// https://www.rfc-editor.org/rfc/rfc9000#section-13.3-3.10
- lostFrameTest(t, func(t *testing.T, pto bool) {
+ lostFrameTestSynctest(t, func(t *testing.T, pto bool) {
tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, func(p *transportParameters) {
p.initialMaxStreamsUni = 1
p.initialMaxData = 1 << 20
@@ -565,7 +589,7 @@
// "A new [STREAM_DATA_BLOCKED] frame is sent [...] only while
// the endpoint is blocked on the corresponding limit."
// https://www.rfc-editor.org/rfc/rfc9000#section-13.3-3.10
- lostFrameTest(t, func(t *testing.T, pto bool) {
+ lostFrameTestSynctest(t, func(t *testing.T, pto bool) {
tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, func(p *transportParameters) {
p.initialMaxStreamsUni = 1
p.initialMaxData = 1 << 20
@@ -607,7 +631,7 @@
func TestLostNewConnectionIDFrame(t *testing.T) {
// "New connection IDs are [...] retransmitted if the packet containing them is lost."
// https://www.rfc-editor.org/rfc/rfc9000#section-13.3-3.13
- lostFrameTest(t, func(t *testing.T, pto bool) {
+ lostFrameTestSynctest(t, func(t *testing.T, pto bool) {
tc := newTestConn(t, serverSide)
tc.handshake()
tc.ignoreFrame(frameTypeAck)
@@ -637,7 +661,7 @@
// "[...] retired connection IDs are [...] retransmitted
// if the packet containing them is lost."
// https://www.rfc-editor.org/rfc/rfc9000#section-13.3-3.13
- lostFrameTest(t, func(t *testing.T, pto bool) {
+ lostFrameTestSynctest(t, func(t *testing.T, pto bool) {
tc := newTestConn(t, clientSide)
tc.handshake()
tc.ignoreFrame(frameTypeAck)
@@ -664,7 +688,7 @@
func TestLostPathResponseFrame(t *testing.T) {
// "Responses to path validation using PATH_RESPONSE frames are sent just once."
// https://www.rfc-editor.org/rfc/rfc9000.html#section-13.3-3.12
- lostFrameTest(t, func(t *testing.T, pto bool) {
+ lostFrameTestSynctest(t, func(t *testing.T, pto bool) {
tc := newTestConn(t, clientSide)
tc.handshake()
tc.ignoreFrame(frameTypeAck)
@@ -687,7 +711,7 @@
func TestLostHandshakeDoneFrame(t *testing.T) {
// "The HANDSHAKE_DONE frame MUST be retransmitted until it is acknowledged."
// https://www.rfc-editor.org/rfc/rfc9000.html#section-13.3-3.16
- lostFrameTest(t, func(t *testing.T, pto bool) {
+ lostFrameTestSynctest(t, func(t *testing.T, pto bool) {
tc := newTestConn(t, serverSide)
tc.ignoreFrame(frameTypeAck)
diff --git a/quic/conn_recv_test.go b/quic/conn_recv_test.go
index 1a0eb3a..6ee728e 100644
--- a/quic/conn_recv_test.go
+++ b/quic/conn_recv_test.go
@@ -2,14 +2,20 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.25
+
package quic
import (
"crypto/tls"
"testing"
+ "testing/synctest"
)
func TestConnReceiveAckForUnsentPacket(t *testing.T) {
+ synctest.Test(t, testConnReceiveAckForUnsentPacket)
+}
+func testConnReceiveAckForUnsentPacket(t *testing.T) {
tc := newTestConn(t, serverSide, permissiveTransportParameters)
tc.handshake()
tc.writeFrames(packetType1RTT,
@@ -27,6 +33,9 @@
// drop state for a number space, and also contains a valid ACK frame for that space,
// we shouldn't complain about the ACK.
func TestConnReceiveAckForDroppedSpace(t *testing.T) {
+ synctest.Test(t, testConnReceiveAckForDroppedSpace)
+}
+func testConnReceiveAckForDroppedSpace(t *testing.T) {
tc := newTestConn(t, serverSide, permissiveTransportParameters)
tc.ignoreFrame(frameTypeAck)
tc.ignoreFrame(frameTypeNewConnectionID)
diff --git a/quic/conn_send_test.go b/quic/conn_send_test.go
index c5cf936..88911bd 100644
--- a/quic/conn_send_test.go
+++ b/quic/conn_send_test.go
@@ -2,14 +2,20 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.25
+
package quic
import (
"testing"
+ "testing/synctest"
"time"
)
func TestAckElicitingAck(t *testing.T) {
+ synctest.Test(t, testAckElicitingAck)
+}
+func testAckElicitingAck(t *testing.T) {
// "A receiver that sends only non-ack-eliciting packets [...] might not receive
// an acknowledgment for a long period of time.
// [...] a receiver could send a [...] ack-eliciting frame occasionally [...]
@@ -22,7 +28,7 @@
tc.handshake()
const count = 100
for i := 0; i < count; i++ {
- tc.advance(1 * time.Millisecond)
+ time.Sleep(1 * time.Millisecond)
tc.writeFrames(packetType1RTT,
debugFramePing{},
)
@@ -38,6 +44,9 @@
}
func TestSendPacketNumberSize(t *testing.T) {
+ synctest.Test(t, testSendPacketNumberSize)
+}
+func testSendPacketNumberSize(t *testing.T) {
tc := newTestConn(t, clientSide, permissiveTransportParameters)
tc.handshake()
diff --git a/quic/conn_streams.go b/quic/conn_streams.go
index 80884fd..0e4bf50 100644
--- a/quic/conn_streams.go
+++ b/quic/conn_streams.go
@@ -71,7 +71,7 @@
// AcceptStream waits for and returns the next stream created by the peer.
func (c *Conn) AcceptStream(ctx context.Context) (*Stream, error) {
- return c.streams.queue.get(ctx, c.testHooks)
+ return c.streams.queue.get(ctx)
}
// NewStream creates a stream.
diff --git a/quic/conn_streams_test.go b/quic/conn_streams_test.go
index af3c1de..b95aa47 100644
--- a/quic/conn_streams_test.go
+++ b/quic/conn_streams_test.go
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.25
+
package quic
import (
@@ -11,9 +13,13 @@
"math"
"sync"
"testing"
+ "testing/synctest"
)
func TestStreamsCreate(t *testing.T) {
+ synctest.Test(t, testStreamsCreate)
+}
+func testStreamsCreate(t *testing.T) {
ctx := canceledContext()
tc := newTestConn(t, clientSide, permissiveTransportParameters)
tc.handshake()
@@ -53,6 +59,9 @@
}
func TestStreamsAccept(t *testing.T) {
+ synctest.Test(t, testStreamsAccept)
+}
+func testStreamsAccept(t *testing.T) {
ctx := canceledContext()
tc := newTestConn(t, serverSide)
tc.handshake()
@@ -95,6 +104,9 @@
}
func TestStreamsBlockingAccept(t *testing.T) {
+ synctest.Test(t, testStreamsBlockingAccept)
+}
+func testStreamsBlockingAccept(t *testing.T) {
tc := newTestConn(t, serverSide)
tc.handshake()
@@ -124,6 +136,9 @@
}
func TestStreamsLocalStreamNotCreated(t *testing.T) {
+ synctest.Test(t, testStreamsLocalStreamNotCreated)
+}
+func testStreamsLocalStreamNotCreated(t *testing.T) {
// "An endpoint MUST terminate the connection with error STREAM_STATE_ERROR
// if it receives a STREAM frame for a locally initiated stream that has
// not yet been created [...]"
@@ -142,6 +157,9 @@
}
func TestStreamsLocalStreamClosed(t *testing.T) {
+ synctest.Test(t, testStreamsLocalStreamClosed)
+}
+func testStreamsLocalStreamClosed(t *testing.T) {
tc, s := newTestConnAndLocalStream(t, clientSide, uniStream, permissiveTransportParameters)
s.CloseWrite()
tc.wantFrame("FIN for closed stream",
@@ -168,6 +186,9 @@
}
func TestStreamsStreamSendOnly(t *testing.T) {
+ synctest.Test(t, testStreamsStreamSendOnly)
+}
+func testStreamsStreamSendOnly(t *testing.T) {
// "An endpoint MUST terminate the connection with error STREAM_STATE_ERROR
// if it receives a STREAM frame for a locally initiated stream that has
// not yet been created [...]"
@@ -198,6 +219,9 @@
}
func TestStreamsWriteQueueFairness(t *testing.T) {
+ synctest.Test(t, testStreamsWriteQueueFairness)
+}
+func testStreamsWriteQueueFairness(t *testing.T) {
ctx := canceledContext()
const dataLen = 1 << 20
const numStreams = 3
@@ -233,7 +257,7 @@
}
// Wait for the stream to finish writing whatever frames it can before
// congestion control blocks it.
- tc.wait()
+ synctest.Wait()
}
sent := make([]int64, len(streams))
@@ -344,7 +368,7 @@
},
}} {
name := fmt.Sprintf("%v/%v/%v", test.side, test.styp, test.name)
- t.Run(name, func(t *testing.T) {
+ synctestSubtest(t, name, func(t *testing.T) {
tc, s := newTestConnAndStream(t, serverSide, test.side, test.styp,
permissiveTransportParameters)
tc.ignoreFrame(frameTypeStreamBase)
@@ -364,6 +388,9 @@
}
func TestStreamsCreateAndCloseRemote(t *testing.T) {
+ synctest.Test(t, testStreamsCreateAndCloseRemote)
+}
+func testStreamsCreateAndCloseRemote(t *testing.T) {
// This test exercises creating new streams in response to frames
// from the peer, and cleaning up after streams are fully closed.
//
@@ -473,6 +500,9 @@
}
func TestStreamsCreateConcurrency(t *testing.T) {
+ synctest.Test(t, testStreamsCreateConcurrency)
+}
+func testStreamsCreateConcurrency(t *testing.T) {
cli, srv := newLocalConnPair(t, &Config{}, &Config{})
srvdone := make(chan int)
@@ -520,6 +550,9 @@
}
func TestStreamsPTOWithImplicitStream(t *testing.T) {
+ synctest.Test(t, testStreamsPTOWithImplicitStream)
+}
+func testStreamsPTOWithImplicitStream(t *testing.T) {
ctx := canceledContext()
tc := newTestConn(t, serverSide, permissiveTransportParameters)
tc.handshake()
diff --git a/quic/conn_test.go b/quic/conn_test.go
index a5f2f61..81eeffc 100644
--- a/quic/conn_test.go
+++ b/quic/conn_test.go
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.25
+
package quic
import (
@@ -17,6 +19,7 @@
"reflect"
"strings"
"testing"
+ "testing/synctest"
"time"
"golang.org/x/net/quic/qlog"
@@ -27,7 +30,8 @@
qlogdir = flag.String("qlog", "", "write qlog logs to directory")
)
-func TestConnTestConn(t *testing.T) {
+func TestConnTestConn(t *testing.T) { synctest.Test(t, testConnTestConn) }
+func testConnTestConn(t *testing.T) {
tc := newTestConn(t, serverSide)
tc.handshake()
if got, want := tc.timeUntilEvent(), defaultMaxIdleTimeout; got != want {
@@ -40,13 +44,13 @@
})
return
}).result()
- if !ranAt.Equal(tc.endpoint.now) {
- t.Errorf("func ran on loop at %v, want %v", ranAt, tc.endpoint.now)
+ if !ranAt.Equal(time.Now()) {
+ t.Errorf("func ran on loop at %v, want %v", ranAt, time.Now())
}
- tc.wait()
+ synctest.Wait()
- nextTime := tc.endpoint.now.Add(defaultMaxIdleTimeout / 2)
- tc.advanceTo(nextTime)
+ nextTime := time.Now().Add(defaultMaxIdleTimeout / 2)
+ time.Sleep(time.Until(nextTime))
ranAt, _ = runAsync(tc, func(ctx context.Context) (when time.Time, _ error) {
tc.conn.runOnLoop(ctx, func(now time.Time, c *Conn) {
when = now
@@ -56,7 +60,7 @@
if !ranAt.Equal(nextTime) {
t.Errorf("func ran on loop at %v, want %v", ranAt, nextTime)
}
- tc.wait()
+ synctest.Wait()
tc.advanceToTimer()
if got := tc.conn.lifetime.state; got != connStateDone {
@@ -125,12 +129,9 @@
// A testConn is a Conn whose external interactions (sending and receiving packets,
// setting timers) can be manipulated in tests.
type testConn struct {
- t *testing.T
- conn *Conn
- endpoint *testEndpoint
- timer time.Time
- timerLastFired time.Time
- idlec chan struct{} // only accessed on the conn's loop
+ t *testing.T
+ conn *Conn
+ endpoint *testEndpoint
// Keys are distinct from the conn's keys,
// because the test may know about keys before the conn does.
@@ -183,8 +184,6 @@
// Values to set in packets sent to the conn.
sendKeyNumber int
sendKeyPhaseBit bool
-
- asyncTestState
}
type test1RTTKeys struct {
@@ -198,10 +197,6 @@
}
// newTestConn creates a Conn for testing.
-//
-// The Conn's event loop is controlled by the test,
-// allowing test code to access Conn state directly
-// by first ensuring the loop goroutine is idle.
func newTestConn(t *testing.T, side connSide, opts ...any) *testConn {
t.Helper()
config := &Config{
@@ -242,7 +237,7 @@
endpoint.configTransportParams = configTransportParams
endpoint.configTestConn = configTestConn
conn, err := endpoint.e.newConn(
- endpoint.now,
+ time.Now(),
config,
side,
cids,
@@ -252,7 +247,7 @@
t.Fatal(err)
}
tc := endpoint.conns[conn]
- tc.wait()
+ synctest.Wait()
return tc
}
@@ -306,76 +301,33 @@
return tc
}
-// advance causes time to pass.
-func (tc *testConn) advance(d time.Duration) {
- tc.t.Helper()
- tc.endpoint.advance(d)
-}
-
-// advanceTo sets the current time.
-func (tc *testConn) advanceTo(now time.Time) {
- tc.t.Helper()
- tc.endpoint.advanceTo(now)
-}
-
// advanceToTimer sets the current time to the time of the Conn's next timer event.
func (tc *testConn) advanceToTimer() {
- if tc.timer.IsZero() {
+ when := tc.nextEvent()
+ if when.IsZero() {
tc.t.Fatalf("advancing to timer, but timer is not set")
}
- tc.advanceTo(tc.timer)
-}
-
-func (tc *testConn) timerDelay() time.Duration {
- if tc.timer.IsZero() {
- return math.MaxInt64 // infinite
- }
- if tc.timer.Before(tc.endpoint.now) {
- return 0
- }
- return tc.timer.Sub(tc.endpoint.now)
+ time.Sleep(time.Until(when))
+ synctest.Wait()
}
const infiniteDuration = time.Duration(math.MaxInt64)
// timeUntilEvent returns the amount of time until the next connection event.
func (tc *testConn) timeUntilEvent() time.Duration {
- if tc.timer.IsZero() {
+ next := tc.nextEvent()
+ if next.IsZero() {
return infiniteDuration
}
- if tc.timer.Before(tc.endpoint.now) {
- return 0
- }
- return tc.timer.Sub(tc.endpoint.now)
+ return max(0, time.Until(next))
}
-// wait blocks until the conn becomes idle.
-// The conn is idle when it is blocked waiting for a packet to arrive or a timer to expire.
-// Tests shouldn't need to call wait directly.
-// testConn methods that wake the Conn event loop will call wait for them.
-func (tc *testConn) wait() {
- tc.t.Helper()
- idlec := make(chan struct{})
- fail := false
- tc.conn.sendMsg(func(now time.Time, c *Conn) {
- if tc.idlec != nil {
- tc.t.Errorf("testConn.wait called concurrently")
- fail = true
- close(idlec)
- } else {
- // nextMessage will close idlec.
- tc.idlec = idlec
- }
+func (tc *testConn) nextEvent() time.Time {
+ nextc := make(chan time.Time)
+ tc.conn.sendMsg(func(now, next time.Time, c *Conn) {
+ nextc <- next
})
- select {
- case <-idlec:
- case <-tc.conn.donec:
- // We may have async ops that can proceed now that the conn is done.
- tc.wakeAsync()
- }
- if fail {
- panic(fail)
- }
+ return <-nextc
}
func (tc *testConn) cleanup() {
@@ -498,7 +450,7 @@
// It returns nil if the Conn has no more datagrams to send at this time.
func (tc *testConn) readDatagram() *testDatagram {
tc.t.Helper()
- tc.wait()
+ synctest.Wait()
tc.sentPackets = nil
tc.sentFrames = nil
buf := tc.endpoint.read()
@@ -1103,48 +1055,10 @@
}
}
-// nextMessage is called by the Conn's event loop to request its next event.
-func (tc *testConnHooks) nextMessage(msgc chan any, timer time.Time) (now time.Time, m any) {
- tc.timer = timer
- for {
- if !timer.IsZero() && !timer.After(tc.endpoint.now) {
- if timer.Equal(tc.timerLastFired) {
- // If the connection timer fires at time T, the Conn should take some
- // action to advance the timer into the future. If the Conn reschedules
- // the timer for the same time, it isn't making progress and we have a bug.
- tc.t.Errorf("connection timer spinning; now=%v timer=%v", tc.endpoint.now, timer)
- } else {
- tc.timerLastFired = timer
- return tc.endpoint.now, timerEvent{}
- }
- }
- select {
- case m := <-msgc:
- return tc.endpoint.now, m
- default:
- }
- if !tc.wakeAsync() {
- break
- }
- }
- // If the message queue is empty, then the conn is idle.
- if tc.idlec != nil {
- idlec := tc.idlec
- tc.idlec = nil
- close(idlec)
- }
- m = <-msgc
- return tc.endpoint.now, m
-}
-
func (tc *testConnHooks) newConnID(seq int64) ([]byte, error) {
return testLocalConnID(seq), nil
}
-func (tc *testConnHooks) timeNow() time.Time {
- return tc.endpoint.now
-}
-
// testLocalConnID returns the connection ID with a given sequence number
// used by a Conn under test.
func testLocalConnID(seq int64) []byte {
diff --git a/quic/endpoint.go b/quic/endpoint.go
index 1bb9015..3d68073 100644
--- a/quic/endpoint.go
+++ b/quic/endpoint.go
@@ -36,7 +36,6 @@
}
type endpointTestHooks interface {
- timeNow() time.Time
newConn(c *Conn)
}
@@ -160,7 +159,7 @@
// Accept waits for and returns the next connection.
func (e *Endpoint) Accept(ctx context.Context) (*Conn, error) {
- return e.acceptQueue.get(ctx, nil)
+ return e.acceptQueue.get(ctx)
}
// Dial creates and returns a connection to a network address.
@@ -269,12 +268,7 @@
if len(m.b) < minimumValidPacketSize {
return
}
- var now time.Time
- if e.testHooks != nil {
- now = e.testHooks.timeNow()
- } else {
- now = time.Now()
- }
+ now := time.Now()
// Check to see if this is a stateless reset.
var token statelessResetToken
copy(token[:], m.b[len(m.b)-len(token):])
diff --git a/quic/endpoint_test.go b/quic/endpoint_test.go
index 7ec8139..6a62104 100644
--- a/quic/endpoint_test.go
+++ b/quic/endpoint_test.go
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.25
+
package quic
import (
@@ -12,8 +14,9 @@
"log/slog"
"net/netip"
"runtime"
+ "sync"
"testing"
- "time"
+ "testing/synctest"
"golang.org/x/net/quic/qlog"
)
@@ -126,22 +129,22 @@
type testEndpoint struct {
t *testing.T
e *Endpoint
- now time.Time
recvc chan *datagram
idlec chan struct{}
conns map[*Conn]*testConn
acceptQueue []*testConn
configTransportParams []func(*transportParameters)
configTestConn []func(*testConn)
- sentDatagrams [][]byte
peerTLSConn *tls.QUICConn
lastInitialDstConnID []byte // for parsing Retry packets
+
+ sentDatagramsMu sync.Mutex
+ sentDatagrams [][]byte
}
func newTestEndpoint(t *testing.T, config *Config) *testEndpoint {
te := &testEndpoint{
t: t,
- now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
recvc: make(chan *datagram),
idlec: make(chan struct{}),
conns: make(map[*Conn]*testConn),
@@ -159,16 +162,6 @@
te.e.Close(canceledContext())
}
-func (te *testEndpoint) wait() {
- select {
- case te.idlec <- struct{}{}:
- case <-te.e.closec:
- }
- for _, tc := range te.conns {
- tc.wait()
- }
-}
-
// accept returns a server connection from the endpoint.
// Unlike Endpoint.Accept, connections are available as soon as they are created.
func (te *testEndpoint) accept() *testConn {
@@ -182,7 +175,7 @@
func (te *testEndpoint) write(d *datagram) {
te.recvc <- d
- te.wait()
+ synctest.Wait()
}
var testClientAddr = netip.MustParseAddrPort("10.0.0.1:8000")
@@ -241,7 +234,9 @@
func (te *testEndpoint) read() []byte {
te.t.Helper()
- te.wait()
+ synctest.Wait()
+ te.sentDatagramsMu.Lock()
+ defer te.sentDatagramsMu.Unlock()
if len(te.sentDatagrams) == 0 {
return nil
}
@@ -279,34 +274,9 @@
}
}
-// advance causes time to pass.
-func (te *testEndpoint) advance(d time.Duration) {
- te.t.Helper()
- te.advanceTo(te.now.Add(d))
-}
-
-// advanceTo sets the current time.
-func (te *testEndpoint) advanceTo(now time.Time) {
- te.t.Helper()
- if te.now.After(now) {
- te.t.Fatalf("time moved backwards: %v -> %v", te.now, now)
- }
- te.now = now
- for _, tc := range te.conns {
- if !tc.timer.After(te.now) {
- tc.conn.sendMsg(timerEvent{})
- tc.wait()
- }
- }
-}
-
// testEndpointHooks implements endpointTestHooks.
type testEndpointHooks testEndpoint
-func (te *testEndpointHooks) timeNow() time.Time {
- return te.now
-}
-
func (te *testEndpointHooks) newConn(c *Conn) {
tc := newTestConnForConn(te.t, (*testEndpoint)(te), c)
te.conns[c] = tc
@@ -338,6 +308,8 @@
}
func (te *testEndpointUDPConn) Write(dgram datagram) error {
+ te.sentDatagramsMu.Lock()
+ defer te.sentDatagramsMu.Unlock()
te.sentDatagrams = append(te.sentDatagrams, append([]byte(nil), dgram.b...))
return nil
}
diff --git a/quic/gate.go b/quic/gate.go
index 1f570bb..b8b8605 100644
--- a/quic/gate.go
+++ b/quic/gate.go
@@ -46,10 +46,7 @@
// waitAndLock waits until the condition is set before acquiring the gate.
// If the context expires, waitAndLock returns an error and does not acquire the gate.
-func (g *gate) waitAndLock(ctx context.Context, testHooks connTestHooks) error {
- if testHooks != nil {
- return testHooks.waitUntil(ctx, g.lockIfSet)
- }
+func (g *gate) waitAndLock(ctx context.Context) error {
select {
case <-g.set:
return nil
diff --git a/quic/gate_test.go b/quic/gate_test.go
index 54f7a8a..59c157d 100644
--- a/quic/gate_test.go
+++ b/quic/gate_test.go
@@ -47,7 +47,7 @@
time.Sleep(1 * time.Millisecond)
cancel()
}()
- if err := g.waitAndLock(ctx, nil); err != context.Canceled {
+ if err := g.waitAndLock(ctx); err != context.Canceled {
t.Errorf("g.waitAndLock() = %v, want context.Canceled", err)
}
// waitAndLock succeeds
@@ -58,7 +58,7 @@
set = true
g.unlock(true)
}()
- if err := g.waitAndLock(context.Background(), nil); err != nil {
+ if err := g.waitAndLock(context.Background()); err != nil {
t.Errorf("g.waitAndLock() = %v, want nil", err)
}
if !set {
@@ -66,7 +66,7 @@
}
g.unlock(true)
// waitAndLock succeeds when the gate is set and the context is canceled
- if err := g.waitAndLock(ctx, nil); err != nil {
+ if err := g.waitAndLock(ctx); err != nil {
t.Errorf("g.waitAndLock() = %v, want nil", err)
}
}
@@ -89,5 +89,5 @@
g.lock()
defer g.unlockFunc(func() bool { return true })
}()
- g.waitAndLock(context.Background(), nil)
+ g.waitAndLock(context.Background())
}
diff --git a/quic/idle_test.go b/quic/idle_test.go
index 29d3bd1..d9ae16a 100644
--- a/quic/idle_test.go
+++ b/quic/idle_test.go
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.25
+
package quic
import (
@@ -9,10 +11,14 @@
"crypto/tls"
"fmt"
"testing"
+ "testing/synctest"
"time"
)
func TestHandshakeTimeoutExpiresServer(t *testing.T) {
+ synctest.Test(t, testHandshakeTimeoutExpiresServer)
+}
+func testHandshakeTimeoutExpiresServer(t *testing.T) {
const timeout = 5 * time.Second
tc := newTestConn(t, serverSide, func(c *Config) {
c.HandshakeTimeout = timeout
@@ -32,18 +38,18 @@
packetTypeHandshake, debugFrameCrypto{})
tc.writeAckForAll()
- if got, want := tc.timerDelay(), timeout; got != want {
+ if got, want := tc.timeUntilEvent(), timeout; got != want {
t.Errorf("connection timer = %v, want %v (handshake timeout)", got, want)
}
// Client sends a packet, but this does not extend the handshake timer.
- tc.advance(1 * time.Second)
+ time.Sleep(1 * time.Second)
tc.writeFrames(packetTypeHandshake, debugFrameCrypto{
data: tc.cryptoDataIn[tls.QUICEncryptionLevelHandshake][:1], // partial data
})
tc.wantIdle("handshake is not complete")
- tc.advance(timeout - 1*time.Second)
+ time.Sleep(timeout - 1*time.Second)
tc.wantFrame("server closes connection after handshake timeout",
packetTypeHandshake, debugFrameConnectionCloseTransport{
code: errConnectionRefused,
@@ -51,6 +57,9 @@
}
func TestHandshakeTimeoutExpiresClient(t *testing.T) {
+ synctest.Test(t, testHandshakeTimeoutExpiresClient)
+}
+func testHandshakeTimeoutExpiresClient(t *testing.T) {
const timeout = 5 * time.Second
tc := newTestConn(t, clientSide, func(c *Config) {
c.HandshakeTimeout = timeout
@@ -77,10 +86,10 @@
tc.writeAckForAll()
tc.wantIdle("client is waiting for end of handshake")
- if got, want := tc.timerDelay(), timeout; got != want {
+ if got, want := tc.timeUntilEvent(), timeout; got != want {
t.Errorf("connection timer = %v, want %v (handshake timeout)", got, want)
}
- tc.advance(timeout)
+ time.Sleep(timeout)
tc.wantFrame("client closes connection after handshake timeout",
packetTypeHandshake, debugFrameConnectionCloseTransport{
code: errConnectionRefused,
@@ -110,7 +119,7 @@
wantTimeout: 10 * time.Second,
}} {
name := fmt.Sprintf("local=%v/peer=%v", test.localMaxIdleTimeout, test.peerMaxIdleTimeout)
- t.Run(name, func(t *testing.T) {
+ synctestSubtest(t, name, func(t *testing.T) {
tc := newTestConn(t, serverSide, func(p *transportParameters) {
p.maxIdleTimeout = test.peerMaxIdleTimeout
}, func(c *Config) {
@@ -120,13 +129,13 @@
if got, want := tc.timeUntilEvent(), test.wantTimeout; got != want {
t.Errorf("new conn timeout=%v, want %v (idle timeout)", got, want)
}
- tc.advance(test.wantTimeout - 1)
+ time.Sleep(test.wantTimeout - 1)
tc.wantIdle("connection is idle and alive prior to timeout")
ctx := canceledContext()
if err := tc.conn.Wait(ctx); err != context.Canceled {
t.Fatalf("conn.Wait() = %v, want Canceled", err)
}
- tc.advance(1)
+ time.Sleep(1)
tc.wantIdle("connection exits after timeout")
if err := tc.conn.Wait(ctx); err != errIdleTimeout {
t.Fatalf("conn.Wait() = %v, want errIdleTimeout", err)
@@ -154,7 +163,7 @@
wantTimeout: 30 * time.Second,
}} {
name := fmt.Sprintf("idle_timeout=%v/keepalive=%v", test.idleTimeout, test.keepAlive)
- t.Run(name, func(t *testing.T) {
+ synctestSubtest(t, name, func(t *testing.T) {
tc := newTestConn(t, serverSide, func(c *Config) {
c.MaxIdleTimeout = test.idleTimeout
c.KeepAlivePeriod = test.keepAlive
@@ -163,9 +172,9 @@
if got, want := tc.timeUntilEvent(), test.wantTimeout; got != want {
t.Errorf("new conn timeout=%v, want %v (keepalive timeout)", got, want)
}
- tc.advance(test.wantTimeout - 1)
+ time.Sleep(test.wantTimeout - 1)
tc.wantIdle("connection is idle prior to timeout")
- tc.advance(1)
+ time.Sleep(1)
tc.wantFrameType("keep-alive ping is sent", packetType1RTT,
debugFramePing{})
})
@@ -173,6 +182,9 @@
}
func TestIdleLongTermKeepAliveSent(t *testing.T) {
+ synctest.Test(t, testIdleLongTermKeepAliveSent)
+}
+func testIdleLongTermKeepAliveSent(t *testing.T) {
// This test examines a connection sitting idle and sending periodic keep-alive pings.
const keepAlivePeriod = 30 * time.Second
tc := newTestConn(t, clientSide, func(c *Config) {
@@ -191,7 +203,7 @@
if got, want := tc.timeUntilEvent(), keepAlivePeriod; got != want {
t.Errorf("i=%v conn timeout=%v, want %v (keepalive timeout)", i, got, want)
}
- tc.advance(keepAlivePeriod)
+ time.Sleep(keepAlivePeriod)
tc.wantFrameType("keep-alive ping is sent", packetType1RTT,
debugFramePing{})
tc.writeAckForAll()
@@ -199,6 +211,9 @@
}
func TestIdleLongTermKeepAliveReceived(t *testing.T) {
+ synctest.Test(t, testIdleLongTermKeepAliveReceived)
+}
+func testIdleLongTermKeepAliveReceived(t *testing.T) {
// This test examines a connection sitting idle, but receiving periodic peer
// traffic to keep the connection alive.
const idleTimeout = 30 * time.Second
@@ -207,7 +222,7 @@
})
tc.handshake()
for i := 0; i < 10; i++ {
- tc.advance(idleTimeout - 1*time.Second)
+ time.Sleep(idleTimeout - 1*time.Second)
tc.writeFrames(packetType1RTT, debugFramePing{})
if got, want := tc.timeUntilEvent(), maxAckDelay-timerGranularity; got != want {
t.Errorf("i=%v conn timeout=%v, want %v (max_ack_delay)", i, got, want)
diff --git a/quic/key_update_test.go b/quic/key_update_test.go
index 2daf7db..7a02e84 100644
--- a/quic/key_update_test.go
+++ b/quic/key_update_test.go
@@ -2,13 +2,19 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.25
+
package quic
import (
"testing"
+ "testing/synctest"
)
func TestKeyUpdatePeerUpdates(t *testing.T) {
+ synctest.Test(t, testKeyUpdatePeerUpdates)
+}
+func testKeyUpdatePeerUpdates(t *testing.T) {
tc := newTestConn(t, serverSide)
tc.handshake()
tc.ignoreFrames = nil // ignore nothing
@@ -56,6 +62,9 @@
}
func TestKeyUpdateAcceptPreviousPhaseKeys(t *testing.T) {
+ synctest.Test(t, testKeyUpdateAcceptPreviousPhaseKeys)
+}
+func testKeyUpdateAcceptPreviousPhaseKeys(t *testing.T) {
// "An endpoint SHOULD retain old keys for some time after
// unprotecting a packet sent using the new keys."
// https://www.rfc-editor.org/rfc/rfc9001#section-6.1-8
@@ -112,6 +121,9 @@
}
func TestKeyUpdateRejectPacketFromPriorPhase(t *testing.T) {
+ synctest.Test(t, testKeyUpdateRejectPacketFromPriorPhase)
+}
+func testKeyUpdateRejectPacketFromPriorPhase(t *testing.T) {
// "Packets with higher packet numbers MUST be protected with either
// the same or newer packet protection keys than packets with lower packet numbers."
// https://www.rfc-editor.org/rfc/rfc9001#section-6.4-2
@@ -161,6 +173,9 @@
}
func TestKeyUpdateLocallyInitiated(t *testing.T) {
+ synctest.Test(t, testKeyUpdateLocallyInitiated)
+}
+func testKeyUpdateLocallyInitiated(t *testing.T) {
const updateAfter = 4 // initiate key update after 1-RTT packet 4
tc := newTestConn(t, serverSide)
tc.conn.keysAppData.updateAfter = updateAfter
diff --git a/quic/packet_codec_test.go b/quic/packet_codec_test.go
index 4ae22b3..d49f0ea 100644
--- a/quic/packet_codec_test.go
+++ b/quic/packet_codec_test.go
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.25
+
package quic
import (
diff --git a/quic/path_test.go b/quic/path_test.go
index 60ff51e..16dd9fc 100644
--- a/quic/path_test.go
+++ b/quic/path_test.go
@@ -2,10 +2,13 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.25
+
package quic
import (
"testing"
+ "testing/synctest"
)
func TestPathChallengeReceived(t *testing.T) {
@@ -22,30 +25,35 @@
padTo: 1200,
wantPadding: 1200,
}} {
- // "The recipient of [a PATH_CHALLENGE] frame MUST generate
- // a PATH_RESPONSE frame [...] containing the same Data value."
- // https://www.rfc-editor.org/rfc/rfc9000.html#section-19.17-7
- tc := newTestConn(t, clientSide)
- tc.handshake()
- tc.ignoreFrame(frameTypeAck)
- data := pathChallengeData{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}
- tc.writeFrames(packetType1RTT, debugFramePathChallenge{
- data: data,
- }, debugFramePadding{
- to: test.padTo,
- })
- tc.wantFrame("response to PATH_CHALLENGE",
- packetType1RTT, debugFramePathResponse{
+ synctestSubtest(t, test.name, func(t *testing.T) {
+ // "The recipient of [a PATH_CHALLENGE] frame MUST generate
+ // a PATH_RESPONSE frame [...] containing the same Data value."
+ // https://www.rfc-editor.org/rfc/rfc9000.html#section-19.17-7
+ tc := newTestConn(t, clientSide)
+ tc.handshake()
+ tc.ignoreFrame(frameTypeAck)
+ data := pathChallengeData{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}
+ tc.writeFrames(packetType1RTT, debugFramePathChallenge{
data: data,
+ }, debugFramePadding{
+ to: test.padTo,
})
- if got, want := tc.lastDatagram.paddedSize, test.wantPadding; got != want {
- t.Errorf("PATH_RESPONSE expanded to %v bytes, want %v", got, want)
- }
- tc.wantIdle("connection is idle")
+ tc.wantFrame("response to PATH_CHALLENGE",
+ packetType1RTT, debugFramePathResponse{
+ data: data,
+ })
+ if got, want := tc.lastDatagram.paddedSize, test.wantPadding; got != want {
+ t.Errorf("PATH_RESPONSE expanded to %v bytes, want %v", got, want)
+ }
+ tc.wantIdle("connection is idle")
+ })
}
}
func TestPathResponseMismatchReceived(t *testing.T) {
+ synctest.Test(t, testPathResponseMismatchReceived)
+}
+func testPathResponseMismatchReceived(t *testing.T) {
// "If the content of a PATH_RESPONSE frame does not match the content of
// a PATH_CHALLENGE frame previously sent by the endpoint,
// the endpoint MAY generate a connection error of type PROTOCOL_VIOLATION."
diff --git a/quic/ping_test.go b/quic/ping_test.go
index a8e6b61..4589a6c 100644
--- a/quic/ping_test.go
+++ b/quic/ping_test.go
@@ -2,11 +2,19 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.25
+
package quic
-import "testing"
+import (
+ "testing"
+ "testing/synctest"
+)
func TestPing(t *testing.T) {
+ synctest.Test(t, testPing)
+}
+func testPing(t *testing.T) {
tc := newTestConn(t, clientSide)
tc.handshake()
@@ -22,6 +30,9 @@
}
func TestAck(t *testing.T) {
+ synctest.Test(t, testAck)
+}
+func testAck(t *testing.T) {
tc := newTestConn(t, serverSide)
tc.handshake()
diff --git a/quic/qlog_test.go b/quic/qlog_test.go
index 08c2a77..47e4671 100644
--- a/quic/qlog_test.go
+++ b/quic/qlog_test.go
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.25
+
package quic
import (
@@ -12,14 +14,16 @@
"io"
"log/slog"
"reflect"
+ "sync"
"testing"
+ "testing/synctest"
"time"
"golang.org/x/net/quic/qlog"
)
func TestQLogHandshake(t *testing.T) {
- testSides(t, "", func(t *testing.T, side connSide) {
+ testSidesSynctest(t, "", func(t *testing.T, side connSide) {
qr := &qlogRecord{}
tc := newTestConn(t, side, qr.config)
tc.handshake()
@@ -55,6 +59,9 @@
}
func TestQLogPacketFrames(t *testing.T) {
+ synctest.Test(t, testQLogPacketFrames)
+}
+func testQLogPacketFrames(t *testing.T) {
qr := &qlogRecord{}
tc := newTestConn(t, clientSide, qr.config)
tc.handshake()
@@ -111,7 +118,7 @@
tc.ignoreFrame(frameTypeCrypto)
tc.ignoreFrame(frameTypeAck)
tc.ignoreFrame(frameTypePing)
- tc.advance(5 * time.Second)
+ time.Sleep(5 * time.Second)
},
}, {
trigger: "idle_timeout",
@@ -122,7 +129,7 @@
},
f: func(tc *testConn) {
tc.handshake()
- tc.advance(5 * time.Second)
+ time.Sleep(5 * time.Second)
},
}, {
trigger: "error",
@@ -134,7 +141,7 @@
tc.conn.Abort(nil)
},
}} {
- t.Run(test.trigger, func(t *testing.T) {
+ synctestSubtest(t, test.trigger, func(t *testing.T) {
qr := &qlogRecord{}
tc := newTestConn(t, clientSide, append(test.connOpts, qr.config)...)
test.f(tc)
@@ -147,7 +154,7 @@
t.Fatalf("unexpected frame: %v", fr)
}
tc.wantIdle("connection should be idle while closing")
- tc.advance(5 * time.Second) // long enough for the drain timer to expire
+ time.Sleep(5 * time.Second) // long enough for the drain timer to expire
qr.wantEvents(t, jsonEvent{
"name": "connectivity:connection_closed",
"data": map[string]any{
@@ -159,6 +166,9 @@
}
func TestQLogRecovery(t *testing.T) {
+ synctest.Test(t, testQLogRecovery)
+}
+func testQLogRecovery(t *testing.T) {
qr := &qlogRecord{}
tc, s := newTestConnAndLocalStream(t, clientSide, uniStream,
permissiveTransportParameters, qr.config)
@@ -198,6 +208,9 @@
}
func TestQLogLoss(t *testing.T) {
+ synctest.Test(t, testQLogLoss)
+}
+func testQLogLoss(t *testing.T) {
qr := &qlogRecord{}
tc, s := newTestConnAndLocalStream(t, clientSide, uniStream,
permissiveTransportParameters, qr.config)
@@ -230,6 +243,9 @@
}
func TestQLogPacketDropped(t *testing.T) {
+ synctest.Test(t, testQLogPacketDropped)
+}
+func testQLogPacketDropped(t *testing.T) {
qr := &qlogRecord{}
tc := newTestConn(t, clientSide, permissiveTransportParameters, qr.config)
tc.handshake()
@@ -324,10 +340,13 @@
// A qlogRecord records events.
type qlogRecord struct {
+ mu sync.Mutex
ev []jsonEvent
}
func (q *qlogRecord) Write(b []byte) (int, error) {
+ q.mu.Lock()
+ defer q.mu.Unlock()
// This relies on the property that the Handler always makes one Write call per event.
if len(b) < 1 || b[0] != 0x1e {
panic(fmt.Errorf("trace Write should start with record separator, got %q", string(b)))
@@ -355,6 +374,8 @@
// wantEvents checks that every event in want occurs in the order specified.
func (q *qlogRecord) wantEvents(t *testing.T, want ...jsonEvent) {
t.Helper()
+ q.mu.Lock()
+ defer q.mu.Unlock()
got := q.ev
if !jsonPartialEqual(got, want) {
t.Fatalf("got events:\n%v\n\nwant events:\n%v", got, want)
diff --git a/quic/queue.go b/quic/queue.go
index 8b90ae7..f2712f4 100644
--- a/quic/queue.go
+++ b/quic/queue.go
@@ -42,9 +42,9 @@
// get removes the first item from the queue, blocking until ctx is done, an item is available,
// or the queue is closed.
-func (q *queue[T]) get(ctx context.Context, testHooks connTestHooks) (T, error) {
+func (q *queue[T]) get(ctx context.Context) (T, error) {
var zero T
- if err := q.gate.waitAndLock(ctx, testHooks); err != nil {
+ if err := q.gate.waitAndLock(ctx); err != nil {
return zero, err
}
defer q.unlock()
diff --git a/quic/queue_test.go b/quic/queue_test.go
index b583521..a3907f3 100644
--- a/quic/queue_test.go
+++ b/quic/queue_test.go
@@ -16,7 +16,7 @@
cancel()
q := newQueue[int]()
- if got, err := q.get(nonblocking, nil); err != context.Canceled {
+ if got, err := q.get(nonblocking); err != context.Canceled {
t.Fatalf("q.get() = %v, %v, want nil, context.Canceled", got, err)
}
@@ -26,13 +26,13 @@
if !q.put(2) {
t.Fatalf("q.put(2) = false, want true")
}
- if got, err := q.get(nonblocking, nil); got != 1 || err != nil {
+ if got, err := q.get(nonblocking); got != 1 || err != nil {
t.Fatalf("q.get() = %v, %v, want 1, nil", got, err)
}
- if got, err := q.get(nonblocking, nil); got != 2 || err != nil {
+ if got, err := q.get(nonblocking); got != 2 || err != nil {
t.Fatalf("q.get() = %v, %v, want 2, nil", got, err)
}
- if got, err := q.get(nonblocking, nil); err != context.Canceled {
+ if got, err := q.get(nonblocking); err != context.Canceled {
t.Fatalf("q.get() = %v, %v, want nil, context.Canceled", got, err)
}
@@ -40,7 +40,7 @@
time.Sleep(1 * time.Millisecond)
q.put(3)
}()
- if got, err := q.get(context.Background(), nil); got != 3 || err != nil {
+ if got, err := q.get(context.Background()); got != 3 || err != nil {
t.Fatalf("q.get() = %v, %v, want 3, nil", got, err)
}
@@ -48,7 +48,7 @@
t.Fatalf("q.put(2) = false, want true")
}
q.close(io.EOF)
- if got, err := q.get(context.Background(), nil); got != 0 || err != io.EOF {
+ if got, err := q.get(context.Background()); got != 0 || err != io.EOF {
t.Fatalf("q.get() = %v, %v, want 0, io.EOF", got, err)
}
if q.put(5) {
diff --git a/quic/quic_test.go b/quic/quic_test.go
index 071003e..cdcc0d7 100644
--- a/quic/quic_test.go
+++ b/quic/quic_test.go
@@ -2,10 +2,13 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.25
+
package quic
import (
"testing"
+ "testing/synctest"
)
func testSides(t *testing.T, name string, f func(*testing.T, connSide)) {
@@ -16,6 +19,16 @@
t.Run(name+"client", func(t *testing.T) { f(t, clientSide) })
}
+func testSidesSynctest(t *testing.T, name string, f func(*testing.T, connSide)) {
+ t.Helper()
+ testSides(t, name, func(t *testing.T, side connSide) {
+ t.Helper()
+ synctest.Test(t, func(t *testing.T) {
+ f(t, side)
+ })
+ })
+}
+
func testStreamTypes(t *testing.T, name string, f func(*testing.T, streamType)) {
if name != "" {
name += "/"
@@ -24,6 +37,16 @@
t.Run(name+"uni", func(t *testing.T) { f(t, uniStream) })
}
+func testStreamTypesSynctest(t *testing.T, name string, f func(*testing.T, streamType)) {
+ t.Helper()
+ testStreamTypes(t, name, func(t *testing.T, stype streamType) {
+ t.Helper()
+ synctest.Test(t, func(t *testing.T) {
+ f(t, stype)
+ })
+ })
+}
+
func testSidesAndStreamTypes(t *testing.T, name string, f func(*testing.T, connSide, streamType)) {
if name != "" {
name += "/"
@@ -33,3 +56,20 @@
t.Run(name+"server/uni", func(t *testing.T) { f(t, serverSide, uniStream) })
t.Run(name+"client/uni", func(t *testing.T) { f(t, clientSide, uniStream) })
}
+
+func testSidesAndStreamTypesSynctest(t *testing.T, name string, f func(*testing.T, connSide, streamType)) {
+ t.Helper()
+ testSidesAndStreamTypes(t, name, func(t *testing.T, side connSide, stype streamType) {
+ t.Helper()
+ synctest.Test(t, func(t *testing.T) {
+ f(t, side, stype)
+ })
+ })
+}
+
+func synctestSubtest(t *testing.T, name string, f func(t *testing.T)) {
+ t.Run(name, func(t *testing.T) {
+ t.Helper()
+ synctest.Test(t, f)
+ })
+}
diff --git a/quic/retry_test.go b/quic/retry_test.go
index d6f0254..7a4481c 100644
--- a/quic/retry_test.go
+++ b/quic/retry_test.go
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.25
+
package quic
import (
@@ -10,6 +12,7 @@
"crypto/tls"
"net/netip"
"testing"
+ "testing/synctest"
"time"
)
@@ -77,9 +80,12 @@
}
func TestRetryServerSucceeds(t *testing.T) {
+ synctest.Test(t, testRetryServerSucceeds)
+}
+func testRetryServerSucceeds(t *testing.T) {
rt := newRetryServerTest(t)
te := rt.te
- te.advance(retryTokenValidityPeriod)
+ time.Sleep(retryTokenValidityPeriod)
te.writeDatagram(&testDatagram{
packets: []*testPacket{{
ptype: packetTypeInitial,
@@ -117,6 +123,9 @@
}
func TestRetryServerTokenInvalid(t *testing.T) {
+ synctest.Test(t, testRetryServerTokenInvalid)
+}
+func testRetryServerTokenInvalid(t *testing.T) {
// "If a server receives a client Initial that contains an invalid Retry token [...]
// the server SHOULD immediately close [...] the connection with an
// INVALID_TOKEN error."
@@ -147,11 +156,14 @@
}
func TestRetryServerTokenTooOld(t *testing.T) {
+ synctest.Test(t, testRetryServerTokenTooOld)
+}
+func testRetryServerTokenTooOld(t *testing.T) {
// "[...] a token SHOULD have an expiration time [...]"
// https://www.rfc-editor.org/rfc/rfc9000#section-8.1.3-3
rt := newRetryServerTest(t)
te := rt.te
- te.advance(retryTokenValidityPeriod + time.Second)
+ time.Sleep(retryTokenValidityPeriod + time.Second)
te.writeDatagram(&testDatagram{
packets: []*testPacket{{
ptype: packetTypeInitial,
@@ -176,6 +188,9 @@
}
func TestRetryServerTokenWrongIP(t *testing.T) {
+ synctest.Test(t, testRetryServerTokenWrongIP)
+}
+func testRetryServerTokenWrongIP(t *testing.T) {
// "Tokens sent in Retry packets SHOULD include information that allows the server
// to verify that the source IP address and port in client packets remain constant."
// https://www.rfc-editor.org/rfc/rfc9000#section-8.1.4-3
@@ -206,6 +221,9 @@
}
func TestRetryServerIgnoresRetry(t *testing.T) {
+ synctest.Test(t, testRetryServerIgnoresRetry)
+}
+func testRetryServerIgnoresRetry(t *testing.T) {
tc := newTestConn(t, serverSide)
tc.handshake()
tc.write(&testDatagram{
@@ -225,6 +243,9 @@
}
func TestRetryClientSuccess(t *testing.T) {
+ synctest.Test(t, testRetryClientSuccess)
+}
+func testRetryClientSuccess(t *testing.T) {
// "This token MUST be repeated by the client in all Initial packets it sends
// for that connection after it receives the Retry packet."
// https://www.rfc-editor.org/rfc/rfc9000#section-8.1.2-1
@@ -323,7 +344,7 @@
p.retrySrcConnID = []byte("invalid")
},
}} {
- t.Run(test.name, func(t *testing.T) {
+ synctestSubtest(t, test.name, func(t *testing.T) {
tc := newTestConn(t, clientSide,
func(p *transportParameters) {
p.initialSrcConnID = initialSrcConnID
@@ -367,6 +388,9 @@
}
func TestRetryClientIgnoresRetryAfterReceivingPacket(t *testing.T) {
+ synctest.Test(t, testRetryClientIgnoresRetryAfterReceivingPacket)
+}
+func testRetryClientIgnoresRetryAfterReceivingPacket(t *testing.T) {
// "After the client has received and processed an Initial or Retry packet
// from the server, it MUST discard any subsequent Retry packets that it receives."
// https://www.rfc-editor.org/rfc/rfc9000#section-17.2.5.2-1
@@ -401,6 +425,9 @@
}
func TestRetryClientIgnoresRetryAfterReceivingRetry(t *testing.T) {
+ synctest.Test(t, testRetryClientIgnoresRetryAfterReceivingRetry)
+}
+func testRetryClientIgnoresRetryAfterReceivingRetry(t *testing.T) {
// "After the client has received and processed an Initial or Retry packet
// from the server, it MUST discard any subsequent Retry packets that it receives."
// https://www.rfc-editor.org/rfc/rfc9000#section-17.2.5.2-1
@@ -424,6 +451,9 @@
}
func TestRetryClientIgnoresRetryWithInvalidIntegrityTag(t *testing.T) {
+ synctest.Test(t, testRetryClientIgnoresRetryWithInvalidIntegrityTag)
+}
+func testRetryClientIgnoresRetryWithInvalidIntegrityTag(t *testing.T) {
tc := newTestConn(t, clientSide)
tc.wantFrameType("client Initial CRYPTO data",
packetTypeInitial, debugFrameCrypto{})
@@ -441,6 +471,9 @@
}
func TestRetryClientIgnoresRetryWithZeroLengthToken(t *testing.T) {
+ synctest.Test(t, testRetryClientIgnoresRetryWithZeroLengthToken)
+}
+func testRetryClientIgnoresRetryWithZeroLengthToken(t *testing.T) {
// "A client MUST discard a Retry packet with a zero-length Retry Token field."
// https://www.rfc-editor.org/rfc/rfc9000#section-17.2.5.2-2
tc := newTestConn(t, clientSide)
diff --git a/quic/skip_test.go b/quic/skip_test.go
index 1fcb735..2c33378 100644
--- a/quic/skip_test.go
+++ b/quic/skip_test.go
@@ -2,11 +2,19 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.25
+
package quic
-import "testing"
+import (
+ "testing"
+ "testing/synctest"
+)
func TestSkipPackets(t *testing.T) {
+ synctest.Test(t, testSkipPackets)
+}
+func testSkipPackets(t *testing.T) {
tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, permissiveTransportParameters)
connWritesPacket := func() {
s.WriteByte(0)
@@ -39,6 +47,9 @@
}
func TestSkipAckForSkippedPacket(t *testing.T) {
+ synctest.Test(t, testSkipAckForSkippedPacket)
+}
+func testSkipAckForSkippedPacket(t *testing.T) {
tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, permissiveTransportParameters)
// Cause the connection to send packets until it skips a packet number.
diff --git a/quic/stateless_reset_test.go b/quic/stateless_reset_test.go
index 33d467a..9473750 100644
--- a/quic/stateless_reset_test.go
+++ b/quic/stateless_reset_test.go
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.25
+
package quic
import (
@@ -12,10 +14,14 @@
"errors"
"net/netip"
"testing"
+ "testing/synctest"
"time"
)
func TestStatelessResetClientSendsStatelessResetTokenTransportParameter(t *testing.T) {
+ synctest.Test(t, testStatelessResetClientSendsStatelessResetTokenTransportParameter)
+}
+func testStatelessResetClientSendsStatelessResetTokenTransportParameter(t *testing.T) {
// "[The stateless_reset_token] transport parameter MUST NOT be sent by a client [...]"
// https://www.rfc-editor.org/rfc/rfc9000#section-18.2-4.6.1
resetToken := testPeerStatelessResetToken(0)
@@ -61,6 +67,9 @@
}
func TestStatelessResetSentSizes(t *testing.T) {
+ synctest.Test(t, testStatelessResetSentSizes)
+}
+func testStatelessResetSentSizes(t *testing.T) {
config := &Config{
TLSConfig: newTestTLSConfig(serverSide),
StatelessResetKey: testStatelessResetKey,
@@ -126,6 +135,9 @@
}
func TestStatelessResetSuccessfulNewConnectionID(t *testing.T) {
+ synctest.Test(t, testStatelessResetSuccessfulNewConnectionID)
+}
+func testStatelessResetSuccessfulNewConnectionID(t *testing.T) {
// "[...] Stateless Reset Token field values from [...] NEW_CONNECTION_ID frames [...]"
// https://www.rfc-editor.org/rfc/rfc9000#section-10.3.1-1
qr := &qlogRecord{}
@@ -155,7 +167,7 @@
t.Errorf("conn.Wait() = %v, want errStatelessReset", err)
}
tc.wantIdle("closed connection is idle in draining")
- tc.advance(1 * time.Second) // long enough to exit the draining state
+ time.Sleep(1 * time.Second) // long enough to exit the draining state
tc.wantIdle("closed connection is idle after draining")
qr.wantEvents(t, jsonEvent{
@@ -167,6 +179,9 @@
}
func TestStatelessResetSuccessfulTransportParameter(t *testing.T) {
+ synctest.Test(t, testStatelessResetSuccessfulTransportParameter)
+}
+func testStatelessResetSuccessfulTransportParameter(t *testing.T) {
// "[...] Stateless Reset Token field values from [...]
// the server's transport parameters [...]"
// https://www.rfc-editor.org/rfc/rfc9000#section-10.3.1-1
@@ -229,7 +244,7 @@
}, testLocalConnID(0)...),
size: 100,
}} {
- t.Run(test.name, func(t *testing.T) {
+ synctestSubtest(t, test.name, func(t *testing.T) {
resetToken := testPeerStatelessResetToken(0)
tc := newTestConn(t, clientSide, func(p *transportParameters) {
p.statelessResetToken = resetToken[:]
@@ -252,6 +267,9 @@
}
func TestStatelessResetRetiredConnID(t *testing.T) {
+ synctest.Test(t, testStatelessResetRetiredConnID)
+}
+func testStatelessResetRetiredConnID(t *testing.T) {
// "An endpoint MUST NOT check for any stateless reset tokens [...]
// for connection IDs that have been retired."
// https://www.rfc-editor.org/rfc/rfc9000#section-10.3.1-3
diff --git a/quic/stream.go b/quic/stream.go
index b20cfe7..4c63207 100644
--- a/quic/stream.go
+++ b/quic/stream.go
@@ -236,7 +236,7 @@
s.inbufoff += n
return n, nil
}
- if err := s.ingate.waitAndLock(s.inctx, s.conn.testHooks); err != nil {
+ if err := s.ingate.waitAndLock(s.inctx); err != nil {
return 0, err
}
if s.inbufoff > 0 {
@@ -350,7 +350,7 @@
if len(b) > 0 && !canWrite {
// Our send buffer is full. Wait for the peer to ack some data.
s.outUnlock()
- if err := s.outgate.waitAndLock(s.outctx, s.conn.testHooks); err != nil {
+ if err := s.outgate.waitAndLock(s.outctx); err != nil {
return n, err
}
// Successfully returning from waitAndLockGate means we are no longer
diff --git a/quic/stream_limits.go b/quic/stream_limits.go
index ed31c36..f1abcae 100644
--- a/quic/stream_limits.go
+++ b/quic/stream_limits.go
@@ -29,7 +29,7 @@
// open creates a new local stream, blocking until MAX_STREAMS quota is available.
func (lim *localStreamLimits) open(ctx context.Context, c *Conn) (num int64, err error) {
// TODO: Send a STREAMS_BLOCKED when blocked.
- if err := lim.gate.waitAndLock(ctx, c.testHooks); err != nil {
+ if err := lim.gate.waitAndLock(ctx); err != nil {
return 0, err
}
if lim.opened < 0 {
diff --git a/quic/stream_limits_test.go b/quic/stream_limits_test.go
index ad63411..d62b29b 100644
--- a/quic/stream_limits_test.go
+++ b/quic/stream_limits_test.go
@@ -2,19 +2,22 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.25
+
package quic
import (
"context"
"crypto/tls"
"testing"
+ "testing/synctest"
)
func TestStreamLimitNewStreamBlocked(t *testing.T) {
// "An endpoint that receives a frame with a stream ID exceeding the limit
// it has sent MUST treat this as a connection error of type STREAM_LIMIT_ERROR [...]"
// https://www.rfc-editor.org/rfc/rfc9000#section-4.6-3
- testStreamTypes(t, "", func(t *testing.T, styp streamType) {
+ testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
ctx := canceledContext()
tc := newTestConn(t, clientSide,
permissiveTransportParameters,
@@ -46,7 +49,7 @@
func TestStreamLimitMaxStreamsDecreases(t *testing.T) {
// "MAX_STREAMS frames that do not increase the stream limit MUST be ignored."
// https://www.rfc-editor.org/rfc/rfc9000#section-4.6-4
- testStreamTypes(t, "", func(t *testing.T, styp streamType) {
+ testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
ctx := canceledContext()
tc := newTestConn(t, clientSide,
permissiveTransportParameters,
@@ -77,7 +80,7 @@
}
func TestStreamLimitViolated(t *testing.T) {
- testStreamTypes(t, "", func(t *testing.T, styp streamType) {
+ testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
tc := newTestConn(t, serverSide,
func(c *Config) {
if styp == bidiStream {
@@ -104,7 +107,7 @@
}
func TestStreamLimitImplicitStreams(t *testing.T) {
- testStreamTypes(t, "", func(t *testing.T, styp streamType) {
+ testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
tc := newTestConn(t, serverSide,
func(c *Config) {
c.MaxBidiRemoteStreams = 1 << 60
@@ -152,7 +155,7 @@
// a value greater than 2^60 [...] the connection MUST be closed
// immediately with a connection error of type TRANSPORT_PARAMETER_ERROR [...]"
// https://www.rfc-editor.org/rfc/rfc9000#section-4.6-2
- testStreamTypes(t, "", func(t *testing.T, styp streamType) {
+ testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
tc := newTestConn(t, serverSide,
func(p *transportParameters) {
if styp == bidiStream {
@@ -177,7 +180,7 @@
// greater than 2^60 [...] the connection MUST be closed immediately
// with a connection error [...] of type FRAME_ENCODING_ERROR [...]"
// https://www.rfc-editor.org/rfc/rfc9000#section-4.6-2
- testStreamTypes(t, "", func(t *testing.T, styp streamType) {
+ testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
tc := newTestConn(t, serverSide)
tc.handshake()
tc.writeFrames(packetTypeInitial,
@@ -197,7 +200,7 @@
}
func TestStreamLimitSendUpdatesMaxStreams(t *testing.T) {
- testStreamTypes(t, "", func(t *testing.T, styp streamType) {
+ testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
tc := newTestConn(t, serverSide, func(c *Config) {
if styp == uniStream {
c.MaxUniRemoteStreams = 4
@@ -236,6 +239,9 @@
}
func TestStreamLimitStopSendingDoesNotUpdateMaxStreams(t *testing.T) {
+ synctest.Test(t, testStreamLimitStopSendingDoesNotUpdateMaxStreams)
+}
+func testStreamLimitStopSendingDoesNotUpdateMaxStreams(t *testing.T) {
tc, s := newTestConnAndRemoteStream(t, serverSide, bidiStream, func(c *Config) {
c.MaxBidiRemoteStreams = 1
})
diff --git a/quic/stream_test.go b/quic/stream_test.go
index 4119cc1..67d17f6 100644
--- a/quic/stream_test.go
+++ b/quic/stream_test.go
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.25
+
package quic
import (
@@ -13,12 +15,13 @@
"io"
"strings"
"testing"
+ "testing/synctest"
"golang.org/x/net/internal/quic/quicwire"
)
func TestStreamWriteBlockedByOutputBuffer(t *testing.T) {
- testStreamTypes(t, "", func(t *testing.T, styp streamType) {
+ testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
want := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
const writeBufferSize = 4
tc := newTestConn(t, clientSide, permissiveTransportParameters, func(c *Config) {
@@ -79,7 +82,7 @@
}
func TestStreamWriteBlockedByStreamFlowControl(t *testing.T) {
- testStreamTypes(t, "", func(t *testing.T, styp streamType) {
+ testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
ctx := canceledContext()
want := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
tc := newTestConn(t, clientSide, func(p *transportParameters) {
@@ -149,7 +152,7 @@
// "A sender MUST ignore any MAX_STREAM_DATA [...] frames that
// do not increase flow control limits."
// https://www.rfc-editor.org/rfc/rfc9000#section-4.1-9
- testStreamTypes(t, "", func(t *testing.T, styp streamType) {
+ testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
ctx := canceledContext()
want := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
tc := newTestConn(t, clientSide, func(p *transportParameters) {
@@ -218,7 +221,7 @@
}
func TestStreamWriteBlockedByWriteBufferLimit(t *testing.T) {
- testStreamTypes(t, "", func(t *testing.T, styp streamType) {
+ testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
want := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
const maxWriteBuffer = 4
tc := newTestConn(t, clientSide, func(p *transportParameters) {
@@ -392,7 +395,7 @@
wantEOF: true,
}},
}} {
- testStreamTypes(t, test.name, func(t *testing.T, styp streamType) {
+ testStreamTypesSynctest(t, test.name, func(t *testing.T, styp streamType) {
tc := newTestConn(t, serverSide)
tc.handshake()
sid := newStreamID(clientSide, styp, 0)
@@ -439,7 +442,7 @@
}
func TestStreamReceiveExtendsStreamWindow(t *testing.T) {
- testStreamTypes(t, "", func(t *testing.T, styp streamType) {
+ testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
const maxWindowSize = 20
ctx := canceledContext()
tc := newTestConn(t, serverSide, func(c *Config) {
@@ -484,7 +487,7 @@
// "A receiver MUST close the connection with an error of type FLOW_CONTROL_ERROR if
// the sender violates the advertised [...] stream data limits [...]"
// https://www.rfc-editor.org/rfc/rfc9000#section-4.1-8
- testStreamTypes(t, "", func(t *testing.T, styp streamType) {
+ testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
const maxStreamData = 10
for _, test := range []struct {
off int64
@@ -521,7 +524,7 @@
}
func TestStreamReceiveDuplicateDataDoesNotViolateLimits(t *testing.T) {
- testStreamTypes(t, "", func(t *testing.T, styp streamType) {
+ testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
const maxData = 10
tc := newTestConn(t, serverSide, func(c *Config) {
// TODO: Add connection-level maximum data here as well.
@@ -544,7 +547,7 @@
// A stream receives some data, we read a byte of that data
// (causing the rest to be pulled into the s.inbuf buffer),
// and then we receive a FIN with no additional data.
- testStreamTypes(t, "", func(t *testing.T, styp streamType) {
+ testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
tc, s := newTestConnAndRemoteStream(t, serverSide, styp, permissiveTransportParameters)
want := []byte{1, 2, 3}
tc.writeFrames(packetType1RTT, debugFrameStream{
@@ -568,7 +571,7 @@
func TestStreamReadByteFromOneByteStream(t *testing.T) {
// ReadByte on the only byte of a stream should not return an error.
- testStreamTypes(t, "", func(t *testing.T, styp streamType) {
+ testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
tc, s := newTestConnAndRemoteStream(t, serverSide, styp, permissiveTransportParameters)
want := byte(1)
tc.writeFrames(packetType1RTT, debugFrameStream{
@@ -608,7 +611,7 @@
})
},
}} {
- t.Run(test.name, func(t *testing.T) {
+ synctestSubtest(t, test.name, func(t *testing.T) {
tc := newTestConn(t, serverSide, opts...)
tc.handshake()
sid := newStreamID(clientSide, styp, 0)
@@ -662,7 +665,7 @@
// "A receiver SHOULD treat receipt of data at or beyond
// the final size as an error of type FINAL_SIZE_ERROR [...]"
// https://www.rfc-editor.org/rfc/rfc9000#section-4.5-5
- testStreamTypes(t, "", func(t *testing.T, styp streamType) {
+ testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
tc := newTestConn(t, serverSide)
tc.handshake()
sid := newStreamID(clientSide, styp, 0)
@@ -688,7 +691,7 @@
}
func TestStreamReceiveUnblocksReader(t *testing.T) {
- testStreamTypes(t, "", func(t *testing.T, styp streamType) {
+ testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
tc := newTestConn(t, serverSide)
tc.handshake()
want := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
@@ -746,7 +749,7 @@
// It then sends the returned frame (STREAM, STREAM_DATA_BLOCKED, etc.)
// to the conn and expects a STREAM_STATE_ERROR.
func testStreamSendFrameInvalidState(t *testing.T, f func(sid streamID) debugFrame) {
- testSides(t, "stream_not_created", func(t *testing.T, side connSide) {
+ testSidesSynctest(t, "stream_not_created", func(t *testing.T, side connSide) {
tc := newTestConn(t, side, permissiveTransportParameters)
tc.handshake()
tc.writeFrames(packetType1RTT, f(newStreamID(side, bidiStream, 0)))
@@ -755,7 +758,7 @@
code: errStreamState,
})
})
- testSides(t, "uni_stream", func(t *testing.T, side connSide) {
+ testSidesSynctest(t, "uni_stream", func(t *testing.T, side connSide) {
ctx := canceledContext()
tc := newTestConn(t, side, permissiveTransportParameters)
tc.handshake()
@@ -823,7 +826,7 @@
// It then sends the returned frame (MAX_STREAM_DATA, STOP_SENDING, etc.)
// to the conn and expects a STREAM_STATE_ERROR.
func testStreamReceiveFrameInvalidState(t *testing.T, f func(sid streamID) debugFrame) {
- testSides(t, "stream_not_created", func(t *testing.T, side connSide) {
+ testSidesSynctest(t, "stream_not_created", func(t *testing.T, side connSide) {
tc := newTestConn(t, side)
tc.handshake()
tc.writeFrames(packetType1RTT, f(newStreamID(side, bidiStream, 0)))
@@ -832,7 +835,7 @@
code: errStreamState,
})
})
- testSides(t, "uni_stream", func(t *testing.T, side connSide) {
+ testSidesSynctest(t, "uni_stream", func(t *testing.T, side connSide) {
tc := newTestConn(t, side)
tc.handshake()
tc.writeFrames(packetType1RTT, f(newStreamID(side.peer(), uniStream, 0)))
@@ -873,6 +876,9 @@
}
func TestStreamOffsetTooLarge(t *testing.T) {
+ synctest.Test(t, testStreamOffsetTooLarge)
+}
+func testStreamOffsetTooLarge(t *testing.T) {
// "Receipt of a frame that exceeds [2^62-1] MUST be treated as a
// connection error of type FRAME_ENCODING_ERROR or FLOW_CONTROL_ERROR."
// https://www.rfc-editor.org/rfc/rfc9000.html#section-19.8-9
@@ -894,6 +900,9 @@
}
func TestStreamReadFromWriteOnlyStream(t *testing.T) {
+ synctest.Test(t, testStreamReadFromWriteOnlyStream)
+}
+func testStreamReadFromWriteOnlyStream(t *testing.T) {
_, s := newTestConnAndLocalStream(t, serverSide, uniStream, permissiveTransportParameters)
buf := make([]byte, 10)
wantErr := "read from write-only stream"
@@ -903,6 +912,9 @@
}
func TestStreamWriteToReadOnlyStream(t *testing.T) {
+ synctest.Test(t, testStreamWriteToReadOnlyStream)
+}
+func testStreamWriteToReadOnlyStream(t *testing.T) {
_, s := newTestConnAndRemoteStream(t, serverSide, uniStream)
buf := make([]byte, 10)
wantErr := "write to read-only stream"
@@ -912,6 +924,9 @@
}
func TestStreamReadFromClosedStream(t *testing.T) {
+ synctest.Test(t, testStreamReadFromClosedStream)
+}
+func testStreamReadFromClosedStream(t *testing.T) {
tc, s := newTestConnAndRemoteStream(t, serverSide, bidiStream, permissiveTransportParameters)
s.CloseRead()
tc.wantFrame("CloseRead sends a STOP_SENDING frame",
@@ -934,6 +949,9 @@
}
func TestStreamCloseReadWithAllDataReceived(t *testing.T) {
+ synctest.Test(t, testStreamCloseReadWithAllDataReceived)
+}
+func testStreamCloseReadWithAllDataReceived(t *testing.T) {
tc, s := newTestConnAndRemoteStream(t, serverSide, bidiStream, permissiveTransportParameters)
tc.writeFrames(packetType1RTT, debugFrameStream{
id: s.id,
@@ -950,6 +968,9 @@
}
func TestStreamWriteToClosedStream(t *testing.T) {
+ synctest.Test(t, testStreamWriteToClosedStream)
+}
+func testStreamWriteToClosedStream(t *testing.T) {
tc, s := newTestConnAndLocalStream(t, serverSide, bidiStream, permissiveTransportParameters)
s.CloseWrite()
tc.wantFrame("stream is opened after being closed",
@@ -966,6 +987,9 @@
}
func TestStreamResetBlockedStream(t *testing.T) {
+ synctest.Test(t, testStreamResetBlockedStream)
+}
+func testStreamResetBlockedStream(t *testing.T) {
tc, s := newTestConnAndLocalStream(t, serverSide, bidiStream, permissiveTransportParameters,
func(c *Config) {
c.MaxStreamWriteBufferSize = 4
@@ -1002,6 +1026,9 @@
}
func TestStreamWriteMoreThanOnePacketOfData(t *testing.T) {
+ synctest.Test(t, testStreamWriteMoreThanOnePacketOfData)
+}
+func testStreamWriteMoreThanOnePacketOfData(t *testing.T) {
tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, func(p *transportParameters) {
p.initialMaxStreamsUni = 1
p.initialMaxData = 1 << 20
@@ -1038,6 +1065,9 @@
}
func TestStreamCloseWaitsForAcks(t *testing.T) {
+ synctest.Test(t, testStreamCloseWaitsForAcks)
+}
+func testStreamCloseWaitsForAcks(t *testing.T) {
tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, permissiveTransportParameters)
data := make([]byte, 100)
s.Write(data)
@@ -1071,6 +1101,9 @@
}
func TestStreamCloseReadOnly(t *testing.T) {
+ synctest.Test(t, testStreamCloseReadOnly)
+}
+func testStreamCloseReadOnly(t *testing.T) {
tc, s := newTestConnAndRemoteStream(t, serverSide, uniStream, permissiveTransportParameters)
if err := s.Close(); err != nil {
t.Errorf("s.Close() = %v, want nil", err)
@@ -1103,10 +1136,10 @@
name: "stream reset",
unblock: func(tc *testConn, s *Stream) {
s.Reset(0)
- tc.wait() // wait for test conn to process the Reset
+ synctest.Wait() // wait for test conn to process the Reset
},
}} {
- t.Run(test.name, func(t *testing.T) {
+ synctestSubtest(t, test.name, func(t *testing.T) {
tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, permissiveTransportParameters)
data := make([]byte, 100)
s.Write(data)
@@ -1148,6 +1181,9 @@
}
func TestStreamCloseWriteWhenBlockedByStreamFlowControl(t *testing.T) {
+ synctest.Test(t, testStreamCloseWriteWhenBlockedByStreamFlowControl)
+}
+func testStreamCloseWriteWhenBlockedByStreamFlowControl(t *testing.T) {
tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, permissiveTransportParameters,
func(p *transportParameters) {
//p.initialMaxData = 0
@@ -1185,7 +1221,7 @@
}
func TestStreamPeerResetsWithUnreadAndUnsentData(t *testing.T) {
- testStreamTypes(t, "", func(t *testing.T, styp streamType) {
+ testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
tc, s := newTestConnAndRemoteStream(t, serverSide, styp)
data := []byte{0, 1, 2, 3, 4, 5, 6, 7}
tc.writeFrames(packetType1RTT, debugFrameStream{
@@ -1210,7 +1246,7 @@
}
func TestStreamPeerResetWakesBlockedRead(t *testing.T) {
- testStreamTypes(t, "", func(t *testing.T, styp streamType) {
+ testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
tc, s := newTestConnAndRemoteStream(t, serverSide, styp)
reader := runAsync(tc, func(ctx context.Context) (int, error) {
s.SetReadContext(ctx)
@@ -1231,7 +1267,7 @@
}
func TestStreamPeerResetFollowedByData(t *testing.T) {
- testStreamTypes(t, "", func(t *testing.T, styp streamType) {
+ testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
tc, s := newTestConnAndRemoteStream(t, serverSide, styp)
tc.writeFrames(packetType1RTT, debugFrameResetStream{
id: s.id,
@@ -1256,6 +1292,9 @@
}
func TestStreamResetInvalidCode(t *testing.T) {
+ synctest.Test(t, testStreamResetInvalidCode)
+}
+func testStreamResetInvalidCode(t *testing.T) {
tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, permissiveTransportParameters)
s.Reset(1 << 62)
tc.wantFrame("reset with invalid code sends a RESET_STREAM anyway",
@@ -1268,6 +1307,9 @@
}
func TestStreamResetReceiveOnly(t *testing.T) {
+ synctest.Test(t, testStreamResetReceiveOnly)
+}
+func testStreamResetReceiveOnly(t *testing.T) {
tc, s := newTestConnAndRemoteStream(t, serverSide, uniStream)
s.Reset(0)
tc.wantIdle("resetting a receive-only stream has no effect")
@@ -1277,7 +1319,7 @@
// "An endpoint that receives a STOP_SENDING frame MUST send a RESET_STREAM frame if
// the stream is in the "Ready" or "Send" state."
// https://www.rfc-editor.org/rfc/rfc9000#section-3.5-4
- testStreamTypes(t, "", func(t *testing.T, styp streamType) {
+ testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
tc, s := newTestConnAndLocalStream(t, serverSide, styp, permissiveTransportParameters)
for i := 0; i < 4; i++ {
s.Write([]byte{byte(i)})
@@ -1309,6 +1351,9 @@
}
func TestStreamReceiveDataBlocked(t *testing.T) {
+ synctest.Test(t, testStreamReceiveDataBlocked)
+}
+func testStreamReceiveDataBlocked(t *testing.T) {
tc := newTestConn(t, serverSide, permissiveTransportParameters)
tc.handshake()
tc.ignoreFrame(frameTypeAck)
@@ -1326,7 +1371,7 @@
}
func TestStreamFlushExplicit(t *testing.T) {
- testStreamTypes(t, "", func(t *testing.T, styp streamType) {
+ testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
tc, s := newTestConnAndLocalStream(t, clientSide, styp, permissiveTransportParameters)
want := []byte{0, 1, 2, 3}
n, err := s.Write(want)
@@ -1344,6 +1389,9 @@
}
func TestStreamFlushClosedStream(t *testing.T) {
+ synctest.Test(t, testStreamFlushClosedStream)
+}
+func testStreamFlushClosedStream(t *testing.T) {
_, s := newTestConnAndLocalStream(t, clientSide, bidiStream,
permissiveTransportParameters)
s.Close()
@@ -1353,6 +1401,9 @@
}
func TestStreamFlushResetStream(t *testing.T) {
+ synctest.Test(t, testStreamFlushResetStream)
+}
+func testStreamFlushResetStream(t *testing.T) {
_, s := newTestConnAndLocalStream(t, clientSide, bidiStream,
permissiveTransportParameters)
s.Reset(0)
@@ -1362,6 +1413,9 @@
}
func TestStreamFlushStreamAfterPeerStopSending(t *testing.T) {
+ synctest.Test(t, testStreamFlushStreamAfterPeerStopSending)
+}
+func testStreamFlushStreamAfterPeerStopSending(t *testing.T) {
tc, s := newTestConnAndLocalStream(t, clientSide, bidiStream,
permissiveTransportParameters)
s.Flush() // create the stream
@@ -1381,6 +1435,9 @@
}
func TestStreamErrorsAfterConnectionClosed(t *testing.T) {
+ synctest.Test(t, testStreamErrorsAfterConnectionClosed)
+}
+func testStreamErrorsAfterConnectionClosed(t *testing.T) {
tc, s := newTestConnAndLocalStream(t, clientSide, bidiStream,
permissiveTransportParameters)
wantErr := &ApplicationError{Code: 42}
@@ -1399,7 +1456,7 @@
}
func TestStreamFlushImplicitExact(t *testing.T) {
- testStreamTypes(t, "", func(t *testing.T, styp streamType) {
+ testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
const writeBufferSize = 4
tc, s := newTestConnAndLocalStream(t, clientSide, styp,
permissiveTransportParameters,
@@ -1429,7 +1486,7 @@
}
func TestStreamFlushImplicitLargerThanBuffer(t *testing.T) {
- testStreamTypes(t, "", func(t *testing.T, styp streamType) {
+ testStreamTypesSynctest(t, "", func(t *testing.T, styp streamType) {
const writeBufferSize = 4
tc, s := newTestConnAndLocalStream(t, clientSide, styp,
permissiveTransportParameters,
diff --git a/quic/tls_test.go b/quic/tls_test.go
index 08c75dd..0818c68 100644
--- a/quic/tls_test.go
+++ b/quic/tls_test.go
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.25
+
package quic
import (
@@ -9,12 +11,12 @@
"crypto/x509"
"errors"
"testing"
+ "testing/synctest"
"time"
)
// handshake executes the handshake.
func (tc *testConn) handshake() {
- tc.t.Helper()
if *testVV {
*testVV = false
defer func() {
@@ -32,16 +34,16 @@
i := 0
for {
if i == len(dgrams)-1 {
- want := tc.endpoint.now.Add(maxAckDelay - timerGranularity)
+ want := time.Now().Add(maxAckDelay - timerGranularity)
if tc.conn.side == clientSide {
- if !tc.timer.Equal(want) {
- t.Fatalf("want timer = %v (max_ack_delay), got %v", want, tc.timer)
+ if got := tc.nextEvent(); !got.Equal(want) {
+ t.Fatalf("want timer = %v (max_ack_delay), got %v", want, got)
}
if got := tc.readDatagram(); got != nil {
t.Fatalf("client unexpectedly sent: %v", got)
}
}
- tc.advanceTo(want)
+ time.Sleep(time.Until(want))
}
// Check that we're sending exactly the data we expect.
@@ -308,20 +310,29 @@
}
func TestConnClientHandshake(t *testing.T) {
+ synctest.Test(t, testConnClientHandshake)
+}
+func testConnClientHandshake(t *testing.T) {
tc := newTestConn(t, clientSide)
tc.handshake()
- tc.advance(1 * time.Second)
+ time.Sleep(1 * time.Second)
tc.wantIdle("no packets should be sent by an idle conn after the handshake")
}
func TestConnServerHandshake(t *testing.T) {
+ synctest.Test(t, testConnServerHandshake)
+}
+func testConnServerHandshake(t *testing.T) {
tc := newTestConn(t, serverSide)
tc.handshake()
- tc.advance(1 * time.Second)
+ time.Sleep(1 * time.Second)
tc.wantIdle("no packets should be sent by an idle conn after the handshake")
}
func TestConnKeysDiscardedClient(t *testing.T) {
+ synctest.Test(t, testConnKeysDiscardedClient)
+}
+func testConnKeysDiscardedClient(t *testing.T) {
tc := newTestConn(t, clientSide)
tc.ignoreFrame(frameTypeAck)
@@ -370,6 +381,9 @@
}
func TestConnKeysDiscardedServer(t *testing.T) {
+ synctest.Test(t, testConnKeysDiscardedServer)
+}
+func testConnKeysDiscardedServer(t *testing.T) {
tc := newTestConn(t, serverSide)
tc.ignoreFrame(frameTypeAck)
@@ -425,6 +439,9 @@
}
func TestConnInvalidCryptoData(t *testing.T) {
+ synctest.Test(t, testConnInvalidCryptoData)
+}
+func testConnInvalidCryptoData(t *testing.T) {
tc := newTestConn(t, clientSide)
tc.ignoreFrame(frameTypeAck)
@@ -455,6 +472,9 @@
}
func TestConnInvalidPeerCertificate(t *testing.T) {
+ synctest.Test(t, testConnInvalidPeerCertificate)
+}
+func testConnInvalidPeerCertificate(t *testing.T) {
tc := newTestConn(t, clientSide, func(c *tls.Config) {
c.VerifyPeerCertificate = func([][]byte, [][]*x509.Certificate) error {
return errors.New("I will not buy this certificate. It is scratched.")
@@ -481,6 +501,9 @@
}
func TestConnHandshakeDoneSentToServer(t *testing.T) {
+ synctest.Test(t, testConnHandshakeDoneSentToServer)
+}
+func testConnHandshakeDoneSentToServer(t *testing.T) {
tc := newTestConn(t, serverSide)
tc.handshake()
@@ -493,6 +516,9 @@
}
func TestConnCryptoDataOutOfOrder(t *testing.T) {
+ synctest.Test(t, testConnCryptoDataOutOfOrder)
+}
+func testConnCryptoDataOutOfOrder(t *testing.T) {
tc := newTestConn(t, clientSide)
tc.ignoreFrame(frameTypeAck)
@@ -531,6 +557,9 @@
}
func TestConnCryptoBufferSizeExceeded(t *testing.T) {
+ synctest.Test(t, testConnCryptoBufferSizeExceeded)
+}
+func testConnCryptoBufferSizeExceeded(t *testing.T) {
tc := newTestConn(t, clientSide)
tc.ignoreFrame(frameTypeAck)
@@ -550,6 +579,9 @@
}
func TestConnAEADLimitReached(t *testing.T) {
+ synctest.Test(t, testConnAEADLimitReached)
+}
+func testConnAEADLimitReached(t *testing.T) {
// "[...] endpoints MUST count the number of received packets that
// fail authentication during the lifetime of a connection.
// If the total number of received packets that fail authentication [...]
@@ -590,7 +622,7 @@
tc.conn.sendMsg(&datagram{
b: invalid,
})
- tc.wait()
+ synctest.Wait()
}
// Set the conn's auth failure count to just before the AEAD integrity limit.
@@ -610,11 +642,14 @@
})
tc.writeFrames(packetType1RTT, debugFramePing{})
- tc.advance(1 * time.Second)
+ time.Sleep(1 * time.Second)
tc.wantIdle("auth failures at limit: conn does not process additional packets")
}
func TestConnKeysDiscardedWithExcessCryptoData(t *testing.T) {
+ synctest.Test(t, testConnKeysDiscardedWithExcessCryptoData)
+}
+func testConnKeysDiscardedWithExcessCryptoData(t *testing.T) {
tc := newTestConn(t, serverSide, permissiveTransportParameters)
tc.ignoreFrame(frameTypeAck)
tc.ignoreFrame(frameTypeNewConnectionID)
diff --git a/quic/version_test.go b/quic/version_test.go
index 60d8307..ac054a8 100644
--- a/quic/version_test.go
+++ b/quic/version_test.go
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build go1.25
+
package quic
import (
@@ -9,9 +11,13 @@
"context"
"crypto/tls"
"testing"
+ "testing/synctest"
)
func TestVersionNegotiationServerReceivesUnknownVersion(t *testing.T) {
+ synctest.Test(t, testVersionNegotiationServerReceivesUnknownVersion)
+}
+func testVersionNegotiationServerReceivesUnknownVersion(t *testing.T) {
config := &Config{
TLSConfig: newTestTLSConfig(serverSide),
}
@@ -55,6 +61,9 @@
}
func TestVersionNegotiationClientAborts(t *testing.T) {
+ synctest.Test(t, testVersionNegotiationClientAborts)
+}
+func testVersionNegotiationClientAborts(t *testing.T) {
tc := newTestConn(t, clientSide)
p := tc.readPacket() // client Initial packet
tc.endpoint.write(&datagram{
@@ -67,6 +76,9 @@
}
func TestVersionNegotiationClientIgnoresAfterProcessingPacket(t *testing.T) {
+ synctest.Test(t, testVersionNegotiationClientIgnoresAfterProcessingPacket)
+}
+func testVersionNegotiationClientIgnoresAfterProcessingPacket(t *testing.T) {
tc := newTestConn(t, clientSide)
tc.ignoreFrame(frameTypeAck)
p := tc.readPacket() // client Initial packet
@@ -89,6 +101,9 @@
}
func TestVersionNegotiationClientIgnoresMismatchingSourceConnID(t *testing.T) {
+ synctest.Test(t, testVersionNegotiationClientIgnoresMismatchingSourceConnID)
+}
+func testVersionNegotiationClientIgnoresMismatchingSourceConnID(t *testing.T) {
tc := newTestConn(t, clientSide)
tc.ignoreFrame(frameTypeAck)
p := tc.readPacket() // client Initial packet