Skip to content
Snippets Groups Projects
Commit f432b5f7 authored by Vladimir Varankin's avatar Vladimir Varankin Committed by Gopher Robot
Browse files

testing: add Context

Adds a new Context method to testing.T, that returns a context, that is
canceled before the end of its test function.

Fixes #36532.

Change-Id: I9315ad4dad25529d0b5be809e2d9db4e7528b5f2
GitHub-Last-Rev: 1c3fd6c4d8a9cc68a61f2df284d04d3d080216be
GitHub-Pull-Request: golang/go#68828
Reviewed-on: https://go-review.googlesource.com/c/go/+/603959


Auto-Submit: Alan Donovan <adonovan@google.com>
Reviewed-by: default avatarAlan Donovan <adonovan@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: default avatarDamien Neil <dneil@google.com>
parent c6c96345
No related branches found
No related tags found
No related merge requests found
pkg testing, method (*B) Context() context.Context #36532
pkg testing, method (*F) Context() context.Context #36532
pkg testing, method (*T) Context() context.Context #36532
pkg testing, type TB interface, Context() context.Context #36532
The new [T.Context] and [B.Context] methods return a context that's canceled
before the end of its associated test or benchmark function.
The new [T.Chdir] and [B.Chdir] methods can be used to change the working The new [T.Context] and [B.Context] methods return a context that is canceled
directory for the duration of a test or benchmark. after the test completes and before test cleanup functions run.
...@@ -371,6 +371,7 @@ package testing ...@@ -371,6 +371,7 @@ package testing
import ( import (
"bytes" "bytes"
"context"
"errors" "errors"
"flag" "flag"
"fmt" "fmt"
...@@ -633,6 +634,9 @@ type common struct { ...@@ -633,6 +634,9 @@ type common struct {
tempDir string tempDir string
tempDirErr error tempDirErr error
tempDirSeq int32 tempDirSeq int32
ctx context.Context
cancelCtx context.CancelFunc
} }
// Short reports whether the -test.short flag is set. // Short reports whether the -test.short flag is set.
...@@ -898,6 +902,7 @@ type TB interface { ...@@ -898,6 +902,7 @@ type TB interface {
Skipf(format string, args ...any) Skipf(format string, args ...any)
Skipped() bool Skipped() bool
TempDir() string TempDir() string
Context() context.Context
// A private method to prevent users implementing the // A private method to prevent users implementing the
// interface and so future additions to it will not // interface and so future additions to it will not
...@@ -1351,6 +1356,16 @@ func (c *common) Chdir(dir string) { ...@@ -1351,6 +1356,16 @@ func (c *common) Chdir(dir string) {
}) })
} }
// Context returns a context that is canceled just before
// [T.Cleanup]-registered functions are called.
//
// Cleanup functions can wait for any resources
// that shut down on Context.Done before the test completes.
func (c *common) Context() context.Context {
c.checkFuzzFn("Context")
return c.ctx
}
// panicHandling controls the panic handling used by runCleanup. // panicHandling controls the panic handling used by runCleanup.
type panicHandling int type panicHandling int
...@@ -1383,6 +1398,10 @@ func (c *common) runCleanup(ph panicHandling) (panicVal any) { ...@@ -1383,6 +1398,10 @@ func (c *common) runCleanup(ph panicHandling) (panicVal any) {
} }
}() }()
if c.cancelCtx != nil {
c.cancelCtx()
}
for { for {
var cleanup func() var cleanup func()
c.mu.Lock() c.mu.Lock()
...@@ -1771,15 +1790,21 @@ func (t *T) Run(name string, f func(t *T)) bool { ...@@ -1771,15 +1790,21 @@ func (t *T) Run(name string, f func(t *T)) bool {
// continue walking the stack into the parent test. // continue walking the stack into the parent test.
var pc [maxStackLen]uintptr var pc [maxStackLen]uintptr
n := runtime.Callers(2, pc[:]) n := runtime.Callers(2, pc[:])
// There's no reason to inherit this context from parent. The user's code can't observe
// the difference between the background context and the one from the parent test.
ctx, cancelCtx := context.WithCancel(context.Background())
t = &T{ t = &T{
common: common{ common: common{
barrier: make(chan bool), barrier: make(chan bool),
signal: make(chan bool, 1), signal: make(chan bool, 1),
name: testName, name: testName,
parent: &t.common, parent: &t.common,
level: t.level + 1, level: t.level + 1,
creator: pc[:n], creator: pc[:n],
chatty: t.chatty, chatty: t.chatty,
ctx: ctx,
cancelCtx: cancelCtx,
}, },
context: t.context, context: t.context,
} }
...@@ -2205,15 +2230,18 @@ func runTests(matchString func(pat, str string) (bool, error), tests []InternalT ...@@ -2205,15 +2230,18 @@ func runTests(matchString func(pat, str string) (bool, error), tests []InternalT
// to keep trying. // to keep trying.
break break
} }
ctx := newTestContext(*parallel, newMatcher(matchString, *match, "-test.run", *skip)) ctx, cancelCtx := context.WithCancel(context.Background())
ctx.deadline = deadline tctx := newTestContext(*parallel, newMatcher(matchString, *match, "-test.run", *skip))
tctx.deadline = deadline
t := &T{ t := &T{
common: common{ common: common{
signal: make(chan bool, 1), signal: make(chan bool, 1),
barrier: make(chan bool), barrier: make(chan bool),
w: os.Stdout, w: os.Stdout,
ctx: ctx,
cancelCtx: cancelCtx,
}, },
context: ctx, context: tctx,
} }
if Verbose() { if Verbose() {
t.chatty = newChattyPrinter(t.w) t.chatty = newChattyPrinter(t.w)
......
...@@ -6,6 +6,8 @@ package testing_test ...@@ -6,6 +6,8 @@ package testing_test
import ( import (
"bytes" "bytes"
"context"
"errors"
"fmt" "fmt"
"internal/race" "internal/race"
"internal/testenv" "internal/testenv"
...@@ -918,3 +920,29 @@ func TestParentRun(t1 *testing.T) { ...@@ -918,3 +920,29 @@ func TestParentRun(t1 *testing.T) {
}) })
}) })
} }
func TestContext(t *testing.T) {
ctx := t.Context()
if err := ctx.Err(); err != nil {
t.Fatalf("expected non-canceled context, got %v", err)
}
var innerCtx context.Context
t.Run("inner", func(t *testing.T) {
innerCtx = t.Context()
if err := innerCtx.Err(); err != nil {
t.Fatalf("expected inner test to not inherit canceled context, got %v", err)
}
})
t.Run("inner2", func(t *testing.T) {
if !errors.Is(innerCtx.Err(), context.Canceled) {
t.Fatal("expected context of sibling test to be canceled after its test function finished")
}
})
t.Cleanup(func() {
if !errors.Is(ctx.Err(), context.Canceled) {
t.Fatal("expected context canceled before cleanup")
}
})
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment