"runtime/trace": {"L0"},
"text/tabwriter": {"L2"},
- "testing": {"L2", "flag", "fmt", "internal/race", "os", "runtime/debug", "runtime/pprof", "runtime/trace", "time"},
+ "testing": {"L2", "context", "flag", "fmt", "internal/race", "os", "runtime/debug", "runtime/pprof", "runtime/trace", "time"},
"testing/iotest": {"L2", "log"},
"testing/quick": {"L2", "flag", "fmt", "reflect"},
"internal/testenv": {"L2", "OS", "flag", "testing", "syscall"},
package testing
import (
+ "context"
"flag"
"fmt"
"internal/race"
// runN runs a single benchmark for the specified number of iterations.
func (b *B) runN(n int) {
+ b.ctx, b.cancel = context.WithCancel(b.parentContext())
+ defer b.cancel()
+
benchmarkLock.Lock()
defer benchmarkLock.Unlock()
// Try to get a comparable environment for each run
import (
"bytes"
+ "context"
"regexp"
"strings"
"sync/atomic"
ok: true,
maxPar: 4,
f: func(t *T) {
- t.Parallel()
- for i := 0; i < 12; i++ {
- t.Run("a", func(t *T) {
- t.Parallel()
- time.Sleep(time.Nanosecond)
- for i := 0; i < 12; i++ {
- t.Run("b", func(t *T) {
- time.Sleep(time.Nanosecond)
- for i := 0; i < 12; i++ {
- t.Run("c", func(t *T) {
- t.Parallel()
- time.Sleep(time.Nanosecond)
- t.Run("d1", func(t *T) {})
- t.Run("d2", func(t *T) {})
- t.Run("d3", func(t *T) {})
- t.Run("d4", func(t *T) {})
- })
- }
- })
- }
- })
- }
+ // t.Parallel doesn't work in the pseudo-T we start with:
+ // it leaks a goroutine.
+ // Call t.Run to get a real one.
+ t.Run("X", func(t *T) {
+ t.Parallel()
+ for i := 0; i < 12; i++ {
+ t.Run("a", func(t *T) {
+ t.Parallel()
+ time.Sleep(time.Nanosecond)
+ for i := 0; i < 12; i++ {
+ t.Run("b", func(t *T) {
+ time.Sleep(time.Nanosecond)
+ for i := 0; i < 12; i++ {
+ t.Run("c", func(t *T) {
+ t.Parallel()
+ time.Sleep(time.Nanosecond)
+ t.Run("d1", func(t *T) {})
+ t.Run("d2", func(t *T) {})
+ t.Run("d3", func(t *T) {})
+ t.Run("d4", func(t *T) {})
+ })
+ }
+ })
+ }
+ })
+ }
+ })
},
}, {
desc: "skip output",
},
context: ctx,
}
+ root.ctx, root.cancel = context.WithCancel(context.Background())
ok := root.Run(tc.desc, tc.f)
ctx.release()
import (
"bytes"
+ "context"
"errors"
"flag"
"fmt"
mu sync.RWMutex // guards output, failed, and done.
output []byte // Output generated by test or benchmark.
w io.Writer // For flushToParent.
- chatty bool // A copy of the chatty flag.
- ran bool // Test or benchmark (or one of its subtests) was executed.
- failed bool // Test or benchmark has failed.
- skipped bool // Test of benchmark has been skipped.
- finished bool // Test function has completed.
- done bool // Test is finished and all subtests have completed.
+ ctx context.Context
+ cancel context.CancelFunc
+ chatty bool // A copy of the chatty flag.
+ ran bool // Test or benchmark (or one of its subtests) was executed.
+ failed bool // Test or benchmark has failed.
+ skipped bool // Test of benchmark has been skipped.
+ finished bool // Test function has completed.
+ done bool // Test is finished and all subtests have completed.
hasSub bool
raceErrors int // number of races detected during test
sub []*T // Queue of subtests to be run in parallel.
}
+func (c *common) parentContext() context.Context {
+ if c == nil || c.parent == nil || c.parent.ctx == nil {
+ return context.Background()
+ }
+ return c.parent.ctx
+}
+
// Short reports whether the -test.short flag is set.
func Short() bool {
return *short
// TB is the interface common to T and B.
type TB interface {
+ Context() context.Context
Error(args ...interface{})
Errorf(format string, args ...interface{})
Fail()
return c.name
}
+// Context returns the context for the current test or benchmark.
+// The context is cancelled when the test or benchmark finishes.
+// A goroutine started during a test or benchmark can wait for the
+// context's Done channel to become readable as a signal that the
+// test or benchmark is over, so that the goroutine can exit.
+func (c *common) Context() context.Context {
+ return c.ctx
+}
+
func (c *common) setRan() {
if c.parent != nil {
c.parent.setRan()
}
func tRunner(t *T, fn func(t *T)) {
+ t.ctx, t.cancel = context.WithCancel(t.parentContext())
+ defer t.cancel()
+
// When this goroutine is done, either because fn(t)
// returned normally or because a test failure triggered
// a call to runtime.Goexit, record the duration and send
package testing_test
import (
+ "fmt"
"os"
+ "runtime"
"testing"
+ "time"
)
-// This is exactly what a test would do without a TestMain.
-// It's here only so that there is at least one package in the
-// standard library with a TestMain, so that code is executed.
-
func TestMain(m *testing.M) {
- os.Exit(m.Run())
+ g0 := runtime.NumGoroutine()
+
+ code := m.Run()
+ if code != 0 {
+ os.Exit(code)
+ }
+
+ // Check that there are no goroutines left behind.
+ t0 := time.Now()
+ stacks := make([]byte, 1<<20)
+ for {
+ g1 := runtime.NumGoroutine()
+ if g1 == g0 {
+ return
+ }
+ stacks = stacks[:runtime.Stack(stacks, true)]
+ time.Sleep(50 * time.Millisecond)
+ if time.Since(t0) > 2*time.Second {
+ fmt.Fprintf(os.Stderr, "Unexpected leftover goroutines detected: %v -> %v\n%s\n", g0, g1, stacks)
+ os.Exit(1)
+ }
+ }
+}
+
+func TestContextCancel(t *testing.T) {
+ ctx := t.Context()
+ // Tests we don't leak this goroutine:
+ go func() {
+ <-ctx.Done()
+ }()
}