mirror of https://go.googlesource.com/go
298 lines
7.7 KiB
Go
298 lines
7.7 KiB
Go
// Copyright 2014 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package 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 (
|
|
"time"
|
|
)
|
|
|
|
type testingT interface {
|
|
Deadline() (time.Time, bool)
|
|
Error(args ...any)
|
|
Errorf(format string, args ...any)
|
|
Fail()
|
|
FailNow()
|
|
Failed() bool
|
|
Fatal(args ...any)
|
|
Fatalf(format string, args ...any)
|
|
Helper()
|
|
Log(args ...any)
|
|
Logf(format string, args ...any)
|
|
Name() string
|
|
Parallel()
|
|
Skip(args ...any)
|
|
SkipNow()
|
|
Skipf(format string, args ...any)
|
|
Skipped() bool
|
|
}
|
|
|
|
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]
|
|
return ret
|
|
}
|
|
|
|
func XTestParentFinishesChild(t testingT) {
|
|
// Context tree:
|
|
// parent -> cancelChild
|
|
// parent -> valueChild -> timerChild
|
|
// parent -> afterChild
|
|
parent, cancel := WithCancel(Background())
|
|
cancelChild, stop := WithCancel(parent)
|
|
defer stop()
|
|
valueChild := WithValue(parent, "key", "value")
|
|
timerChild, stop := WithTimeout(valueChild, veryLongDuration)
|
|
defer stop()
|
|
afterStop := AfterFunc(parent, func() {})
|
|
defer afterStop()
|
|
|
|
select {
|
|
case x := <-parent.Done():
|
|
t.Errorf("<-parent.Done() == %v want nothing (it should block)", x)
|
|
case x := <-cancelChild.Done():
|
|
t.Errorf("<-cancelChild.Done() == %v want nothing (it should block)", x)
|
|
case x := <-timerChild.Done():
|
|
t.Errorf("<-timerChild.Done() == %v want nothing (it should block)", x)
|
|
case x := <-valueChild.Done():
|
|
t.Errorf("<-valueChild.Done() == %v want nothing (it should block)", x)
|
|
default:
|
|
}
|
|
|
|
// The parent's children should contain the three cancelable children.
|
|
pc := parent.(*cancelCtx)
|
|
cc := cancelChild.(*cancelCtx)
|
|
tc := timerChild.(*timerCtx)
|
|
pc.mu.Lock()
|
|
var ac *afterFuncCtx
|
|
for c := range pc.children {
|
|
if a, ok := c.(*afterFuncCtx); ok {
|
|
ac = a
|
|
break
|
|
}
|
|
}
|
|
if len(pc.children) != 3 || !contains(pc.children, cc) || !contains(pc.children, tc) || ac == nil {
|
|
t.Errorf("bad linkage: pc.children = %v, want %v, %v, and an afterFunc",
|
|
pc.children, cc, tc)
|
|
}
|
|
pc.mu.Unlock()
|
|
|
|
if p, ok := parentCancelCtx(cc.Context); !ok || p != pc {
|
|
t.Errorf("bad linkage: parentCancelCtx(cancelChild.Context) = %v, %v want %v, true", p, ok, pc)
|
|
}
|
|
if p, ok := parentCancelCtx(tc.Context); !ok || p != pc {
|
|
t.Errorf("bad linkage: parentCancelCtx(timerChild.Context) = %v, %v want %v, true", p, ok, pc)
|
|
}
|
|
if p, ok := parentCancelCtx(ac.Context); !ok || p != pc {
|
|
t.Errorf("bad linkage: parentCancelCtx(afterChild.Context) = %v, %v want %v, true", p, ok, pc)
|
|
}
|
|
|
|
cancel()
|
|
|
|
pc.mu.Lock()
|
|
if len(pc.children) != 0 {
|
|
t.Errorf("pc.cancel didn't clear pc.children = %v", pc.children)
|
|
}
|
|
pc.mu.Unlock()
|
|
|
|
// parent and children should all be finished.
|
|
check := func(ctx Context, name string) {
|
|
select {
|
|
case <-ctx.Done():
|
|
default:
|
|
t.Errorf("<-%s.Done() blocked, but shouldn't have", name)
|
|
}
|
|
if e := ctx.Err(); e != Canceled {
|
|
t.Errorf("%s.Err() == %v want %v", name, e, Canceled)
|
|
}
|
|
}
|
|
check(parent, "parent")
|
|
check(cancelChild, "cancelChild")
|
|
check(valueChild, "valueChild")
|
|
check(timerChild, "timerChild")
|
|
|
|
// WithCancel should return a canceled context on a canceled parent.
|
|
precanceledChild := WithValue(parent, "key", "value")
|
|
select {
|
|
case <-precanceledChild.Done():
|
|
default:
|
|
t.Errorf("<-precanceledChild.Done() blocked, but shouldn't have")
|
|
}
|
|
if e := precanceledChild.Err(); e != Canceled {
|
|
t.Errorf("precanceledChild.Err() == %v want %v", e, Canceled)
|
|
}
|
|
}
|
|
|
|
func XTestChildFinishesFirst(t testingT) {
|
|
cancelable, stop := WithCancel(Background())
|
|
defer stop()
|
|
for _, parent := range []Context{Background(), cancelable} {
|
|
child, cancel := WithCancel(parent)
|
|
|
|
select {
|
|
case x := <-parent.Done():
|
|
t.Errorf("<-parent.Done() == %v want nothing (it should block)", x)
|
|
case x := <-child.Done():
|
|
t.Errorf("<-child.Done() == %v want nothing (it should block)", x)
|
|
default:
|
|
}
|
|
|
|
cc := child.(*cancelCtx)
|
|
pc, pcok := parent.(*cancelCtx) // pcok == false when parent == Background()
|
|
if p, ok := parentCancelCtx(cc.Context); ok != pcok || (ok && pc != p) {
|
|
t.Errorf("bad linkage: parentCancelCtx(cc.Context) = %v, %v want %v, %v", p, ok, pc, pcok)
|
|
}
|
|
|
|
if pcok {
|
|
pc.mu.Lock()
|
|
if len(pc.children) != 1 || !contains(pc.children, cc) {
|
|
t.Errorf("bad linkage: pc.children = %v, cc = %v", pc.children, cc)
|
|
}
|
|
pc.mu.Unlock()
|
|
}
|
|
|
|
cancel()
|
|
|
|
if pcok {
|
|
pc.mu.Lock()
|
|
if len(pc.children) != 0 {
|
|
t.Errorf("child's cancel didn't remove self from pc.children = %v", pc.children)
|
|
}
|
|
pc.mu.Unlock()
|
|
}
|
|
|
|
// child should be finished.
|
|
select {
|
|
case <-child.Done():
|
|
default:
|
|
t.Errorf("<-child.Done() blocked, but shouldn't have")
|
|
}
|
|
if e := child.Err(); e != Canceled {
|
|
t.Errorf("child.Err() == %v want %v", e, Canceled)
|
|
}
|
|
|
|
// parent should not be finished.
|
|
select {
|
|
case x := <-parent.Done():
|
|
t.Errorf("<-parent.Done() == %v want nothing (it should block)", x)
|
|
default:
|
|
}
|
|
if e := parent.Err(); e != nil {
|
|
t.Errorf("parent.Err() == %v want nil", e)
|
|
}
|
|
}
|
|
}
|
|
|
|
func XTestCancelRemoves(t testingT) {
|
|
checkChildren := func(when string, ctx Context, want int) {
|
|
if got := len(ctx.(*cancelCtx).children); got != want {
|
|
t.Errorf("%s: context has %d children, want %d", when, got, want)
|
|
}
|
|
}
|
|
|
|
ctx, _ := WithCancel(Background())
|
|
checkChildren("after creation", ctx, 0)
|
|
_, cancel := WithCancel(ctx)
|
|
checkChildren("with WithCancel child ", ctx, 1)
|
|
cancel()
|
|
checkChildren("after canceling WithCancel child", ctx, 0)
|
|
|
|
ctx, _ = WithCancel(Background())
|
|
checkChildren("after creation", ctx, 0)
|
|
_, cancel = WithTimeout(ctx, 60*time.Minute)
|
|
checkChildren("with WithTimeout child ", ctx, 1)
|
|
cancel()
|
|
checkChildren("after canceling WithTimeout child", ctx, 0)
|
|
|
|
ctx, _ = WithCancel(Background())
|
|
checkChildren("after creation", ctx, 0)
|
|
stop := AfterFunc(ctx, func() {})
|
|
checkChildren("with AfterFunc child ", ctx, 1)
|
|
stop()
|
|
checkChildren("after stopping AfterFunc child ", ctx, 0)
|
|
}
|
|
|
|
type myCtx struct {
|
|
Context
|
|
}
|
|
|
|
type myDoneCtx struct {
|
|
Context
|
|
}
|
|
|
|
func (d *myDoneCtx) Done() <-chan struct{} {
|
|
c := make(chan struct{})
|
|
return c
|
|
}
|
|
func XTestCustomContextGoroutines(t testingT) {
|
|
g := goroutines.Load()
|
|
checkNoGoroutine := func() {
|
|
t.Helper()
|
|
now := goroutines.Load()
|
|
if now != g {
|
|
t.Fatalf("%d goroutines created", now-g)
|
|
}
|
|
}
|
|
checkCreatedGoroutine := func() {
|
|
t.Helper()
|
|
now := goroutines.Load()
|
|
if now != g+1 {
|
|
t.Fatalf("%d goroutines created, want 1", now-g)
|
|
}
|
|
g = now
|
|
}
|
|
|
|
_, cancel0 := WithCancel(&myDoneCtx{Background()})
|
|
cancel0()
|
|
checkCreatedGoroutine()
|
|
|
|
_, cancel0 = WithTimeout(&myDoneCtx{Background()}, veryLongDuration)
|
|
cancel0()
|
|
checkCreatedGoroutine()
|
|
|
|
checkNoGoroutine()
|
|
defer checkNoGoroutine()
|
|
|
|
ctx1, cancel1 := WithCancel(Background())
|
|
defer cancel1()
|
|
checkNoGoroutine()
|
|
|
|
ctx2 := &myCtx{ctx1}
|
|
ctx3, cancel3 := WithCancel(ctx2)
|
|
defer cancel3()
|
|
checkNoGoroutine()
|
|
|
|
_, cancel3b := WithCancel(&myDoneCtx{ctx2})
|
|
defer cancel3b()
|
|
checkCreatedGoroutine() // ctx1 is not providing Done, must not be used
|
|
|
|
ctx4, cancel4 := WithTimeout(ctx3, veryLongDuration)
|
|
defer cancel4()
|
|
checkNoGoroutine()
|
|
|
|
ctx5, cancel5 := WithCancel(ctx4)
|
|
defer cancel5()
|
|
checkNoGoroutine()
|
|
|
|
cancel5()
|
|
checkNoGoroutine()
|
|
|
|
_, cancel6 := WithTimeout(ctx5, veryLongDuration)
|
|
defer cancel6()
|
|
checkNoGoroutine()
|
|
|
|
// Check applied to canceled context.
|
|
cancel6()
|
|
cancel1()
|
|
_, cancel7 := WithCancel(ctx5)
|
|
defer cancel7()
|
|
checkNoGoroutine()
|
|
}
|