aboutsummaryrefslogtreecommitdiff
path: root/src/testing/testing.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/testing/testing.go')
-rw-r--r--src/testing/testing.go124
1 files changed, 101 insertions, 23 deletions
diff --git a/src/testing/testing.go b/src/testing/testing.go
index da26dec6fb..1877ce3ba4 100644
--- a/src/testing/testing.go
+++ b/src/testing/testing.go
@@ -190,7 +190,7 @@
// }
// }
//
-// The race detector kills the program if it exceeds 8192 concurrent goroutines,
+// The race detector kills the program if it exceeds 8128 concurrent goroutines,
// so use care when running parallel tests with the -race flag set.
//
// Run does not return until parallel subtests have completed, providing a way
@@ -208,14 +208,14 @@
//
// Main
//
-// It is sometimes necessary for a test program to do extra setup or teardown
-// before or after testing. It is also sometimes necessary for a test to control
+// It is sometimes necessary for a test or benchmark program to do extra setup or teardown
+// before or after it executes. It is also sometimes necessary to control
// which code runs on the main thread. To support these and other cases,
// if a test file contains a function:
//
// func TestMain(m *testing.M)
//
-// then the generated test will call TestMain(m) instead of running the tests
+// then the generated test will call TestMain(m) instead of running the tests or benchmarks
// directly. TestMain runs in the main goroutine and can do whatever setup
// and teardown is necessary around a call to m.Run. m.Run will return an exit
// code that may be passed to os.Exit. If TestMain returns, the test wrapper
@@ -242,6 +242,7 @@ import (
"fmt"
"internal/race"
"io"
+ "math/rand"
"os"
"reflect"
"runtime"
@@ -300,6 +301,7 @@ func Init() {
cpuListStr = flag.String("test.cpu", "", "comma-separated `list` of cpu counts to run each test with")
parallel = flag.Int("test.parallel", runtime.GOMAXPROCS(0), "run at most `n` tests in parallel")
testlog = flag.String("test.testlogfile", "", "write test action log to `file` (for use only by cmd/go)")
+ shuffle = flag.String("test.shuffle", "off", "randomize the execution order of tests and benchmarks")
initBenchmarkFlags()
initFuzzFlags()
@@ -327,6 +329,7 @@ var (
timeout *time.Duration
cpuListStr *string
parallel *int
+ shuffle *string
testlog *string
haveExamples bool // are there examples?
@@ -390,17 +393,17 @@ type common struct {
w io.Writer // For flushToParent.
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.
+ skipped bool // Test or benchmark has been skipped.
done bool // Test is finished and all subtests have completed.
helperPCs map[uintptr]struct{} // functions to be skipped when writing file/line info
helperNames map[string]struct{} // helperPCs converted to function names
cleanups []func() // optional functions to be called at the end of the test
cleanupName string // Name of the cleanup function.
cleanupPc []uintptr // The stack trace at the point where Cleanup was called.
+ finished bool // Test function has completed.
chatty *chattyPrinter // A copy of chattyPrinter, if the chatty flag is set.
bench bool // Whether the current test is a benchmark.
- finished bool // Test function has completed.
hasSub int32 // Written atomically.
raceErrors int // Number of races detected during test.
runner string // Function name of tRunner running the test.
@@ -511,6 +514,13 @@ func (c *common) frameSkip(skip int) runtime.Frame {
}
return prevFrame
}
+ // If more helper PCs have been added since we last did the conversion
+ if c.helperNames == nil {
+ c.helperNames = make(map[string]struct{})
+ for pc := range c.helperPCs {
+ c.helperNames[pcToName(pc)] = struct{}{}
+ }
+ }
if _, ok := c.helperNames[frame.Function]; !ok {
// Found a frame that wasn't inside a helper function.
return frame
@@ -670,6 +680,7 @@ var _ TB = (*B)(nil)
type T struct {
common
isParallel bool
+ isEnvSet bool
context *testContext // For running tests and subtests.
}
@@ -741,7 +752,9 @@ func (c *common) FailNow() {
// it would run on a test failure. Because we send on c.signal during
// a top-of-stack deferred function now, we know that the send
// only happens after any other stacked defers have completed.
+ c.mu.Lock()
c.finished = true
+ c.mu.Unlock()
runtime.Goexit()
}
@@ -765,7 +778,7 @@ func (c *common) logDepth(s string, depth int) {
return
}
}
- panic("Log in goroutine after " + c.name + " has completed")
+ panic("Log in goroutine after " + c.name + " has completed: " + s)
} else {
if c.chatty != nil {
if c.bench {
@@ -840,15 +853,11 @@ func (c *common) Skipf(format string, args ...interface{}) {
// other goroutines created during the test. Calling SkipNow does not stop
// those other goroutines.
func (c *common) SkipNow() {
- c.skip()
- c.finished = true
- runtime.Goexit()
-}
-
-func (c *common) skip() {
c.mu.Lock()
- defer c.mu.Unlock()
c.skipped = true
+ c.finished = true
+ c.mu.Unlock()
+ runtime.Goexit()
}
// Skipped reports whether the test was skipped.
@@ -879,7 +888,7 @@ func (c *common) Helper() {
}
}
-// Cleanup registers a function to be called when the test and all its
+// Cleanup registers a function to be called when the test (or subtest) and all its
// subtests complete. Cleanup functions will be called in last added,
// first called order.
func (c *common) Cleanup(f func()) {
@@ -967,6 +976,29 @@ func (c *common) TempDir() string {
return dir
}
+// Setenv calls os.Setenv(key, value) and uses Cleanup to
+// restore the environment variable to its original value
+// after the test.
+//
+// This cannot be used in parallel tests.
+func (c *common) Setenv(key, value string) {
+ prevValue, ok := os.LookupEnv(key)
+
+ if err := os.Setenv(key, value); err != nil {
+ c.Fatalf("cannot set environment variable: %v", err)
+ }
+
+ if ok {
+ c.Cleanup(func() {
+ os.Setenv(key, prevValue)
+ })
+ } else {
+ c.Cleanup(func() {
+ os.Unsetenv(key)
+ })
+ }
+}
+
// panicHanding is an argument to runCleanup.
type panicHandling int
@@ -1038,6 +1070,9 @@ func (t *T) Parallel() {
if t.isParallel {
panic("testing: t.Parallel called multiple times")
}
+ if t.isEnvSet {
+ panic("testing: t.Parallel called after t.Setenv; cannot set environment variables in parallel tests")
+ }
t.isParallel = true
if t.parent.barrier == nil {
// T.Parallel has no effect when fuzzing.
@@ -1077,6 +1112,21 @@ func (t *T) Parallel() {
t.raceErrors += -race.Errors()
}
+// Setenv calls os.Setenv(key, value) and uses Cleanup to
+// restore the environment variable to its original value
+// after the test.
+//
+// This cannot be used in parallel tests.
+func (t *T) Setenv(key, value string) {
+ if t.isParallel {
+ panic("testing: t.Setenv called after t.Parallel; cannot set environment variables in parallel tests")
+ }
+
+ t.isEnvSet = true
+
+ t.common.Setenv(key, value)
+}
+
// InternalTest is an internal type but exported because it is cross-package;
// it is part of the implementation of the "go test" command.
type InternalTest struct {
@@ -1106,10 +1156,16 @@ func tRunner(t *T, fn func(t *T)) {
err := recover()
signal := true
- if !t.finished && err == nil {
+ t.mu.RLock()
+ finished := t.finished
+ t.mu.RUnlock()
+ if !finished && err == nil {
err = errNilPanicOrGoexit
for p := t.parent; p != nil; p = p.parent {
- if p.finished {
+ p.mu.RLock()
+ finished = p.finished
+ p.mu.RUnlock()
+ if finished {
t.Errorf("%v: subtest may have called FailNow on a parent test", err)
err = nil
signal = false
@@ -1203,7 +1259,9 @@ func tRunner(t *T, fn func(t *T)) {
fn(t)
// code beyond here will not be executed when FailNow is invoked
+ t.mu.Lock()
t.finished = true
+ t.mu.Unlock()
}
// Run runs f as a subtest of t called name. It runs f in a separate goroutine
@@ -1226,7 +1284,7 @@ func (t *T) Run(name string, f func(t *T)) bool {
t = &T{
common: common{
barrier: make(chan bool),
- signal: make(chan bool),
+ signal: make(chan bool, 1),
name: testName,
parent: &t.common,
level: t.level + 1,
@@ -1436,6 +1494,25 @@ func (m *M) Run() (code int) {
return
}
+ if *shuffle != "off" {
+ var n int64
+ var err error
+ if *shuffle == "on" {
+ n = time.Now().UnixNano()
+ } else {
+ n, err = strconv.ParseInt(*shuffle, 10, 64)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, `testing: -shuffle should be "off", "on", or a valid integer:`, err)
+ m.exitCode = 2
+ return
+ }
+ }
+ fmt.Println("-test.shuffle", n)
+ rng := rand.New(rand.NewSource(n))
+ rng.Shuffle(len(m.tests), func(i, j int) { m.tests[i], m.tests[j] = m.tests[j], m.tests[i] })
+ rng.Shuffle(len(m.benchmarks), func(i, j int) { m.benchmarks[i], m.benchmarks[j] = m.benchmarks[j], m.benchmarks[i] })
+ }
+
parseCpuList()
m.before()
@@ -1551,7 +1628,7 @@ func runTests(matchString func(pat, str string) (bool, error), tests []InternalT
ctx.deadline = deadline
t := &T{
common: common{
- signal: make(chan bool),
+ signal: make(chan bool, 1),
barrier: make(chan bool),
w: os.Stdout,
},
@@ -1564,11 +1641,12 @@ func runTests(matchString func(pat, str string) (bool, error), tests []InternalT
for _, test := range tests {
t.Run(test.Name, test.F)
}
- // Run catching the signal rather than the tRunner as a separate
- // goroutine to avoid adding a goroutine during the sequential
- // phase as this pollutes the stacktrace output when aborting.
- go func() { <-t.signal }()
})
+ select {
+ case <-t.signal:
+ default:
+ panic("internal error: tRunner exited without sending on t.signal")
+ }
ok = ok && !t.Failed()
ran = ran || t.ran
}