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>
This commit is contained in:
Damien Neil
2025-10-23 15:17:56 -07:00
committed by Gopher Robot
parent fff0469cf5
commit d1f64cc670
35 changed files with 702 additions and 479 deletions

View File

@@ -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 (

View File

@@ -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)

View File

@@ -69,23 +69,12 @@ type connTestHooks interface {
// 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 @@ func newConn(now time.Time, side connSide, cids newServerConnIDs, peerHostname s
endpoint: e,
config: config,
peerAddr: unmapAddrPort(peerAddr),
msgc: make(chan any, 1),
donec: make(chan struct{}),
peerAckDelayExponent: -1,
}
@@ -299,17 +287,12 @@ func (c *Conn) loop(now time.Time) {
// 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 @@ func (c *Conn) loop(now time.Time) {
}
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 @@ func (c *Conn) loop(now time.Time) {
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 @@ func (c *Conn) runOnLoop(ctx context.Context, f func(now time.Time, c *Conn)) er
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) runOnLoop(ctx context.Context, f func(now time.Time, c *Conn)) er
}
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.

View File

@@ -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 @@ type asyncOp[T any] struct {
// 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 @@ var errNotDone = errors.New("async op is not done")
// 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
}

View File

@@ -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 @@ import (
"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 @@ func TestConnCloseResponseBackoff(t *testing.T) {
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 @@ func TestConnCloseResponseBackoff(t *testing.T) {
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 TestConnCloseResponseBackoff(t *testing.T) {
}
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 @@ func TestConnCloseWithPeerResponse(t *testing.T) {
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 TestConnCloseWithPeerResponse(t *testing.T) {
}
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 @@ func TestConnClosePeerCloses(t *testing.T) {
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 TestConnClosePeerCloses(t *testing.T) {
}
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 TestConnCloseReceiveInInitial(t *testing.T) {
}
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 TestConnCloseReceiveInHandshake(t *testing.T) {
}
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 testConnCloseUnblocks(t *testing.T, f func(context.Context, *testConn) erro
}
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 TestConnCloseUnblocksAcceptStream(t *testing.T) {
}
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 TestConnCloseUnblocksNewStream(t *testing.T) {
}
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 TestConnCloseUnblocksStreamRead(t *testing.T) {
}
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 TestConnCloseUnblocksStreamWrite(t *testing.T) {
}
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)

View File

@@ -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 TestConnInflowReturnOnRead(t *testing.T) {
}
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 TestConnInflowReturnOnRacingReads(t *testing.T) {
}
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 TestConnInflowReturnOnClose(t *testing.T) {
}
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 TestConnInflowReturnOnReset(t *testing.T) {
}
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 TestConnInflowStreamViolation(t *testing.T) {
}
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 TestConnInflowResetViolation(t *testing.T) {
}
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 TestConnInflowMultipleStreams(t *testing.T) {
}
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 TestConnOutflowBlocked(t *testing.T) {
}
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 TestConnOutflowMaxDataDecreases(t *testing.T) {
}
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 TestConnOutflowMaxDataRoundRobin(t *testing.T) {
}
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 TestConnOutflowMetaAndData(t *testing.T) {
}
func TestConnOutflowResentData(t *testing.T) {
synctest.Test(t, testConnOutflowResentData)
}
func testConnOutflowResentData(t *testing.T) {
tc, s := newTestConnAndLocalStream(t, clientSide, bidiStream,
permissiveTransportParameters,
func(p *transportParameters) {

View File

@@ -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 @@ import (
"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 TestConnIDClientHandshake(t *testing.T) {
}
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 TestNewRandomConnID(t *testing.T) {
}
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 TestConnIDPeerRequestsManyIDs(t *testing.T) {
}
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 TestConnIDPeerProvidesTooManyIDs(t *testing.T) {
}
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 @@ func TestConnIDPeerRetiresConnID(t *testing.T) {
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 TestConnIDPeerRetiresConnID(t *testing.T) {
}
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 TestConnIDPeerWithZeroLengthConnIDSendsNewConnectionID(t *testing.T) {
}
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 TestConnIDPeerRequestsRetirement(t *testing.T) {
}
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 TestConnIDPeerDoesNotAcknowledgeRetirement(t *testing.T) {
}
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 TestConnIDRepeatedNewConnectionIDFrame(t *testing.T) {
}
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 TestConnIDForSequenceNumberChanges(t *testing.T) {
}
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 TestConnIDRetirePriorToAfterNewConnID(t *testing.T) {
}
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 TestConnIDAlreadyRetired(t *testing.T) {
}
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 TestConnIDRepeatedRetireConnectionIDFrame(t *testing.T) {
}
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 TestConnIDRetiredUnsent(t *testing.T) {
}
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 TestConnIDUsePreferredAddressConnID(t *testing.T) {
}
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 TestConnIDPeerProvidesPreferredAddrAndTooManyConnIDs(t *testing.T) {
}
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 @@ func TestConnIDInitialSrcConnIDMismatch(t *testing.T) {
// "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 TestConnIDInitialSrcConnIDMismatch(t *testing.T) {
}
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 TestConnIDsCleanedUpAfterClose(t *testing.T) {
}
func TestConnIDRetiredConnIDResent(t *testing.T) {
synctest.Test(t, testConnIDRetiredConnIDResent)
}
func testConnIDRetiredConnIDResent(t *testing.T) {
tc := newTestConn(t, serverSide)
tc.handshake()
tc.ignoreFrame(frameTypeAck)

View File

@@ -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 @@ import (
"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 lostFrameTest(t *testing.T, f func(t *testing.T, pto bool)) {
})
}
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 @@ func (tc *testConn) triggerLossOrPTO(ptype packetType, pto bool) {
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 @@ func TestLostResetStreamFrame(t *testing.T) {
// "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 @@ func TestLostStopSendingFrame(t *testing.T) {
// 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 TestLostStopSendingFrame(t *testing.T) {
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 TestLostCryptoFrame(t *testing.T) {
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 @@ func TestLostStreamWithData(t *testing.T) {
// 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 TestLostStreamWithData(t *testing.T) {
}
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 @@ func TestLostMaxDataFrame(t *testing.T) {
// "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 @@ func TestLostMaxStreamDataFrame(t *testing.T) {
// "[...] 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 @@ func TestLostMaxStreamDataFrameAfterStreamFinReceived(t *testing.T) {
// "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 @@ func TestLostMaxStreamsFrameMostRecent(t *testing.T) {
// 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 TestLostMaxStreamsFrameMostRecent(t *testing.T) {
}
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 @@ func TestLostStreamDataBlockedFrame(t *testing.T) {
// "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 @@ func TestLostStreamDataBlockedFrameAfterStreamUnblocked(t *testing.T) {
// "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 TestLostStreamDataBlockedFrameAfterStreamUnblocked(t *testing.T) {
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 @@ func TestLostRetireConnectionIDFrame(t *testing.T) {
// "[...] 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 TestLostRetireConnectionIDFrame(t *testing.T) {
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 TestLostPathResponseFrame(t *testing.T) {
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)

View File

@@ -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 @@ func TestConnReceiveAckForUnsentPacket(t *testing.T) {
// 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)

View File

@@ -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 @@ func TestAckElicitingAck(t *testing.T) {
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 TestAckElicitingAck(t *testing.T) {
}
func TestSendPacketNumberSize(t *testing.T) {
synctest.Test(t, testSendPacketNumberSize)
}
func testSendPacketNumberSize(t *testing.T) {
tc := newTestConn(t, clientSide, permissiveTransportParameters)
tc.handshake()

View File

@@ -71,7 +71,7 @@ func (c *Conn) streamsCleanup() {
// 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.

View File

@@ -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 @@ import (
"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 TestStreamsCreate(t *testing.T) {
}
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 TestStreamsAccept(t *testing.T) {
}
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 TestStreamsBlockingAccept(t *testing.T) {
}
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 TestStreamsLocalStreamNotCreated(t *testing.T) {
}
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 TestStreamsLocalStreamClosed(t *testing.T) {
}
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 TestStreamsStreamSendOnly(t *testing.T) {
}
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 @@ func TestStreamsWriteQueueFairness(t *testing.T) {
}
// 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 @@ func TestStreamsShutdown(t *testing.T) {
},
}} {
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 TestStreamsShutdown(t *testing.T) {
}
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 TestStreamsCreateAndCloseRemote(t *testing.T) {
}
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 TestStreamsCreateConcurrency(t *testing.T) {
}
func TestStreamsPTOWithImplicitStream(t *testing.T) {
synctest.Test(t, testStreamsPTOWithImplicitStream)
}
func testStreamsPTOWithImplicitStream(t *testing.T) {
ctx := canceledContext()
tc := newTestConn(t, serverSide, permissiveTransportParameters)
tc.handshake()

View File

@@ -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 @@ import (
"reflect"
"strings"
"testing"
"testing/synctest"
"time"
"golang.org/x/net/quic/qlog"
@@ -27,7 +30,8 @@ var (
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 @@ func TestConnTestConn(t *testing.T) {
})
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 @@ func TestConnTestConn(t *testing.T) {
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 @@ const maxTestKeyPhases = 3
// 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 @@ type testConn struct {
// Values to set in packets sent to the conn.
sendKeyNumber int
sendKeyPhaseBit bool
asyncTestState
}
type test1RTTKeys struct {
@@ -198,10 +197,6 @@ type keySecret struct {
}
// 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 @@ func newTestConn(t *testing.T, side connSide, opts ...any) *testConn {
endpoint.configTransportParams = configTransportParams
endpoint.configTestConn = configTestConn
conn, err := endpoint.e.newConn(
endpoint.now,
time.Now(),
config,
side,
cids,
@@ -252,7 +247,7 @@ func newTestConn(t *testing.T, side connSide, opts ...any) *testConn {
t.Fatal(err)
}
tc := endpoint.conns[conn]
tc.wait()
synctest.Wait()
return tc
}
@@ -306,76 +301,33 @@ func newTestConnForConn(t *testing.T, endpoint *testEndpoint, conn *Conn) *testC
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 @@ func (tc *testConn) ignoreFrame(frameType byte) {
// 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 @@ func (tc *testConnHooks) handleTLSEvent(e tls.QUICEvent) {
}
}
// 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 {

View File

@@ -36,7 +36,6 @@ type Endpoint struct {
}
type endpointTestHooks interface {
timeNow() time.Time
newConn(c *Conn)
}
@@ -160,7 +159,7 @@ func (e *Endpoint) Close(ctx context.Context) error {
// 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 @@ func (e *Endpoint) handleUnknownDestinationDatagram(m *datagram) {
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):])

View File

@@ -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 @@ import (
"log/slog"
"net/netip"
"runtime"
"sync"
"testing"
"time"
"testing/synctest"
"golang.org/x/net/quic/qlog"
)
@@ -126,22 +129,22 @@ func makeTestConfig(conf *Config, side connSide) *Config {
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 @@ func (te *testEndpoint) cleanup() {
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) accept() *testConn {
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) connForSource(srcConnID []byte) *testConn {
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 @@ func (te *testEndpoint) wantIdle(expectation string) {
}
}
// 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) Read(f func(*datagram)) {
}
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
}

View File

@@ -46,10 +46,7 @@ func (g *gate) lock() (set bool) {
// 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

View File

@@ -47,7 +47,7 @@ func TestGateWaitAndLockContext(t *testing.T) {
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 @@ func TestGateWaitAndLockContext(t *testing.T) {
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 @@ func TestGateWaitAndLockContext(t *testing.T) {
}
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 @@ func TestGateUnlockFunc(t *testing.T) {
g.lock()
defer g.unlockFunc(func() bool { return true })
}()
g.waitAndLock(context.Background(), nil)
g.waitAndLock(context.Background())
}

View File

@@ -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 @@ import (
"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 @@ func TestHandshakeTimeoutExpiresServer(t *testing.T) {
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 TestHandshakeTimeoutExpiresServer(t *testing.T) {
}
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 @@ func TestHandshakeTimeoutExpiresClient(t *testing.T) {
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 @@ func TestIdleTimeoutExpires(t *testing.T) {
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 @@ func TestIdleTimeoutExpires(t *testing.T) {
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 @@ func TestIdleTimeoutKeepAlive(t *testing.T) {
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 @@ func TestIdleTimeoutKeepAlive(t *testing.T) {
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 TestIdleTimeoutKeepAlive(t *testing.T) {
}
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 @@ func TestIdleLongTermKeepAliveSent(t *testing.T) {
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 TestIdleLongTermKeepAliveSent(t *testing.T) {
}
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 @@ func TestIdleLongTermKeepAliveReceived(t *testing.T) {
})
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)

View File

@@ -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 TestKeyUpdatePeerUpdates(t *testing.T) {
}
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 TestKeyUpdateAcceptPreviousPhaseKeys(t *testing.T) {
}
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 TestKeyUpdateRejectPacketFromPriorPhase(t *testing.T) {
}
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

View File

@@ -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 (

View File

@@ -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 @@ func TestPathChallengeReceived(t *testing.T) {
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."

View File

@@ -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 TestPing(t *testing.T) {
}
func TestAck(t *testing.T) {
synctest.Test(t, testAck)
}
func testAck(t *testing.T) {
tc := newTestConn(t, serverSide)
tc.handshake()

View File

@@ -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 @@ import (
"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 TestQLogHandshake(t *testing.T) {
}
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 @@ func TestQLogConnectionClosedTrigger(t *testing.T) {
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 @@ func TestQLogConnectionClosedTrigger(t *testing.T) {
},
f: func(tc *testConn) {
tc.handshake()
tc.advance(5 * time.Second)
time.Sleep(5 * time.Second)
},
}, {
trigger: "error",
@@ -134,7 +141,7 @@ func TestQLogConnectionClosedTrigger(t *testing.T) {
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 @@ func TestQLogConnectionClosedTrigger(t *testing.T) {
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 TestQLogConnectionClosedTrigger(t *testing.T) {
}
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 TestQLogRecovery(t *testing.T) {
}
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 TestQLogLoss(t *testing.T) {
}
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 @@ func jsonPartialEqual(got, want any) (equal bool) {
// 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 @@ func (q *qlogRecord) config(c *Config) {
// 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)

View File

@@ -42,9 +42,9 @@ func (q *queue[T]) put(v T) bool {
// 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()

View File

@@ -16,7 +16,7 @@ func TestQueue(t *testing.T) {
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 @@ func TestQueue(t *testing.T) {
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 @@ func TestQueue(t *testing.T) {
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 @@ func TestQueue(t *testing.T) {
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) {

View File

@@ -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 @@ func testSides(t *testing.T, name string, f func(*testing.T, connSide)) {
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 @@ func testStreamTypes(t *testing.T, name string, f func(*testing.T, streamType))
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 @@ func testSidesAndStreamTypes(t *testing.T, name string, f func(*testing.T, connS
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)
})
}

View File

@@ -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 @@ import (
"crypto/tls"
"net/netip"
"testing"
"testing/synctest"
"time"
)
@@ -77,9 +80,12 @@ func newRetryServerTest(t *testing.T) *retryServerTest {
}
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 TestRetryServerSucceeds(t *testing.T) {
}
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 TestRetryServerTokenInvalid(t *testing.T) {
}
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 TestRetryServerTokenTooOld(t *testing.T) {
}
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 TestRetryServerTokenWrongIP(t *testing.T) {
}
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 TestRetryServerIgnoresRetry(t *testing.T) {
}
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 @@ func TestRetryClientInvalidServerTransportParameters(t *testing.T) {
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 TestRetryClientInvalidServerTransportParameters(t *testing.T) {
}
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 TestRetryClientIgnoresRetryAfterReceivingPacket(t *testing.T) {
}
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 TestRetryClientIgnoresRetryAfterReceivingRetry(t *testing.T) {
}
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 TestRetryClientIgnoresRetryWithInvalidIntegrityTag(t *testing.T) {
}
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)

View File

@@ -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 @@ expectSkip:
}
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.

View File

@@ -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 @@ import (
"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 newDatagramForReset(cid []byte, size int, addr netip.AddrPort) *datagram {
}
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 TestStatelessResetSentSizes(t *testing.T) {
}
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 @@ func TestStatelessResetSuccessfulNewConnectionID(t *testing.T) {
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 TestStatelessResetSuccessfulNewConnectionID(t *testing.T) {
}
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 @@ func TestStatelessResetSuccessfulPrefix(t *testing.T) {
}, 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 TestStatelessResetSuccessfulPrefix(t *testing.T) {
}
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

View File

@@ -236,7 +236,7 @@ func (s *Stream) Read(b []byte) (n int, err error) {
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 @@ func (s *Stream) Write(b []byte) (n int, err error) {
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

View File

@@ -29,7 +29,7 @@ func (lim *localStreamLimits) init() {
// 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 {

View File

@@ -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 TestStreamLimitNewStreamBlocked(t *testing.T) {
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 TestStreamLimitMaxStreamsDecreases(t *testing.T) {
}
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 TestStreamLimitViolated(t *testing.T) {
}
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 @@ func TestStreamLimitMaxStreamsTransportParameterTooLarge(t *testing.T) {
// 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 @@ func TestStreamLimitMaxStreamsFrameTooLarge(t *testing.T) {
// 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 TestStreamLimitMaxStreamsFrameTooLarge(t *testing.T) {
}
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 TestStreamLimitSendUpdatesMaxStreams(t *testing.T) {
}
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
})

View File

@@ -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 @@ import (
"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 TestStreamWriteBlockedByOutputBuffer(t *testing.T) {
}
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 @@ func TestStreamIgnoresMaxStreamDataReduction(t *testing.T) {
// "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 TestStreamIgnoresMaxStreamDataReduction(t *testing.T) {
}
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 @@ func TestStreamReceive(t *testing.T) {
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 TestStreamReceive(t *testing.T) {
}
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 @@ func TestStreamReceiveViolatesStreamDataLimit(t *testing.T) {
// "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 TestStreamReceiveViolatesStreamDataLimit(t *testing.T) {
}
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 @@ func TestStreamReceiveEmptyEOF(t *testing.T) {
// 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 TestStreamReceiveEmptyEOF(t *testing.T) {
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 @@ func finalSizeTest(t *testing.T, wantErr transportError, f func(tc *testConn, si
})
},
}} {
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 @@ func TestStreamDataBeyondFinalSize(t *testing.T) {
// "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 TestStreamDataBeyondFinalSize(t *testing.T) {
}
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 @@ func TestStreamReceiveUnblocksReader(t *testing.T) {
// 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 @@ func testStreamSendFrameInvalidState(t *testing.T, f func(sid streamID) debugFra
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 @@ func TestStreamDataBlockedInvalidState(t *testing.T) {
// 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 @@ func testStreamReceiveFrameInvalidState(t *testing.T, f func(sid streamID) debug
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 TestStreamMaxStreamDataInvalidState(t *testing.T) {
}
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 TestStreamOffsetTooLarge(t *testing.T) {
}
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 TestStreamReadFromWriteOnlyStream(t *testing.T) {
}
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 TestStreamWriteToReadOnlyStream(t *testing.T) {
}
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 TestStreamReadFromClosedStream(t *testing.T) {
}
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 TestStreamCloseReadWithAllDataReceived(t *testing.T) {
}
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 TestStreamWriteToClosedStream(t *testing.T) {
}
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 TestStreamResetBlockedStream(t *testing.T) {
}
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 TestStreamWriteMoreThanOnePacketOfData(t *testing.T) {
}
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 TestStreamCloseWaitsForAcks(t *testing.T) {
}
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 @@ func TestStreamCloseUnblocked(t *testing.T) {
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 TestStreamCloseUnblocked(t *testing.T) {
}
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 TestStreamCloseWriteWhenBlockedByStreamFlowControl(t *testing.T) {
}
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 TestStreamPeerResetsWithUnreadAndUnsentData(t *testing.T) {
}
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 TestStreamPeerResetWakesBlockedRead(t *testing.T) {
}
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 TestStreamPeerResetFollowedByData(t *testing.T) {
}
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 TestStreamResetInvalidCode(t *testing.T) {
}
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 @@ func TestStreamPeerStopSendingForActiveStream(t *testing.T) {
// "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 TestStreamPeerStopSendingForActiveStream(t *testing.T) {
}
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 TestStreamReceiveDataBlocked(t *testing.T) {
}
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 TestStreamFlushExplicit(t *testing.T) {
}
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 TestStreamFlushClosedStream(t *testing.T) {
}
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 TestStreamFlushResetStream(t *testing.T) {
}
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 TestStreamFlushStreamAfterPeerStopSending(t *testing.T) {
}
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 TestStreamErrorsAfterConnectionClosed(t *testing.T) {
}
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 TestStreamFlushImplicitExact(t *testing.T) {
}
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,

View File

@@ -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 @@ import (
"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 @@ func (tc *testConn) handshake() {
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 (tc *testConn) uncheckedHandshake() {
}
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 TestConnKeysDiscardedClient(t *testing.T) {
}
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 TestConnKeysDiscardedServer(t *testing.T) {
}
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 TestConnInvalidCryptoData(t *testing.T) {
}
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 TestConnInvalidPeerCertificate(t *testing.T) {
}
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 TestConnHandshakeDoneSentToServer(t *testing.T) {
}
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 TestConnCryptoDataOutOfOrder(t *testing.T) {
}
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 TestConnCryptoBufferSizeExceeded(t *testing.T) {
}
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 @@ func TestConnAEADLimitReached(t *testing.T) {
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 @@ func TestConnAEADLimitReached(t *testing.T) {
})
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)

View File

@@ -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 @@ import (
"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 TestVersionNegotiationServerReceivesUnknownVersion(t *testing.T) {
}
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 TestVersionNegotiationClientAborts(t *testing.T) {
}
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 TestVersionNegotiationClientIgnoresAfterProcessingPacket(t *testing.T) {
}
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