mirror of
https://github.com/golang/go
synced 2024-11-26 18:16:48 -07:00
context: clean up test files
Tests in package context cannot depend directly on package testing due to an import cycle. We resolved this by having test functions in package context_test (x_test.go) forward to test functions in package context (context_test.go). This is fragile, since it's easy to add a test to context_test.go and forget to add the forwarding function, and tests written in this way cannot easily use testing package features like t.Run for subtests. It turns out that only four test functions actually use unexported members of package context. This CL moves all except those four to x_test.go and makes them regular tests. It also updates TestCause to use t.Run and t.Parallel to parallelize its test cases. It also adds documentation indicating when tests should be added to each file. Change-Id: Ic60bae32a7a44e07831b5388c9af219d53ba9af3 Reviewed-on: https://go-review.googlesource.com/c/go/+/480375 TryBot-Result: Gopher Robot <gobot@golang.org> Run-TryBot: Sameer Ajmani <sameer@golang.org> Reviewed-by: Ian Lance Taylor <iant@golang.org> Reviewed-by: Damien Neil <dneil@google.com>
This commit is contained in:
parent
0d719823af
commit
e7d59ed204
@ -4,13 +4,12 @@
|
||||
|
||||
package context
|
||||
|
||||
// Tests in package context cannot depend directly on package testing due to an import cycle.
|
||||
// If your test does requires access to unexported members of the context package,
|
||||
// add your test below as `func XTestFoo(t testingT)` and add a `TestFoo` to x_test.go
|
||||
// that calls it. Otherwise, write a regular test in a test.go file in package context_test.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -34,98 +33,7 @@ type testingT interface {
|
||||
Skipped() bool
|
||||
}
|
||||
|
||||
// otherContext is a Context that's not one of the types defined in context.go.
|
||||
// This lets us test code paths that differ based on the underlying type of the
|
||||
// Context.
|
||||
type otherContext struct {
|
||||
Context
|
||||
}
|
||||
|
||||
const (
|
||||
shortDuration = 1 * time.Millisecond // a reasonable duration to block in a test
|
||||
veryLongDuration = 1000 * time.Hour // an arbitrary upper bound on the test's running time
|
||||
)
|
||||
|
||||
// quiescent returns an arbitrary duration by which the program should have
|
||||
// completed any remaining work and reached a steady (idle) state.
|
||||
func quiescent(t testingT) time.Duration {
|
||||
deadline, ok := t.Deadline()
|
||||
if !ok {
|
||||
return 5 * time.Second
|
||||
}
|
||||
|
||||
const arbitraryCleanupMargin = 1 * time.Second
|
||||
return time.Until(deadline) - arbitraryCleanupMargin
|
||||
}
|
||||
|
||||
func XTestBackground(t testingT) {
|
||||
c := Background()
|
||||
if c == nil {
|
||||
t.Fatalf("Background returned nil")
|
||||
}
|
||||
select {
|
||||
case x := <-c.Done():
|
||||
t.Errorf("<-c.Done() == %v want nothing (it should block)", x)
|
||||
default:
|
||||
}
|
||||
if got, want := fmt.Sprint(c), "context.Background"; got != want {
|
||||
t.Errorf("Background().String() = %q want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func XTestTODO(t testingT) {
|
||||
c := TODO()
|
||||
if c == nil {
|
||||
t.Fatalf("TODO returned nil")
|
||||
}
|
||||
select {
|
||||
case x := <-c.Done():
|
||||
t.Errorf("<-c.Done() == %v want nothing (it should block)", x)
|
||||
default:
|
||||
}
|
||||
if got, want := fmt.Sprint(c), "context.TODO"; got != want {
|
||||
t.Errorf("TODO().String() = %q want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func XTestWithCancel(t testingT) {
|
||||
c1, cancel := WithCancel(Background())
|
||||
|
||||
if got, want := fmt.Sprint(c1), "context.Background.WithCancel"; got != want {
|
||||
t.Errorf("c1.String() = %q want %q", got, want)
|
||||
}
|
||||
|
||||
o := otherContext{c1}
|
||||
c2, _ := WithCancel(o)
|
||||
contexts := []Context{c1, o, c2}
|
||||
|
||||
for i, c := range contexts {
|
||||
if d := c.Done(); d == nil {
|
||||
t.Errorf("c[%d].Done() == %v want non-nil", i, d)
|
||||
}
|
||||
if e := c.Err(); e != nil {
|
||||
t.Errorf("c[%d].Err() == %v want nil", i, e)
|
||||
}
|
||||
|
||||
select {
|
||||
case x := <-c.Done():
|
||||
t.Errorf("<-c.Done() == %v want nothing (it should block)", x)
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
cancel() // Should propagate synchronously.
|
||||
for i, c := range contexts {
|
||||
select {
|
||||
case <-c.Done():
|
||||
default:
|
||||
t.Errorf("<-c[%d].Done() blocked, but shouldn't have", i)
|
||||
}
|
||||
if e := c.Err(); e != Canceled {
|
||||
t.Errorf("c[%d].Err() == %v want %v", i, e, Canceled)
|
||||
}
|
||||
}
|
||||
}
|
||||
const veryLongDuration = 1000 * time.Hour // an arbitrary upper bound on the test's running time
|
||||
|
||||
func contains(m map[canceler]struct{}, key canceler) bool {
|
||||
_, ret := m[key]
|
||||
@ -269,365 +177,6 @@ func XTestChildFinishesFirst(t testingT) {
|
||||
}
|
||||
}
|
||||
|
||||
func testDeadline(c Context, name string, t testingT) {
|
||||
t.Helper()
|
||||
d := quiescent(t)
|
||||
timer := time.NewTimer(d)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case <-timer.C:
|
||||
t.Fatalf("%s: context not timed out after %v", name, d)
|
||||
case <-c.Done():
|
||||
}
|
||||
if e := c.Err(); e != DeadlineExceeded {
|
||||
t.Errorf("%s: c.Err() == %v; want %v", name, e, DeadlineExceeded)
|
||||
}
|
||||
}
|
||||
|
||||
func XTestDeadline(t testingT) {
|
||||
t.Parallel()
|
||||
|
||||
c, _ := WithDeadline(Background(), time.Now().Add(shortDuration))
|
||||
if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) {
|
||||
t.Errorf("c.String() = %q want prefix %q", got, prefix)
|
||||
}
|
||||
testDeadline(c, "WithDeadline", t)
|
||||
|
||||
c, _ = WithDeadline(Background(), time.Now().Add(shortDuration))
|
||||
o := otherContext{c}
|
||||
testDeadline(o, "WithDeadline+otherContext", t)
|
||||
|
||||
c, _ = WithDeadline(Background(), time.Now().Add(shortDuration))
|
||||
o = otherContext{c}
|
||||
c, _ = WithDeadline(o, time.Now().Add(veryLongDuration))
|
||||
testDeadline(c, "WithDeadline+otherContext+WithDeadline", t)
|
||||
|
||||
c, _ = WithDeadline(Background(), time.Now().Add(-shortDuration))
|
||||
testDeadline(c, "WithDeadline+inthepast", t)
|
||||
|
||||
c, _ = WithDeadline(Background(), time.Now())
|
||||
testDeadline(c, "WithDeadline+now", t)
|
||||
}
|
||||
|
||||
func XTestTimeout(t testingT) {
|
||||
t.Parallel()
|
||||
|
||||
c, _ := WithTimeout(Background(), shortDuration)
|
||||
if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) {
|
||||
t.Errorf("c.String() = %q want prefix %q", got, prefix)
|
||||
}
|
||||
testDeadline(c, "WithTimeout", t)
|
||||
|
||||
c, _ = WithTimeout(Background(), shortDuration)
|
||||
o := otherContext{c}
|
||||
testDeadline(o, "WithTimeout+otherContext", t)
|
||||
|
||||
c, _ = WithTimeout(Background(), shortDuration)
|
||||
o = otherContext{c}
|
||||
c, _ = WithTimeout(o, veryLongDuration)
|
||||
testDeadline(c, "WithTimeout+otherContext+WithTimeout", t)
|
||||
}
|
||||
|
||||
func XTestCanceledTimeout(t testingT) {
|
||||
c, _ := WithTimeout(Background(), time.Second)
|
||||
o := otherContext{c}
|
||||
c, cancel := WithTimeout(o, veryLongDuration)
|
||||
cancel() // Should propagate synchronously.
|
||||
select {
|
||||
case <-c.Done():
|
||||
default:
|
||||
t.Errorf("<-c.Done() blocked, but shouldn't have")
|
||||
}
|
||||
if e := c.Err(); e != Canceled {
|
||||
t.Errorf("c.Err() == %v want %v", e, Canceled)
|
||||
}
|
||||
}
|
||||
|
||||
type key1 int
|
||||
type key2 int
|
||||
|
||||
var k1 = key1(1)
|
||||
var k2 = key2(1) // same int as k1, different type
|
||||
var k3 = key2(3) // same type as k2, different int
|
||||
|
||||
func XTestValues(t testingT) {
|
||||
check := func(c Context, nm, v1, v2, v3 string) {
|
||||
if v, ok := c.Value(k1).(string); ok == (len(v1) == 0) || v != v1 {
|
||||
t.Errorf(`%s.Value(k1).(string) = %q, %t want %q, %t`, nm, v, ok, v1, len(v1) != 0)
|
||||
}
|
||||
if v, ok := c.Value(k2).(string); ok == (len(v2) == 0) || v != v2 {
|
||||
t.Errorf(`%s.Value(k2).(string) = %q, %t want %q, %t`, nm, v, ok, v2, len(v2) != 0)
|
||||
}
|
||||
if v, ok := c.Value(k3).(string); ok == (len(v3) == 0) || v != v3 {
|
||||
t.Errorf(`%s.Value(k3).(string) = %q, %t want %q, %t`, nm, v, ok, v3, len(v3) != 0)
|
||||
}
|
||||
}
|
||||
|
||||
c0 := Background()
|
||||
check(c0, "c0", "", "", "")
|
||||
|
||||
c1 := WithValue(Background(), k1, "c1k1")
|
||||
check(c1, "c1", "c1k1", "", "")
|
||||
|
||||
if got, want := fmt.Sprint(c1), `context.Background.WithValue(type context.key1, val c1k1)`; got != want {
|
||||
t.Errorf("c.String() = %q want %q", got, want)
|
||||
}
|
||||
|
||||
c2 := WithValue(c1, k2, "c2k2")
|
||||
check(c2, "c2", "c1k1", "c2k2", "")
|
||||
|
||||
c3 := WithValue(c2, k3, "c3k3")
|
||||
check(c3, "c2", "c1k1", "c2k2", "c3k3")
|
||||
|
||||
c4 := WithValue(c3, k1, nil)
|
||||
check(c4, "c4", "", "c2k2", "c3k3")
|
||||
|
||||
o0 := otherContext{Background()}
|
||||
check(o0, "o0", "", "", "")
|
||||
|
||||
o1 := otherContext{WithValue(Background(), k1, "c1k1")}
|
||||
check(o1, "o1", "c1k1", "", "")
|
||||
|
||||
o2 := WithValue(o1, k2, "o2k2")
|
||||
check(o2, "o2", "c1k1", "o2k2", "")
|
||||
|
||||
o3 := otherContext{c4}
|
||||
check(o3, "o3", "", "c2k2", "c3k3")
|
||||
|
||||
o4 := WithValue(o3, k3, nil)
|
||||
check(o4, "o4", "", "c2k2", "")
|
||||
}
|
||||
|
||||
func XTestAllocs(t testingT, testingShort func() bool, testingAllocsPerRun func(int, func()) float64) {
|
||||
bg := Background()
|
||||
for _, test := range []struct {
|
||||
desc string
|
||||
f func()
|
||||
limit float64
|
||||
gccgoLimit float64
|
||||
}{
|
||||
{
|
||||
desc: "Background()",
|
||||
f: func() { Background() },
|
||||
limit: 0,
|
||||
gccgoLimit: 0,
|
||||
},
|
||||
{
|
||||
desc: fmt.Sprintf("WithValue(bg, %v, nil)", k1),
|
||||
f: func() {
|
||||
c := WithValue(bg, k1, nil)
|
||||
c.Value(k1)
|
||||
},
|
||||
limit: 3,
|
||||
gccgoLimit: 3,
|
||||
},
|
||||
{
|
||||
desc: "WithTimeout(bg, 1*time.Nanosecond)",
|
||||
f: func() {
|
||||
c, _ := WithTimeout(bg, 1*time.Nanosecond)
|
||||
<-c.Done()
|
||||
},
|
||||
limit: 12,
|
||||
gccgoLimit: 15,
|
||||
},
|
||||
{
|
||||
desc: "WithCancel(bg)",
|
||||
f: func() {
|
||||
c, cancel := WithCancel(bg)
|
||||
cancel()
|
||||
<-c.Done()
|
||||
},
|
||||
limit: 5,
|
||||
gccgoLimit: 8,
|
||||
},
|
||||
{
|
||||
desc: "WithTimeout(bg, 5*time.Millisecond)",
|
||||
f: func() {
|
||||
c, cancel := WithTimeout(bg, 5*time.Millisecond)
|
||||
cancel()
|
||||
<-c.Done()
|
||||
},
|
||||
limit: 8,
|
||||
gccgoLimit: 25,
|
||||
},
|
||||
} {
|
||||
limit := test.limit
|
||||
if runtime.Compiler == "gccgo" {
|
||||
// gccgo does not yet do escape analysis.
|
||||
// TODO(iant): Remove this when gccgo does do escape analysis.
|
||||
limit = test.gccgoLimit
|
||||
}
|
||||
numRuns := 100
|
||||
if testingShort() {
|
||||
numRuns = 10
|
||||
}
|
||||
if n := testingAllocsPerRun(numRuns, test.f); n > limit {
|
||||
t.Errorf("%s allocs = %f want %d", test.desc, n, int(limit))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func XTestSimultaneousCancels(t testingT) {
|
||||
root, cancel := WithCancel(Background())
|
||||
m := map[Context]CancelFunc{root: cancel}
|
||||
q := []Context{root}
|
||||
// Create a tree of contexts.
|
||||
for len(q) != 0 && len(m) < 100 {
|
||||
parent := q[0]
|
||||
q = q[1:]
|
||||
for i := 0; i < 4; i++ {
|
||||
ctx, cancel := WithCancel(parent)
|
||||
m[ctx] = cancel
|
||||
q = append(q, ctx)
|
||||
}
|
||||
}
|
||||
// Start all the cancels in a random order.
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(m))
|
||||
for _, cancel := range m {
|
||||
go func(cancel CancelFunc) {
|
||||
cancel()
|
||||
wg.Done()
|
||||
}(cancel)
|
||||
}
|
||||
|
||||
d := quiescent(t)
|
||||
stuck := make(chan struct{})
|
||||
timer := time.AfterFunc(d, func() { close(stuck) })
|
||||
defer timer.Stop()
|
||||
|
||||
// Wait on all the contexts in a random order.
|
||||
for ctx := range m {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-stuck:
|
||||
buf := make([]byte, 10<<10)
|
||||
n := runtime.Stack(buf, true)
|
||||
t.Fatalf("timed out after %v waiting for <-ctx.Done(); stacks:\n%s", d, buf[:n])
|
||||
}
|
||||
}
|
||||
// Wait for all the cancel functions to return.
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(done)
|
||||
}()
|
||||
select {
|
||||
case <-done:
|
||||
case <-stuck:
|
||||
buf := make([]byte, 10<<10)
|
||||
n := runtime.Stack(buf, true)
|
||||
t.Fatalf("timed out after %v waiting for cancel functions; stacks:\n%s", d, buf[:n])
|
||||
}
|
||||
}
|
||||
|
||||
func XTestInterlockedCancels(t testingT) {
|
||||
parent, cancelParent := WithCancel(Background())
|
||||
child, cancelChild := WithCancel(parent)
|
||||
go func() {
|
||||
<-parent.Done()
|
||||
cancelChild()
|
||||
}()
|
||||
cancelParent()
|
||||
d := quiescent(t)
|
||||
timer := time.NewTimer(d)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case <-child.Done():
|
||||
case <-timer.C:
|
||||
buf := make([]byte, 10<<10)
|
||||
n := runtime.Stack(buf, true)
|
||||
t.Fatalf("timed out after %v waiting for child.Done(); stacks:\n%s", d, buf[:n])
|
||||
}
|
||||
}
|
||||
|
||||
func XTestLayersCancel(t testingT) {
|
||||
testLayers(t, time.Now().UnixNano(), false)
|
||||
}
|
||||
|
||||
func XTestLayersTimeout(t testingT) {
|
||||
testLayers(t, time.Now().UnixNano(), true)
|
||||
}
|
||||
|
||||
func testLayers(t testingT, seed int64, testTimeout bool) {
|
||||
t.Parallel()
|
||||
|
||||
r := rand.New(rand.NewSource(seed))
|
||||
errorf := func(format string, a ...any) {
|
||||
t.Errorf(fmt.Sprintf("seed=%d: %s", seed, format), a...)
|
||||
}
|
||||
const (
|
||||
minLayers = 30
|
||||
)
|
||||
type value int
|
||||
var (
|
||||
vals []*value
|
||||
cancels []CancelFunc
|
||||
numTimers int
|
||||
ctx = Background()
|
||||
)
|
||||
for i := 0; i < minLayers || numTimers == 0 || len(cancels) == 0 || len(vals) == 0; i++ {
|
||||
switch r.Intn(3) {
|
||||
case 0:
|
||||
v := new(value)
|
||||
ctx = WithValue(ctx, v, v)
|
||||
vals = append(vals, v)
|
||||
case 1:
|
||||
var cancel CancelFunc
|
||||
ctx, cancel = WithCancel(ctx)
|
||||
cancels = append(cancels, cancel)
|
||||
case 2:
|
||||
var cancel CancelFunc
|
||||
d := veryLongDuration
|
||||
if testTimeout {
|
||||
d = shortDuration
|
||||
}
|
||||
ctx, cancel = WithTimeout(ctx, d)
|
||||
cancels = append(cancels, cancel)
|
||||
numTimers++
|
||||
}
|
||||
}
|
||||
checkValues := func(when string) {
|
||||
for _, key := range vals {
|
||||
if val := ctx.Value(key).(*value); key != val {
|
||||
errorf("%s: ctx.Value(%p) = %p want %p", when, key, val, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !testTimeout {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
errorf("ctx should not be canceled yet")
|
||||
default:
|
||||
}
|
||||
}
|
||||
if s, prefix := fmt.Sprint(ctx), "context.Background."; !strings.HasPrefix(s, prefix) {
|
||||
t.Errorf("ctx.String() = %q want prefix %q", s, prefix)
|
||||
}
|
||||
t.Log(ctx)
|
||||
checkValues("before cancel")
|
||||
if testTimeout {
|
||||
d := quiescent(t)
|
||||
timer := time.NewTimer(d)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-timer.C:
|
||||
errorf("ctx should have timed out after %v", d)
|
||||
}
|
||||
checkValues("after timeout")
|
||||
} else {
|
||||
cancel := cancels[r.Intn(len(cancels))]
|
||||
cancel()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
default:
|
||||
errorf("ctx should be canceled")
|
||||
}
|
||||
checkValues("after cancel")
|
||||
}
|
||||
}
|
||||
|
||||
func XTestCancelRemoves(t testingT) {
|
||||
checkChildren := func(when string, ctx Context, want int) {
|
||||
if got := len(ctx.(*cancelCtx).children); got != want {
|
||||
@ -650,69 +199,6 @@ func XTestCancelRemoves(t testingT) {
|
||||
checkChildren("after canceling WithTimeout child", ctx, 0)
|
||||
}
|
||||
|
||||
func XTestWithCancelCanceledParent(t testingT) {
|
||||
parent, pcancel := WithCancelCause(Background())
|
||||
cause := fmt.Errorf("Because!")
|
||||
pcancel(cause)
|
||||
|
||||
c, _ := WithCancel(parent)
|
||||
select {
|
||||
case <-c.Done():
|
||||
default:
|
||||
t.Errorf("child not done immediately upon construction")
|
||||
}
|
||||
if got, want := c.Err(), Canceled; got != want {
|
||||
t.Errorf("child not canceled; got = %v, want = %v", got, want)
|
||||
}
|
||||
if got, want := Cause(c), cause; got != want {
|
||||
t.Errorf("child has wrong cause; got = %v, want = %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func XTestWithValueChecksKey(t testingT) {
|
||||
panicVal := recoveredValue(func() { WithValue(Background(), []byte("foo"), "bar") })
|
||||
if panicVal == nil {
|
||||
t.Error("expected panic")
|
||||
}
|
||||
panicVal = recoveredValue(func() { WithValue(Background(), nil, "bar") })
|
||||
if got, want := fmt.Sprint(panicVal), "nil key"; got != want {
|
||||
t.Errorf("panic = %q; want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func XTestInvalidDerivedFail(t testingT) {
|
||||
panicVal := recoveredValue(func() { WithCancel(nil) })
|
||||
if panicVal == nil {
|
||||
t.Error("expected panic")
|
||||
}
|
||||
panicVal = recoveredValue(func() { WithDeadline(nil, time.Now().Add(shortDuration)) })
|
||||
if panicVal == nil {
|
||||
t.Error("expected panic")
|
||||
}
|
||||
panicVal = recoveredValue(func() { WithValue(nil, "foo", "bar") })
|
||||
if panicVal == nil {
|
||||
t.Error("expected panic")
|
||||
}
|
||||
}
|
||||
|
||||
func recoveredValue(fn func()) (v any) {
|
||||
defer func() { v = recover() }()
|
||||
fn()
|
||||
return
|
||||
}
|
||||
|
||||
func XTestDeadlineExceededSupportsTimeout(t testingT) {
|
||||
i, ok := DeadlineExceeded.(interface {
|
||||
Timeout() bool
|
||||
})
|
||||
if !ok {
|
||||
t.Fatal("DeadlineExceeded does not support Timeout interface")
|
||||
}
|
||||
if !i.Timeout() {
|
||||
t.Fatal("wrong value for timeout")
|
||||
}
|
||||
}
|
||||
|
||||
type myCtx struct {
|
||||
Context
|
||||
}
|
||||
@ -725,7 +211,6 @@ func (d *myDoneCtx) Done() <-chan struct{} {
|
||||
c := make(chan struct{})
|
||||
return c
|
||||
}
|
||||
|
||||
func XTestCustomContextGoroutines(t testingT) {
|
||||
g := goroutines.Load()
|
||||
checkNoGoroutine := func() {
|
||||
@ -790,270 +275,3 @@ func XTestCustomContextGoroutines(t testingT) {
|
||||
defer cancel7()
|
||||
checkNoGoroutine()
|
||||
}
|
||||
|
||||
func XTestCause(t testingT) {
|
||||
var (
|
||||
forever = 1e6 * time.Second
|
||||
parentCause = fmt.Errorf("parentCause")
|
||||
childCause = fmt.Errorf("childCause")
|
||||
tooSlow = fmt.Errorf("tooSlow")
|
||||
finishedEarly = fmt.Errorf("finishedEarly")
|
||||
)
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
ctx Context
|
||||
err error
|
||||
cause error
|
||||
}{
|
||||
{
|
||||
name: "Background",
|
||||
ctx: Background(),
|
||||
err: nil,
|
||||
cause: nil,
|
||||
},
|
||||
{
|
||||
name: "TODO",
|
||||
ctx: TODO(),
|
||||
err: nil,
|
||||
cause: nil,
|
||||
},
|
||||
{
|
||||
name: "WithCancel",
|
||||
ctx: func() Context {
|
||||
ctx, cancel := WithCancel(Background())
|
||||
cancel()
|
||||
return ctx
|
||||
}(),
|
||||
err: Canceled,
|
||||
cause: Canceled,
|
||||
},
|
||||
{
|
||||
name: "WithCancelCause",
|
||||
ctx: func() Context {
|
||||
ctx, cancel := WithCancelCause(Background())
|
||||
cancel(parentCause)
|
||||
return ctx
|
||||
}(),
|
||||
err: Canceled,
|
||||
cause: parentCause,
|
||||
},
|
||||
{
|
||||
name: "WithCancelCause nil",
|
||||
ctx: func() Context {
|
||||
ctx, cancel := WithCancelCause(Background())
|
||||
cancel(nil)
|
||||
return ctx
|
||||
}(),
|
||||
err: Canceled,
|
||||
cause: Canceled,
|
||||
},
|
||||
{
|
||||
name: "WithCancelCause: parent cause before child",
|
||||
ctx: func() Context {
|
||||
ctx, cancelParent := WithCancelCause(Background())
|
||||
ctx, cancelChild := WithCancelCause(ctx)
|
||||
cancelParent(parentCause)
|
||||
cancelChild(childCause)
|
||||
return ctx
|
||||
}(),
|
||||
err: Canceled,
|
||||
cause: parentCause,
|
||||
},
|
||||
{
|
||||
name: "WithCancelCause: parent cause after child",
|
||||
ctx: func() Context {
|
||||
ctx, cancelParent := WithCancelCause(Background())
|
||||
ctx, cancelChild := WithCancelCause(ctx)
|
||||
cancelChild(childCause)
|
||||
cancelParent(parentCause)
|
||||
return ctx
|
||||
}(),
|
||||
err: Canceled,
|
||||
cause: childCause,
|
||||
},
|
||||
{
|
||||
name: "WithCancelCause: parent cause before nil",
|
||||
ctx: func() Context {
|
||||
ctx, cancelParent := WithCancelCause(Background())
|
||||
ctx, cancelChild := WithCancel(ctx)
|
||||
cancelParent(parentCause)
|
||||
cancelChild()
|
||||
return ctx
|
||||
}(),
|
||||
err: Canceled,
|
||||
cause: parentCause,
|
||||
},
|
||||
{
|
||||
name: "WithCancelCause: parent cause after nil",
|
||||
ctx: func() Context {
|
||||
ctx, cancelParent := WithCancelCause(Background())
|
||||
ctx, cancelChild := WithCancel(ctx)
|
||||
cancelChild()
|
||||
cancelParent(parentCause)
|
||||
return ctx
|
||||
}(),
|
||||
err: Canceled,
|
||||
cause: Canceled,
|
||||
},
|
||||
{
|
||||
name: "WithCancelCause: child cause after nil",
|
||||
ctx: func() Context {
|
||||
ctx, cancelParent := WithCancel(Background())
|
||||
ctx, cancelChild := WithCancelCause(ctx)
|
||||
cancelParent()
|
||||
cancelChild(childCause)
|
||||
return ctx
|
||||
}(),
|
||||
err: Canceled,
|
||||
cause: Canceled,
|
||||
},
|
||||
{
|
||||
name: "WithCancelCause: child cause before nil",
|
||||
ctx: func() Context {
|
||||
ctx, cancelParent := WithCancel(Background())
|
||||
ctx, cancelChild := WithCancelCause(ctx)
|
||||
cancelChild(childCause)
|
||||
cancelParent()
|
||||
return ctx
|
||||
}(),
|
||||
err: Canceled,
|
||||
cause: childCause,
|
||||
},
|
||||
{
|
||||
name: "WithTimeout",
|
||||
ctx: func() Context {
|
||||
ctx, cancel := WithTimeout(Background(), 0)
|
||||
cancel()
|
||||
return ctx
|
||||
}(),
|
||||
err: DeadlineExceeded,
|
||||
cause: DeadlineExceeded,
|
||||
},
|
||||
{
|
||||
name: "WithTimeout canceled",
|
||||
ctx: func() Context {
|
||||
ctx, cancel := WithTimeout(Background(), forever)
|
||||
cancel()
|
||||
return ctx
|
||||
}(),
|
||||
err: Canceled,
|
||||
cause: Canceled,
|
||||
},
|
||||
{
|
||||
name: "WithTimeoutCause",
|
||||
ctx: func() Context {
|
||||
ctx, cancel := WithTimeoutCause(Background(), 0, tooSlow)
|
||||
cancel()
|
||||
return ctx
|
||||
}(),
|
||||
err: DeadlineExceeded,
|
||||
cause: tooSlow,
|
||||
},
|
||||
{
|
||||
name: "WithTimeoutCause canceled",
|
||||
ctx: func() Context {
|
||||
ctx, cancel := WithTimeoutCause(Background(), forever, tooSlow)
|
||||
cancel()
|
||||
return ctx
|
||||
}(),
|
||||
err: Canceled,
|
||||
cause: Canceled,
|
||||
},
|
||||
{
|
||||
name: "WithTimeoutCause stacked",
|
||||
ctx: func() Context {
|
||||
ctx, cancel := WithCancelCause(Background())
|
||||
ctx, _ = WithTimeoutCause(ctx, 0, tooSlow)
|
||||
cancel(finishedEarly)
|
||||
return ctx
|
||||
}(),
|
||||
err: DeadlineExceeded,
|
||||
cause: tooSlow,
|
||||
},
|
||||
{
|
||||
name: "WithTimeoutCause stacked canceled",
|
||||
ctx: func() Context {
|
||||
ctx, cancel := WithCancelCause(Background())
|
||||
ctx, _ = WithTimeoutCause(ctx, forever, tooSlow)
|
||||
cancel(finishedEarly)
|
||||
return ctx
|
||||
}(),
|
||||
err: Canceled,
|
||||
cause: finishedEarly,
|
||||
},
|
||||
{
|
||||
name: "WithoutCancel",
|
||||
ctx: func() Context {
|
||||
return WithoutCancel(Background())
|
||||
}(),
|
||||
err: nil,
|
||||
cause: nil,
|
||||
},
|
||||
{
|
||||
name: "WithoutCancel canceled",
|
||||
ctx: func() Context {
|
||||
ctx, cancel := WithCancelCause(Background())
|
||||
ctx = WithoutCancel(ctx)
|
||||
cancel(finishedEarly)
|
||||
return ctx
|
||||
}(),
|
||||
err: nil,
|
||||
cause: nil,
|
||||
},
|
||||
{
|
||||
name: "WithoutCancel timeout",
|
||||
ctx: func() Context {
|
||||
ctx, cancel := WithTimeoutCause(Background(), 0, tooSlow)
|
||||
ctx = WithoutCancel(ctx)
|
||||
cancel()
|
||||
return ctx
|
||||
}(),
|
||||
err: nil,
|
||||
cause: nil,
|
||||
},
|
||||
} {
|
||||
if got, want := test.ctx.Err(), test.err; want != got {
|
||||
t.Errorf("%s: ctx.Err() = %v want %v", test.name, got, want)
|
||||
}
|
||||
if got, want := Cause(test.ctx), test.cause; want != got {
|
||||
t.Errorf("%s: Cause(ctx) = %v want %v", test.name, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func XTestCauseRace(t testingT) {
|
||||
cause := errors.New("TestCauseRace")
|
||||
ctx, cancel := WithCancelCause(Background())
|
||||
go func() {
|
||||
cancel(cause)
|
||||
}()
|
||||
for {
|
||||
// Poll Cause, rather than waiting for Done, to test that
|
||||
// access to the underlying cause is synchronized properly.
|
||||
if err := Cause(ctx); err != nil {
|
||||
if err != cause {
|
||||
t.Errorf("Cause returned %v, want %v", err, cause)
|
||||
}
|
||||
break
|
||||
}
|
||||
runtime.Gosched()
|
||||
}
|
||||
}
|
||||
|
||||
func XTestWithoutCancel(t testingT) {
|
||||
key, value := "key", "value"
|
||||
ctx := WithValue(Background(), key, value)
|
||||
ctx = WithoutCancel(ctx)
|
||||
if d, ok := ctx.Deadline(); !d.IsZero() || ok != false {
|
||||
t.Errorf("ctx.Deadline() = %v, %v want zero, false", d, ok)
|
||||
}
|
||||
if done := ctx.Done(); done != nil {
|
||||
t.Errorf("ctx.Deadline() = %v want nil", done)
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
t.Errorf("ctx.Err() = %v want nil", err)
|
||||
}
|
||||
if v := ctx.Value(key); v != value {
|
||||
t.Errorf("ctx.Value(%q) = %q want %q", key, v, value)
|
||||
}
|
||||
}
|
||||
|
@ -10,8 +10,6 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const shortDuration = 1 * time.Millisecond // a reasonable duration to block in an example
|
||||
|
||||
var neverReady = make(chan struct{}) // never closed
|
||||
|
||||
// This example demonstrates the use of a cancelable context to prevent a
|
||||
|
@ -6,29 +6,813 @@ package context_test
|
||||
|
||||
import (
|
||||
. "context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestBackground(t *testing.T) { XTestBackground(t) }
|
||||
func TestTODO(t *testing.T) { XTestTODO(t) }
|
||||
func TestWithCancel(t *testing.T) { XTestWithCancel(t) }
|
||||
func TestParentFinishesChild(t *testing.T) { XTestParentFinishesChild(t) }
|
||||
func TestChildFinishesFirst(t *testing.T) { XTestChildFinishesFirst(t) }
|
||||
func TestDeadline(t *testing.T) { XTestDeadline(t) }
|
||||
func TestTimeout(t *testing.T) { XTestTimeout(t) }
|
||||
func TestCanceledTimeout(t *testing.T) { XTestCanceledTimeout(t) }
|
||||
func TestValues(t *testing.T) { XTestValues(t) }
|
||||
func TestAllocs(t *testing.T) { XTestAllocs(t, testing.Short, testing.AllocsPerRun) }
|
||||
func TestSimultaneousCancels(t *testing.T) { XTestSimultaneousCancels(t) }
|
||||
func TestInterlockedCancels(t *testing.T) { XTestInterlockedCancels(t) }
|
||||
func TestLayersCancel(t *testing.T) { XTestLayersCancel(t) }
|
||||
func TestLayersTimeout(t *testing.T) { XTestLayersTimeout(t) }
|
||||
func TestCancelRemoves(t *testing.T) { XTestCancelRemoves(t) }
|
||||
func TestWithCancelCanceledParent(t *testing.T) { XTestWithCancelCanceledParent(t) }
|
||||
func TestWithValueChecksKey(t *testing.T) { XTestWithValueChecksKey(t) }
|
||||
func TestInvalidDerivedFail(t *testing.T) { XTestInvalidDerivedFail(t) }
|
||||
func TestDeadlineExceededSupportsTimeout(t *testing.T) { XTestDeadlineExceededSupportsTimeout(t) }
|
||||
func TestCustomContextGoroutines(t *testing.T) { XTestCustomContextGoroutines(t) }
|
||||
func TestCause(t *testing.T) { XTestCause(t) }
|
||||
func TestCauseRace(t *testing.T) { XTestCauseRace(t) }
|
||||
func TestWithoutCancel(t *testing.T) { XTestWithoutCancel(t) }
|
||||
// Each XTestFoo in context_test.go must be called from a TestFoo here to run.
|
||||
func TestParentFinishesChild(t *testing.T) {
|
||||
XTestParentFinishesChild(t) // uses unexported context types
|
||||
}
|
||||
func TestChildFinishesFirst(t *testing.T) {
|
||||
XTestChildFinishesFirst(t) // uses unexported context types
|
||||
}
|
||||
func TestCancelRemoves(t *testing.T) {
|
||||
XTestCancelRemoves(t) // uses unexported context types
|
||||
}
|
||||
func TestCustomContextGoroutines(t *testing.T) {
|
||||
XTestCustomContextGoroutines(t) // reads the context.goroutines counter
|
||||
}
|
||||
|
||||
// The following are regular tests in package context_test.
|
||||
|
||||
// otherContext is a Context that's not one of the types defined in context.go.
|
||||
// This lets us test code paths that differ based on the underlying type of the
|
||||
// Context.
|
||||
type otherContext struct {
|
||||
Context
|
||||
}
|
||||
|
||||
const (
|
||||
shortDuration = 1 * time.Millisecond // a reasonable duration to block in a test
|
||||
veryLongDuration = 1000 * time.Hour // an arbitrary upper bound on the test's running time
|
||||
)
|
||||
|
||||
// quiescent returns an arbitrary duration by which the program should have
|
||||
// completed any remaining work and reached a steady (idle) state.
|
||||
func quiescent(t *testing.T) time.Duration {
|
||||
deadline, ok := t.Deadline()
|
||||
if !ok {
|
||||
return 5 * time.Second
|
||||
}
|
||||
|
||||
const arbitraryCleanupMargin = 1 * time.Second
|
||||
return time.Until(deadline) - arbitraryCleanupMargin
|
||||
}
|
||||
func TestBackground(t *testing.T) {
|
||||
c := Background()
|
||||
if c == nil {
|
||||
t.Fatalf("Background returned nil")
|
||||
}
|
||||
select {
|
||||
case x := <-c.Done():
|
||||
t.Errorf("<-c.Done() == %v want nothing (it should block)", x)
|
||||
default:
|
||||
}
|
||||
if got, want := fmt.Sprint(c), "context.Background"; got != want {
|
||||
t.Errorf("Background().String() = %q want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTODO(t *testing.T) {
|
||||
c := TODO()
|
||||
if c == nil {
|
||||
t.Fatalf("TODO returned nil")
|
||||
}
|
||||
select {
|
||||
case x := <-c.Done():
|
||||
t.Errorf("<-c.Done() == %v want nothing (it should block)", x)
|
||||
default:
|
||||
}
|
||||
if got, want := fmt.Sprint(c), "context.TODO"; got != want {
|
||||
t.Errorf("TODO().String() = %q want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithCancel(t *testing.T) {
|
||||
c1, cancel := WithCancel(Background())
|
||||
|
||||
if got, want := fmt.Sprint(c1), "context.Background.WithCancel"; got != want {
|
||||
t.Errorf("c1.String() = %q want %q", got, want)
|
||||
}
|
||||
|
||||
o := otherContext{c1}
|
||||
c2, _ := WithCancel(o)
|
||||
contexts := []Context{c1, o, c2}
|
||||
|
||||
for i, c := range contexts {
|
||||
if d := c.Done(); d == nil {
|
||||
t.Errorf("c[%d].Done() == %v want non-nil", i, d)
|
||||
}
|
||||
if e := c.Err(); e != nil {
|
||||
t.Errorf("c[%d].Err() == %v want nil", i, e)
|
||||
}
|
||||
|
||||
select {
|
||||
case x := <-c.Done():
|
||||
t.Errorf("<-c.Done() == %v want nothing (it should block)", x)
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
cancel() // Should propagate synchronously.
|
||||
for i, c := range contexts {
|
||||
select {
|
||||
case <-c.Done():
|
||||
default:
|
||||
t.Errorf("<-c[%d].Done() blocked, but shouldn't have", i)
|
||||
}
|
||||
if e := c.Err(); e != Canceled {
|
||||
t.Errorf("c[%d].Err() == %v want %v", i, e, Canceled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testDeadline(c Context, name string, t *testing.T) {
|
||||
t.Helper()
|
||||
d := quiescent(t)
|
||||
timer := time.NewTimer(d)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case <-timer.C:
|
||||
t.Fatalf("%s: context not timed out after %v", name, d)
|
||||
case <-c.Done():
|
||||
}
|
||||
if e := c.Err(); e != DeadlineExceeded {
|
||||
t.Errorf("%s: c.Err() == %v; want %v", name, e, DeadlineExceeded)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeadline(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
c, _ := WithDeadline(Background(), time.Now().Add(shortDuration))
|
||||
if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) {
|
||||
t.Errorf("c.String() = %q want prefix %q", got, prefix)
|
||||
}
|
||||
testDeadline(c, "WithDeadline", t)
|
||||
|
||||
c, _ = WithDeadline(Background(), time.Now().Add(shortDuration))
|
||||
o := otherContext{c}
|
||||
testDeadline(o, "WithDeadline+otherContext", t)
|
||||
|
||||
c, _ = WithDeadline(Background(), time.Now().Add(shortDuration))
|
||||
o = otherContext{c}
|
||||
c, _ = WithDeadline(o, time.Now().Add(veryLongDuration))
|
||||
testDeadline(c, "WithDeadline+otherContext+WithDeadline", t)
|
||||
|
||||
c, _ = WithDeadline(Background(), time.Now().Add(-shortDuration))
|
||||
testDeadline(c, "WithDeadline+inthepast", t)
|
||||
|
||||
c, _ = WithDeadline(Background(), time.Now())
|
||||
testDeadline(c, "WithDeadline+now", t)
|
||||
}
|
||||
|
||||
func TestTimeout(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
c, _ := WithTimeout(Background(), shortDuration)
|
||||
if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) {
|
||||
t.Errorf("c.String() = %q want prefix %q", got, prefix)
|
||||
}
|
||||
testDeadline(c, "WithTimeout", t)
|
||||
|
||||
c, _ = WithTimeout(Background(), shortDuration)
|
||||
o := otherContext{c}
|
||||
testDeadline(o, "WithTimeout+otherContext", t)
|
||||
|
||||
c, _ = WithTimeout(Background(), shortDuration)
|
||||
o = otherContext{c}
|
||||
c, _ = WithTimeout(o, veryLongDuration)
|
||||
testDeadline(c, "WithTimeout+otherContext+WithTimeout", t)
|
||||
}
|
||||
|
||||
func TestCanceledTimeout(t *testing.T) {
|
||||
c, _ := WithTimeout(Background(), time.Second)
|
||||
o := otherContext{c}
|
||||
c, cancel := WithTimeout(o, veryLongDuration)
|
||||
cancel() // Should propagate synchronously.
|
||||
select {
|
||||
case <-c.Done():
|
||||
default:
|
||||
t.Errorf("<-c.Done() blocked, but shouldn't have")
|
||||
}
|
||||
if e := c.Err(); e != Canceled {
|
||||
t.Errorf("c.Err() == %v want %v", e, Canceled)
|
||||
}
|
||||
}
|
||||
|
||||
type key1 int
|
||||
type key2 int
|
||||
|
||||
var k1 = key1(1)
|
||||
var k2 = key2(1) // same int as k1, different type
|
||||
var k3 = key2(3) // same type as k2, different int
|
||||
|
||||
func TestValues(t *testing.T) {
|
||||
check := func(c Context, nm, v1, v2, v3 string) {
|
||||
if v, ok := c.Value(k1).(string); ok == (len(v1) == 0) || v != v1 {
|
||||
t.Errorf(`%s.Value(k1).(string) = %q, %t want %q, %t`, nm, v, ok, v1, len(v1) != 0)
|
||||
}
|
||||
if v, ok := c.Value(k2).(string); ok == (len(v2) == 0) || v != v2 {
|
||||
t.Errorf(`%s.Value(k2).(string) = %q, %t want %q, %t`, nm, v, ok, v2, len(v2) != 0)
|
||||
}
|
||||
if v, ok := c.Value(k3).(string); ok == (len(v3) == 0) || v != v3 {
|
||||
t.Errorf(`%s.Value(k3).(string) = %q, %t want %q, %t`, nm, v, ok, v3, len(v3) != 0)
|
||||
}
|
||||
}
|
||||
|
||||
c0 := Background()
|
||||
check(c0, "c0", "", "", "")
|
||||
|
||||
c1 := WithValue(Background(), k1, "c1k1")
|
||||
check(c1, "c1", "c1k1", "", "")
|
||||
|
||||
if got, want := fmt.Sprint(c1), `context.Background.WithValue(type context_test.key1, val c1k1)`; got != want {
|
||||
t.Errorf("c.String() = %q want %q", got, want)
|
||||
}
|
||||
|
||||
c2 := WithValue(c1, k2, "c2k2")
|
||||
check(c2, "c2", "c1k1", "c2k2", "")
|
||||
|
||||
c3 := WithValue(c2, k3, "c3k3")
|
||||
check(c3, "c2", "c1k1", "c2k2", "c3k3")
|
||||
|
||||
c4 := WithValue(c3, k1, nil)
|
||||
check(c4, "c4", "", "c2k2", "c3k3")
|
||||
|
||||
o0 := otherContext{Background()}
|
||||
check(o0, "o0", "", "", "")
|
||||
|
||||
o1 := otherContext{WithValue(Background(), k1, "c1k1")}
|
||||
check(o1, "o1", "c1k1", "", "")
|
||||
|
||||
o2 := WithValue(o1, k2, "o2k2")
|
||||
check(o2, "o2", "c1k1", "o2k2", "")
|
||||
|
||||
o3 := otherContext{c4}
|
||||
check(o3, "o3", "", "c2k2", "c3k3")
|
||||
|
||||
o4 := WithValue(o3, k3, nil)
|
||||
check(o4, "o4", "", "c2k2", "")
|
||||
}
|
||||
|
||||
func TestAllocs(t *testing.T) {
|
||||
bg := Background()
|
||||
for _, test := range []struct {
|
||||
desc string
|
||||
f func()
|
||||
limit float64
|
||||
gccgoLimit float64
|
||||
}{
|
||||
{
|
||||
desc: "Background()",
|
||||
f: func() { Background() },
|
||||
limit: 0,
|
||||
gccgoLimit: 0,
|
||||
},
|
||||
{
|
||||
desc: fmt.Sprintf("WithValue(bg, %v, nil)", k1),
|
||||
f: func() {
|
||||
c := WithValue(bg, k1, nil)
|
||||
c.Value(k1)
|
||||
},
|
||||
limit: 3,
|
||||
gccgoLimit: 3,
|
||||
},
|
||||
{
|
||||
desc: "WithTimeout(bg, 1*time.Nanosecond)",
|
||||
f: func() {
|
||||
c, _ := WithTimeout(bg, 1*time.Nanosecond)
|
||||
<-c.Done()
|
||||
},
|
||||
limit: 12,
|
||||
gccgoLimit: 15,
|
||||
},
|
||||
{
|
||||
desc: "WithCancel(bg)",
|
||||
f: func() {
|
||||
c, cancel := WithCancel(bg)
|
||||
cancel()
|
||||
<-c.Done()
|
||||
},
|
||||
limit: 5,
|
||||
gccgoLimit: 8,
|
||||
},
|
||||
{
|
||||
desc: "WithTimeout(bg, 5*time.Millisecond)",
|
||||
f: func() {
|
||||
c, cancel := WithTimeout(bg, 5*time.Millisecond)
|
||||
cancel()
|
||||
<-c.Done()
|
||||
},
|
||||
limit: 8,
|
||||
gccgoLimit: 25,
|
||||
},
|
||||
} {
|
||||
limit := test.limit
|
||||
if runtime.Compiler == "gccgo" {
|
||||
// gccgo does not yet do escape analysis.
|
||||
// TODO(iant): Remove this when gccgo does do escape analysis.
|
||||
limit = test.gccgoLimit
|
||||
}
|
||||
numRuns := 100
|
||||
if testing.Short() {
|
||||
numRuns = 10
|
||||
}
|
||||
if n := testing.AllocsPerRun(numRuns, test.f); n > limit {
|
||||
t.Errorf("%s allocs = %f want %d", test.desc, n, int(limit))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimultaneousCancels(t *testing.T) {
|
||||
root, cancel := WithCancel(Background())
|
||||
m := map[Context]CancelFunc{root: cancel}
|
||||
q := []Context{root}
|
||||
// Create a tree of contexts.
|
||||
for len(q) != 0 && len(m) < 100 {
|
||||
parent := q[0]
|
||||
q = q[1:]
|
||||
for i := 0; i < 4; i++ {
|
||||
ctx, cancel := WithCancel(parent)
|
||||
m[ctx] = cancel
|
||||
q = append(q, ctx)
|
||||
}
|
||||
}
|
||||
// Start all the cancels in a random order.
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(m))
|
||||
for _, cancel := range m {
|
||||
go func(cancel CancelFunc) {
|
||||
cancel()
|
||||
wg.Done()
|
||||
}(cancel)
|
||||
}
|
||||
|
||||
d := quiescent(t)
|
||||
stuck := make(chan struct{})
|
||||
timer := time.AfterFunc(d, func() { close(stuck) })
|
||||
defer timer.Stop()
|
||||
|
||||
// Wait on all the contexts in a random order.
|
||||
for ctx := range m {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-stuck:
|
||||
buf := make([]byte, 10<<10)
|
||||
n := runtime.Stack(buf, true)
|
||||
t.Fatalf("timed out after %v waiting for <-ctx.Done(); stacks:\n%s", d, buf[:n])
|
||||
}
|
||||
}
|
||||
// Wait for all the cancel functions to return.
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(done)
|
||||
}()
|
||||
select {
|
||||
case <-done:
|
||||
case <-stuck:
|
||||
buf := make([]byte, 10<<10)
|
||||
n := runtime.Stack(buf, true)
|
||||
t.Fatalf("timed out after %v waiting for cancel functions; stacks:\n%s", d, buf[:n])
|
||||
}
|
||||
}
|
||||
|
||||
func TestInterlockedCancels(t *testing.T) {
|
||||
parent, cancelParent := WithCancel(Background())
|
||||
child, cancelChild := WithCancel(parent)
|
||||
go func() {
|
||||
<-parent.Done()
|
||||
cancelChild()
|
||||
}()
|
||||
cancelParent()
|
||||
d := quiescent(t)
|
||||
timer := time.NewTimer(d)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case <-child.Done():
|
||||
case <-timer.C:
|
||||
buf := make([]byte, 10<<10)
|
||||
n := runtime.Stack(buf, true)
|
||||
t.Fatalf("timed out after %v waiting for child.Done(); stacks:\n%s", d, buf[:n])
|
||||
}
|
||||
}
|
||||
|
||||
func TestLayersCancel(t *testing.T) {
|
||||
testLayers(t, time.Now().UnixNano(), false)
|
||||
}
|
||||
|
||||
func TestLayersTimeout(t *testing.T) {
|
||||
testLayers(t, time.Now().UnixNano(), true)
|
||||
}
|
||||
|
||||
func testLayers(t *testing.T, seed int64, testTimeout bool) {
|
||||
t.Parallel()
|
||||
|
||||
r := rand.New(rand.NewSource(seed))
|
||||
errorf := func(format string, a ...any) {
|
||||
t.Errorf(fmt.Sprintf("seed=%d: %s", seed, format), a...)
|
||||
}
|
||||
const (
|
||||
minLayers = 30
|
||||
)
|
||||
type value int
|
||||
var (
|
||||
vals []*value
|
||||
cancels []CancelFunc
|
||||
numTimers int
|
||||
ctx = Background()
|
||||
)
|
||||
for i := 0; i < minLayers || numTimers == 0 || len(cancels) == 0 || len(vals) == 0; i++ {
|
||||
switch r.Intn(3) {
|
||||
case 0:
|
||||
v := new(value)
|
||||
ctx = WithValue(ctx, v, v)
|
||||
vals = append(vals, v)
|
||||
case 1:
|
||||
var cancel CancelFunc
|
||||
ctx, cancel = WithCancel(ctx)
|
||||
cancels = append(cancels, cancel)
|
||||
case 2:
|
||||
var cancel CancelFunc
|
||||
d := veryLongDuration
|
||||
if testTimeout {
|
||||
d = shortDuration
|
||||
}
|
||||
ctx, cancel = WithTimeout(ctx, d)
|
||||
cancels = append(cancels, cancel)
|
||||
numTimers++
|
||||
}
|
||||
}
|
||||
checkValues := func(when string) {
|
||||
for _, key := range vals {
|
||||
if val := ctx.Value(key).(*value); key != val {
|
||||
errorf("%s: ctx.Value(%p) = %p want %p", when, key, val, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !testTimeout {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
errorf("ctx should not be canceled yet")
|
||||
default:
|
||||
}
|
||||
}
|
||||
if s, prefix := fmt.Sprint(ctx), "context.Background."; !strings.HasPrefix(s, prefix) {
|
||||
t.Errorf("ctx.String() = %q want prefix %q", s, prefix)
|
||||
}
|
||||
t.Log(ctx)
|
||||
checkValues("before cancel")
|
||||
if testTimeout {
|
||||
d := quiescent(t)
|
||||
timer := time.NewTimer(d)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-timer.C:
|
||||
errorf("ctx should have timed out after %v", d)
|
||||
}
|
||||
checkValues("after timeout")
|
||||
} else {
|
||||
cancel := cancels[r.Intn(len(cancels))]
|
||||
cancel()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
default:
|
||||
errorf("ctx should be canceled")
|
||||
}
|
||||
checkValues("after cancel")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithCancelCanceledParent(t *testing.T) {
|
||||
parent, pcancel := WithCancelCause(Background())
|
||||
cause := fmt.Errorf("Because!")
|
||||
pcancel(cause)
|
||||
|
||||
c, _ := WithCancel(parent)
|
||||
select {
|
||||
case <-c.Done():
|
||||
default:
|
||||
t.Errorf("child not done immediately upon construction")
|
||||
}
|
||||
if got, want := c.Err(), Canceled; got != want {
|
||||
t.Errorf("child not canceled; got = %v, want = %v", got, want)
|
||||
}
|
||||
if got, want := Cause(c), cause; got != want {
|
||||
t.Errorf("child has wrong cause; got = %v, want = %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithValueChecksKey(t *testing.T) {
|
||||
panicVal := recoveredValue(func() { WithValue(Background(), []byte("foo"), "bar") })
|
||||
if panicVal == nil {
|
||||
t.Error("expected panic")
|
||||
}
|
||||
panicVal = recoveredValue(func() { WithValue(Background(), nil, "bar") })
|
||||
if got, want := fmt.Sprint(panicVal), "nil key"; got != want {
|
||||
t.Errorf("panic = %q; want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidDerivedFail(t *testing.T) {
|
||||
panicVal := recoveredValue(func() { WithCancel(nil) })
|
||||
if panicVal == nil {
|
||||
t.Error("expected panic")
|
||||
}
|
||||
panicVal = recoveredValue(func() { WithDeadline(nil, time.Now().Add(shortDuration)) })
|
||||
if panicVal == nil {
|
||||
t.Error("expected panic")
|
||||
}
|
||||
panicVal = recoveredValue(func() { WithValue(nil, "foo", "bar") })
|
||||
if panicVal == nil {
|
||||
t.Error("expected panic")
|
||||
}
|
||||
}
|
||||
|
||||
func recoveredValue(fn func()) (v any) {
|
||||
defer func() { v = recover() }()
|
||||
fn()
|
||||
return
|
||||
}
|
||||
|
||||
func TestDeadlineExceededSupportsTimeout(t *testing.T) {
|
||||
i, ok := DeadlineExceeded.(interface {
|
||||
Timeout() bool
|
||||
})
|
||||
if !ok {
|
||||
t.Fatal("DeadlineExceeded does not support Timeout interface")
|
||||
}
|
||||
if !i.Timeout() {
|
||||
t.Fatal("wrong value for timeout")
|
||||
}
|
||||
}
|
||||
func TestCause(t *testing.T) {
|
||||
var (
|
||||
forever = 1e6 * time.Second
|
||||
parentCause = fmt.Errorf("parentCause")
|
||||
childCause = fmt.Errorf("childCause")
|
||||
tooSlow = fmt.Errorf("tooSlow")
|
||||
finishedEarly = fmt.Errorf("finishedEarly")
|
||||
)
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
ctx func() Context
|
||||
err error
|
||||
cause error
|
||||
}{
|
||||
{
|
||||
name: "Background",
|
||||
ctx: Background,
|
||||
err: nil,
|
||||
cause: nil,
|
||||
},
|
||||
{
|
||||
name: "TODO",
|
||||
ctx: TODO,
|
||||
err: nil,
|
||||
cause: nil,
|
||||
},
|
||||
{
|
||||
name: "WithCancel",
|
||||
ctx: func() Context {
|
||||
ctx, cancel := WithCancel(Background())
|
||||
cancel()
|
||||
return ctx
|
||||
},
|
||||
err: Canceled,
|
||||
cause: Canceled,
|
||||
},
|
||||
{
|
||||
name: "WithCancelCause",
|
||||
ctx: func() Context {
|
||||
ctx, cancel := WithCancelCause(Background())
|
||||
cancel(parentCause)
|
||||
return ctx
|
||||
},
|
||||
err: Canceled,
|
||||
cause: parentCause,
|
||||
},
|
||||
{
|
||||
name: "WithCancelCause nil",
|
||||
ctx: func() Context {
|
||||
ctx, cancel := WithCancelCause(Background())
|
||||
cancel(nil)
|
||||
return ctx
|
||||
},
|
||||
err: Canceled,
|
||||
cause: Canceled,
|
||||
},
|
||||
{
|
||||
name: "WithCancelCause: parent cause before child",
|
||||
ctx: func() Context {
|
||||
ctx, cancelParent := WithCancelCause(Background())
|
||||
ctx, cancelChild := WithCancelCause(ctx)
|
||||
cancelParent(parentCause)
|
||||
cancelChild(childCause)
|
||||
return ctx
|
||||
},
|
||||
err: Canceled,
|
||||
cause: parentCause,
|
||||
},
|
||||
{
|
||||
name: "WithCancelCause: parent cause after child",
|
||||
ctx: func() Context {
|
||||
ctx, cancelParent := WithCancelCause(Background())
|
||||
ctx, cancelChild := WithCancelCause(ctx)
|
||||
cancelChild(childCause)
|
||||
cancelParent(parentCause)
|
||||
return ctx
|
||||
},
|
||||
err: Canceled,
|
||||
cause: childCause,
|
||||
},
|
||||
{
|
||||
name: "WithCancelCause: parent cause before nil",
|
||||
ctx: func() Context {
|
||||
ctx, cancelParent := WithCancelCause(Background())
|
||||
ctx, cancelChild := WithCancel(ctx)
|
||||
cancelParent(parentCause)
|
||||
cancelChild()
|
||||
return ctx
|
||||
},
|
||||
err: Canceled,
|
||||
cause: parentCause,
|
||||
},
|
||||
{
|
||||
name: "WithCancelCause: parent cause after nil",
|
||||
ctx: func() Context {
|
||||
ctx, cancelParent := WithCancelCause(Background())
|
||||
ctx, cancelChild := WithCancel(ctx)
|
||||
cancelChild()
|
||||
cancelParent(parentCause)
|
||||
return ctx
|
||||
},
|
||||
err: Canceled,
|
||||
cause: Canceled,
|
||||
},
|
||||
{
|
||||
name: "WithCancelCause: child cause after nil",
|
||||
ctx: func() Context {
|
||||
ctx, cancelParent := WithCancel(Background())
|
||||
ctx, cancelChild := WithCancelCause(ctx)
|
||||
cancelParent()
|
||||
cancelChild(childCause)
|
||||
return ctx
|
||||
},
|
||||
err: Canceled,
|
||||
cause: Canceled,
|
||||
},
|
||||
{
|
||||
name: "WithCancelCause: child cause before nil",
|
||||
ctx: func() Context {
|
||||
ctx, cancelParent := WithCancel(Background())
|
||||
ctx, cancelChild := WithCancelCause(ctx)
|
||||
cancelChild(childCause)
|
||||
cancelParent()
|
||||
return ctx
|
||||
},
|
||||
err: Canceled,
|
||||
cause: childCause,
|
||||
},
|
||||
{
|
||||
name: "WithTimeout",
|
||||
ctx: func() Context {
|
||||
ctx, cancel := WithTimeout(Background(), 0)
|
||||
cancel()
|
||||
return ctx
|
||||
},
|
||||
err: DeadlineExceeded,
|
||||
cause: DeadlineExceeded,
|
||||
},
|
||||
{
|
||||
name: "WithTimeout canceled",
|
||||
ctx: func() Context {
|
||||
ctx, cancel := WithTimeout(Background(), forever)
|
||||
cancel()
|
||||
return ctx
|
||||
},
|
||||
err: Canceled,
|
||||
cause: Canceled,
|
||||
},
|
||||
{
|
||||
name: "WithTimeoutCause",
|
||||
ctx: func() Context {
|
||||
ctx, cancel := WithTimeoutCause(Background(), 0, tooSlow)
|
||||
cancel()
|
||||
return ctx
|
||||
},
|
||||
err: DeadlineExceeded,
|
||||
cause: tooSlow,
|
||||
},
|
||||
{
|
||||
name: "WithTimeoutCause canceled",
|
||||
ctx: func() Context {
|
||||
ctx, cancel := WithTimeoutCause(Background(), forever, tooSlow)
|
||||
cancel()
|
||||
return ctx
|
||||
},
|
||||
err: Canceled,
|
||||
cause: Canceled,
|
||||
},
|
||||
{
|
||||
name: "WithTimeoutCause stacked",
|
||||
ctx: func() Context {
|
||||
ctx, cancel := WithCancelCause(Background())
|
||||
ctx, _ = WithTimeoutCause(ctx, 0, tooSlow)
|
||||
cancel(finishedEarly)
|
||||
return ctx
|
||||
},
|
||||
err: DeadlineExceeded,
|
||||
cause: tooSlow,
|
||||
},
|
||||
{
|
||||
name: "WithTimeoutCause stacked canceled",
|
||||
ctx: func() Context {
|
||||
ctx, cancel := WithCancelCause(Background())
|
||||
ctx, _ = WithTimeoutCause(ctx, forever, tooSlow)
|
||||
cancel(finishedEarly)
|
||||
return ctx
|
||||
},
|
||||
err: Canceled,
|
||||
cause: finishedEarly,
|
||||
},
|
||||
{
|
||||
name: "WithoutCancel",
|
||||
ctx: func() Context {
|
||||
return WithoutCancel(Background())
|
||||
},
|
||||
err: nil,
|
||||
cause: nil,
|
||||
},
|
||||
{
|
||||
name: "WithoutCancel canceled",
|
||||
ctx: func() Context {
|
||||
ctx, cancel := WithCancelCause(Background())
|
||||
ctx = WithoutCancel(ctx)
|
||||
cancel(finishedEarly)
|
||||
return ctx
|
||||
},
|
||||
err: nil,
|
||||
cause: nil,
|
||||
},
|
||||
{
|
||||
name: "WithoutCancel timeout",
|
||||
ctx: func() Context {
|
||||
ctx, cancel := WithTimeoutCause(Background(), 0, tooSlow)
|
||||
ctx = WithoutCancel(ctx)
|
||||
cancel()
|
||||
return ctx
|
||||
},
|
||||
err: nil,
|
||||
cause: nil,
|
||||
},
|
||||
} {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := test.ctx()
|
||||
if got, want := ctx.Err(), test.err; want != got {
|
||||
t.Errorf("ctx.Err() = %v want %v", got, want)
|
||||
}
|
||||
if got, want := Cause(ctx), test.cause; want != got {
|
||||
t.Errorf("Cause(ctx) = %v want %v", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCauseRace(t *testing.T) {
|
||||
cause := errors.New("TestCauseRace")
|
||||
ctx, cancel := WithCancelCause(Background())
|
||||
go func() {
|
||||
cancel(cause)
|
||||
}()
|
||||
for {
|
||||
// Poll Cause, rather than waiting for Done, to test that
|
||||
// access to the underlying cause is synchronized properly.
|
||||
if err := Cause(ctx); err != nil {
|
||||
if err != cause {
|
||||
t.Errorf("Cause returned %v, want %v", err, cause)
|
||||
}
|
||||
break
|
||||
}
|
||||
runtime.Gosched()
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithoutCancel(t *testing.T) {
|
||||
key, value := "key", "value"
|
||||
ctx := WithValue(Background(), key, value)
|
||||
ctx = WithoutCancel(ctx)
|
||||
if d, ok := ctx.Deadline(); !d.IsZero() || ok != false {
|
||||
t.Errorf("ctx.Deadline() = %v, %v want zero, false", d, ok)
|
||||
}
|
||||
if done := ctx.Done(); done != nil {
|
||||
t.Errorf("ctx.Deadline() = %v want nil", done)
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
t.Errorf("ctx.Err() = %v want nil", err)
|
||||
}
|
||||
if v := ctx.Value(key); v != value {
|
||||
t.Errorf("ctx.Value(%q) = %q want %q", key, v, value)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user