From a1f3609ec9087c490cd03f4b6a8e825095ae66d7 Mon Sep 17 00:00:00 2001 From: Sameer Ajmani Date: Tue, 29 Jul 2014 14:21:02 -0400 Subject: [PATCH] go.net/context: define an emptyCtx type for Background and TODO. This is the first step in splitting the various context implementations into smaller types. Add a test that creates a large, random stack of Contexts and tests that they work properly. LGTM=crawshaw R=crawshaw CC=golang-codereviews https://golang.org/cl/115350044 --- context/context.go | 36 ++++++++++++++-- context/context_test.go | 95 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+), 3 deletions(-) diff --git a/context/context.go b/context/context.go index b3d2adbd..49239f70 100644 --- a/context/context.go +++ b/context/context.go @@ -183,8 +183,38 @@ func (c *ctx) Value(key interface{}) interface{} { return nil } -// The background context for this process. -var background = newCtx(nil, neverCanceled) +type emptyCtx int + +func (emptyCtx) Deadline() (deadline time.Time, ok bool) { + return +} + +func (emptyCtx) Done() <-chan struct{} { + return nil +} + +func (emptyCtx) Err() error { + return nil +} + +func (emptyCtx) Value(key interface{}) interface{} { + return nil +} + +func (n emptyCtx) String() string { + switch n { + case background: + return "context.Background" + case todo: + return "context.TODO" + } + return "unknown empty Context" +} + +const ( + background emptyCtx = 1 + todo emptyCtx = 2 +) // Background returns a non-nil, empty Context. It is never canceled, has no // values, and has no deadline. It is typically used by the main function, @@ -200,7 +230,7 @@ func Background() Context { // parameter). TODO is recognized by static analysis tools that determine // whether Contexts are propagated correctly in a program. func TODO() Context { - return Background() + return todo } // A CancelFunc tells an operation to abandon its work. diff --git a/context/context_test.go b/context/context_test.go index 518b9a97..b97c25ce 100644 --- a/context/context_test.go +++ b/context/context_test.go @@ -6,6 +6,7 @@ package context import ( "fmt" + "math/rand" "runtime" "sync" "testing" @@ -28,6 +29,24 @@ func TestBackground(t *testing.T) { t.Errorf("<-c.Done() == %v want nothing (it should block)", x) default: } + if s := fmt.Sprint(c); s != "context.Background" { + t.Errorf(`Background.String = %q want "context.Background"`, s) + } +} + +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 s := fmt.Sprint(c); s != "context.TODO" { + t.Errorf(`TODO.String = %q want "context.TODO"`, s) + } } func TestWithCancel(t *testing.T) { @@ -429,3 +448,79 @@ func TestInterlockedCancels(t *testing.T) { t.Fatalf("timed out waiting for child.Done(); stacks:\n%s", 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) { + rand.Seed(seed) + errorf := func(format string, a ...interface{}) { + t.Errorf(fmt.Sprintf("seed=%d: %s", seed, format), a...) + } + const ( + timeout = 200 * time.Millisecond + 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 rand.Intn(3) { + case 0: + v := new(value) + t.Logf("WithValue(%p, %p)", v, v) + ctx = WithValue(ctx, v, v) + vals = append(vals, v) + case 1: + var cancel CancelFunc + t.Logf("WithCancel") + ctx, cancel = WithCancel(ctx) + cancels = append(cancels, cancel) + case 2: + var cancel CancelFunc + t.Logf("WithTimeout") + ctx, cancel = WithTimeout(ctx, timeout) + 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) + } + } + } + select { + case <-ctx.Done(): + errorf("ctx should not be canceled yet") + default: + } + checkValues("before cancel") + if testTimeout { + select { + case <-ctx.Done(): + case <-time.After(timeout + timeout/10): + errorf("ctx should have timed out") + } + checkValues("after timeout") + } else { + cancel := cancels[rand.Intn(len(cancels))] + cancel() + select { + case <-ctx.Done(): + default: + errorf("ctx should be canceled") + } + checkValues("after cancel") + } +}