blob: 08cc7d337a2cbdba4ed13c30b6224abab0d38b1b [file] [log] [blame]
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.25
package quic
import (
"context"
"errors"
"fmt"
"testing/synctest"
)
// An asyncOp is an asynchronous operation that results in (T, error).
type asyncOp[T any] struct {
v T
err error
donec chan struct{}
cancelFunc context.CancelFunc
}
// 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()
synctest.Wait()
select {
case <-a.donec:
default:
panic(fmt.Errorf("async op failed to finish after being canceled"))
}
}
var errNotDone = errors.New("async op is not done")
// result returns the result of the async operation.
// It returns errNotDone if the operation is still in progress.
//
// Note that unlike a traditional async/await, this doesn't block
// waiting for the operation to complete. Since tests have full
// 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) {
synctest.Wait()
select {
case <-a.donec:
return a.v, a.err
default:
return a.v, errNotDone
}
}
// 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] {
ctx, cancel := context.WithCancel(tc.t.Context())
a := &asyncOp[T]{
donec: make(chan struct{}),
cancelFunc: cancel,
}
go func() {
defer close(a.donec)
a.v, a.err = f(ctx)
}()
synctest.Wait()
return a
}