aboutsummaryrefslogtreecommitdiff
path: root/libgo/go/testing/testing.go
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/testing/testing.go')
-rw-r--r--libgo/go/testing/testing.go355
1 files changed, 274 insertions, 81 deletions
diff --git a/libgo/go/testing/testing.go b/libgo/go/testing/testing.go
index bcbe8f1..5a3a9ab 100644
--- a/libgo/go/testing/testing.go
+++ b/libgo/go/testing/testing.go
@@ -45,7 +45,7 @@
//
// The benchmark function must run the target code b.N times.
// During benchmark execution, b.N is adjusted until the benchmark function lasts
-// long enough to be timed reliably. The output
+// long enough to be timed reliably. The output
// BenchmarkHello 10000000 282 ns/op
// means that the loop ran 10000000 times at a speed of 282 ns per loop.
//
@@ -118,6 +118,61 @@
// example function, at least one other function, type, variable, or constant
// declaration, and no test or benchmark functions.
//
+// Subtests and Sub-benchmarks
+//
+// The Run methods of T and B allow defining subtests and sub-benchmarks,
+// without having to define separate functions for each. This enables uses
+// like table-driven benchmarks and creating hierarchical tests.
+// It also provides a way to share common setup and tear-down code:
+//
+// func TestFoo(t *testing.T) {
+// // <setup code>
+// t.Run("A=1", func(t *testing.T) { ... })
+// t.Run("A=2", func(t *testing.T) { ... })
+// t.Run("B=1", func(t *testing.T) { ... })
+// // <tear-down code>
+// }
+//
+// Each subtest and sub-benchmark has a unique name: the combination of the name
+// of the top-level test and the sequence of names passed to Run, separated by
+// slashes, with an optional trailing sequence number for disambiguation.
+//
+// The argument to the -run and -bench command-line flags is a slash-separated
+// list of regular expressions that match each name element in turn.
+// For example:
+//
+// go test -run Foo # Run top-level tests matching "Foo".
+// go test -run Foo/A= # Run subtests of Foo matching "A=".
+// go test -run /A=1 # Run all subtests of a top-level test matching "A=1".
+//
+// Subtests can also be used to control parallelism. A parent test will only
+// complete once all of its subtests complete. In this example, all tests are
+// run in parallel with each other, and only with each other, regardless of
+// other top-level tests that may be defined:
+//
+// func TestGroupedParallel(t *testing.T) {
+// for _, tc := range tests {
+// tc := tc // capture range variable
+// t.Run(tc.Name, func(t *testing.T) {
+// t.Parallel()
+// ...
+// })
+// }
+// }
+//
+// Run does not return until parallel subtests have completed, providing a way
+// to clean up after a group of parallel tests:
+//
+// func TestTeardownParallel(t *testing.T) {
+// // This Run will not return until the parallel tests finish.
+// t.Run("group", func(t *testing.T) {
+// t.Run("Test1", parallelTest1)
+// t.Run("Test2", parallelTest2)
+// t.Run("Test3", parallelTest3)
+// })
+// // <tear-down code>
+// }
+//
// Main
//
// It is sometimes necessary for a test program to do extra setup or teardown
@@ -147,6 +202,7 @@ import (
"bytes"
"flag"
"fmt"
+ "io"
"os"
"runtime"
"runtime/debug"
@@ -159,8 +215,8 @@ import (
var (
// The short flag requests that tests run more quickly, but its functionality
- // is provided by test writers themselves. The testing package is just its
- // home. The all.bash installation script sets it to make installation more
+ // is provided by test writers themselves. The testing package is just its
+ // home. The all.bash installation script sets it to make installation more
// efficient, but by default the flag is off so a plain "go test" will do a
// full test of the package.
short = flag.Bool("test.short", false, "run smaller test suite to save time")
@@ -194,16 +250,23 @@ var (
// common holds the elements common between T and B and
// captures common methods such as Errorf.
type common struct {
- mu sync.RWMutex // guards output and failed
+ 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.
failed bool // Test or benchmark has failed.
skipped bool // Test of benchmark has been skipped.
- finished bool
+ finished bool // Test function has completed.
+ done bool // Test is finished and all subtests have completed.
+ parent *common
+ level int // Nesting depth of test or benchmark.
+ name string // Name of test or benchmark.
start time.Time // Time test or benchmark started
duration time.Duration
- self interface{} // To be sent on signal channel when done.
- signal chan interface{} // Output for serial tests.
+ barrier chan bool // To signal parallel subtests they may start.
+ signal chan bool // To signal a test is done.
+ sub []*T // Queue of subtests to be run in parallel.
}
// Short reports whether the -test.short flag is set.
@@ -250,6 +313,44 @@ func decorate(s string) string {
return buf.String()
}
+// flushToParent writes c.output to the parent after first writing the header
+// with the given format and arguments.
+func (c *common) flushToParent(format string, args ...interface{}) {
+ p := c.parent
+ p.mu.Lock()
+ defer p.mu.Unlock()
+
+ fmt.Fprintf(p.w, format, args...)
+
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ io.Copy(p.w, bytes.NewReader(c.output))
+ c.output = c.output[:0]
+}
+
+type indenter struct {
+ c *common
+}
+
+func (w indenter) Write(b []byte) (n int, err error) {
+ n = len(b)
+ for len(b) > 0 {
+ end := bytes.IndexByte(b, '\n')
+ if end == -1 {
+ end = len(b)
+ } else {
+ end++
+ }
+ // An indent of 4 spaces will neatly align the dashes with the status
+ // indicator of the parent.
+ const indent = " "
+ w.c.output = append(w.c.output, indent...)
+ w.c.output = append(w.c.output, b[:end]...)
+ b = b[end:]
+ }
+ return
+}
+
// fmtDuration returns a string representing d in the form "87.00s".
func fmtDuration(d time.Duration) string {
return fmt.Sprintf("%.2fs", d.Seconds())
@@ -281,7 +382,7 @@ var _ TB = (*T)(nil)
var _ TB = (*B)(nil)
// T is a type passed to Test functions to manage test state and support formatted test logs.
-// Logs are accumulated during execution and dumped to standard error when done.
+// Logs are accumulated during execution and dumped to standard output when done.
//
// A test ends when its Test function returns or calls any of the methods
// FailNow, Fatal, Fatalf, SkipNow, Skip, or Skipf. Those methods, as well as
@@ -292,17 +393,23 @@ var _ TB = (*B)(nil)
// may be called simultaneously from multiple goroutines.
type T struct {
common
- name string // Name of test.
- isParallel bool
- startParallel chan bool // Parallel tests will wait on this.
+ isParallel bool
+ context *testContext // For running tests and subtests.
}
func (c *common) private() {}
// Fail marks the function as having failed but continues execution.
func (c *common) Fail() {
+ if c.parent != nil {
+ c.parent.Fail()
+ }
c.mu.Lock()
defer c.mu.Unlock()
+ // c.done needs to be locked to synchronize checks to c.done in parent tests.
+ if c.done {
+ panic("Fail in goroutine after " + c.name + " has completed")
+ }
c.failed = true
}
@@ -336,9 +443,9 @@ func (c *common) FailNow() {
// This previous version duplicated code (those lines are in
// tRunner no matter what), but worse the goroutine teardown
// implicit in runtime.Goexit was not guaranteed to complete
- // before the test exited. If a test deferred an important cleanup
+ // before the test exited. If a test deferred an important cleanup
// function (like removing temporary files), there was no guarantee
- // it would run on a test failure. Because we send on c.signal during
+ // 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.finished = true
@@ -436,8 +543,13 @@ func (t *T) Parallel() {
// in the test duration. Record the elapsed time thus far and reset the
// timer afterwards.
t.duration += time.Since(t.start)
- t.signal <- (*T)(nil) // Release main testing loop
- <-t.startParallel // Wait for serial tests to finish
+
+ // Add to the list of tests to be released by the parent.
+ t.parent.sub = append(t.parent.sub, t)
+
+ t.signal <- true // Release calling test.
+ <-t.parent.barrier // Wait for the parent test to complete.
+ t.context.waitParallel()
t.start = time.Now()
}
@@ -448,8 +560,8 @@ type InternalTest struct {
F func(*T)
}
-func tRunner(t *T, test *InternalTest) {
- // When this goroutine is done, either because test.F(t)
+func tRunner(t *T, fn func(t *T)) {
+ // 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
// a signal saying that the test is done.
@@ -465,14 +577,130 @@ func tRunner(t *T, test *InternalTest) {
t.report()
panic(err)
}
- t.signal <- t
+
+ if len(t.sub) > 0 {
+ // Run parallel subtests.
+ // Decrease the running count for this test.
+ t.context.release()
+ // Release the parallel subtests.
+ close(t.barrier)
+ // Wait for subtests to complete.
+ for _, sub := range t.sub {
+ <-sub.signal
+ }
+ if !t.isParallel {
+ // Reacquire the count for sequential tests. See comment in Run.
+ t.context.waitParallel()
+ }
+ } else if t.isParallel {
+ // Only release the count for this test if it was run as a parallel
+ // test. See comment in Run method.
+ t.context.release()
+ }
+ t.report() // Report after all subtests have finished.
+
+ // Do not lock t.done to allow race detector to detect race in case
+ // the user does not appropriately synchronizes a goroutine.
+ t.done = true
+ t.signal <- true
}()
t.start = time.Now()
- test.F(t)
+ fn(t)
t.finished = true
}
+// Run runs f as a subtest of t called name. It reports whether f succeeded.
+// Run will block until all its parallel subtests have completed.
+func (t *T) Run(name string, f func(t *T)) bool {
+ testName, ok := t.context.match.fullName(&t.common, name)
+ if !ok {
+ return true
+ }
+ t = &T{
+ common: common{
+ barrier: make(chan bool),
+ signal: make(chan bool),
+ name: testName,
+ parent: &t.common,
+ level: t.level + 1,
+ chatty: t.chatty,
+ },
+ context: t.context,
+ }
+ t.w = indenter{&t.common}
+
+ if t.chatty {
+ // Print directly to root's io.Writer so there is no delay.
+ root := t.parent
+ for ; root.parent != nil; root = root.parent {
+ }
+ fmt.Fprintf(root.w, "=== RUN %s\n", t.name)
+ }
+ // Instead of reducing the running count of this test before calling the
+ // tRunner and increasing it afterwards, we rely on tRunner keeping the
+ // count correct. This ensures that a sequence of sequential tests runs
+ // without being preempted, even when their parent is a parallel test. This
+ // may especially reduce surprises if *parallel == 1.
+ go tRunner(t, f)
+ <-t.signal
+ return !t.failed
+}
+
+// testContext holds all fields that are common to all tests. This includes
+// synchronization primitives to run at most *parallel tests.
+type testContext struct {
+ match *matcher
+
+ mu sync.Mutex
+
+ // Channel used to signal tests that are ready to be run in parallel.
+ startParallel chan bool
+
+ // running is the number of tests currently running in parallel.
+ // This does not include tests that are waiting for subtests to complete.
+ running int
+
+ // numWaiting is the number tests waiting to be run in parallel.
+ numWaiting int
+
+ // maxParallel is a copy of the parallel flag.
+ maxParallel int
+}
+
+func newTestContext(maxParallel int, m *matcher) *testContext {
+ return &testContext{
+ match: m,
+ startParallel: make(chan bool),
+ maxParallel: maxParallel,
+ running: 1, // Set the count to 1 for the main (sequential) test.
+ }
+}
+
+func (c *testContext) waitParallel() {
+ c.mu.Lock()
+ if c.running < c.maxParallel {
+ c.running++
+ c.mu.Unlock()
+ return
+ }
+ c.numWaiting++
+ c.mu.Unlock()
+ <-c.startParallel
+}
+
+func (c *testContext) release() {
+ c.mu.Lock()
+ if c.numWaiting == 0 {
+ c.running--
+ c.mu.Unlock()
+ return
+ }
+ c.numWaiting--
+ c.mu.Unlock()
+ c.startParallel <- true // Pick a waiting test to be run.
+}
+
// An internal function but exported because it is cross-package; part of the implementation
// of the "go test" command.
func Main(matchString func(pat, str string) (bool, error), tests []InternalTest, benchmarks []InternalBenchmark, examples []InternalExample) {
@@ -514,27 +742,29 @@ func (m *M) Run() int {
testOk := RunTests(m.matchString, m.tests)
exampleOk := RunExamples(m.matchString, m.examples)
stopAlarm()
- if !testOk || !exampleOk {
+ if !testOk || !exampleOk || !runBenchmarksInternal(m.matchString, m.benchmarks) {
fmt.Println("FAIL")
after()
return 1
}
fmt.Println("PASS")
- RunBenchmarks(m.matchString, m.benchmarks)
after()
return 0
}
func (t *T) report() {
+ if t.parent == nil {
+ return
+ }
dstr := fmtDuration(t.duration)
- format := "--- %s: %s (%s)\n%s"
+ format := "--- %s: %s (%s)\n"
if t.Failed() {
- fmt.Printf(format, "FAIL", t.name, dstr, t.output)
- } else if *chatty {
+ t.flushToParent(format, "FAIL", t.name, dstr)
+ } else if t.chatty {
if t.Skipped() {
- fmt.Printf(format, "SKIP", t.name, dstr, t.output)
+ t.flushToParent(format, "SKIP", t.name, dstr)
} else {
- fmt.Printf(format, "PASS", t.name, dstr, t.output)
+ t.flushToParent(format, "PASS", t.name, dstr)
}
}
}
@@ -547,63 +777,26 @@ func RunTests(matchString func(pat, str string) (bool, error), tests []InternalT
}
for _, procs := range cpuList {
runtime.GOMAXPROCS(procs)
- // We build a new channel tree for each run of the loop.
- // collector merges in one channel all the upstream signals from parallel tests.
- // If all tests pump to the same channel, a bug can occur where a test
- // kicks off a goroutine that Fails, yet the test still delivers a completion signal,
- // which skews the counting.
- var collector = make(chan interface{})
-
- numParallel := 0
- startParallel := make(chan bool)
-
- for i := 0; i < len(tests); i++ {
- matched, err := matchString(*match, tests[i].Name)
- if err != nil {
- fmt.Fprintf(os.Stderr, "testing: invalid regexp for -test.run: %s\n", err)
- os.Exit(1)
- }
- if !matched {
- continue
- }
- testName := tests[i].Name
- t := &T{
- common: common{
- signal: make(chan interface{}),
- },
- name: testName,
- startParallel: startParallel,
- }
- t.self = t
- if *chatty {
- fmt.Printf("=== RUN %s\n", t.name)
- }
- go tRunner(t, &tests[i])
- out := (<-t.signal).(*T)
- if out == nil { // Parallel run.
- go func() {
- collector <- <-t.signal
- }()
- numParallel++
- continue
- }
- t.report()
- ok = ok && !out.Failed()
+ ctx := newTestContext(*parallel, newMatcher(matchString, *match, "-test.run"))
+ t := &T{
+ common: common{
+ signal: make(chan bool),
+ barrier: make(chan bool),
+ w: os.Stdout,
+ chatty: *chatty,
+ },
+ context: ctx,
}
-
- running := 0
- for numParallel+running > 0 {
- if running < *parallel && numParallel > 0 {
- startParallel <- true
- running++
- numParallel--
- continue
+ tRunner(t, func(t *T) {
+ for _, test := range tests {
+ t.Run(test.Name, test.F)
}
- t := (<-collector).(*T)
- t.report()
- ok = ok && !t.Failed()
- running--
- }
+ // 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 }()
+ })
+ ok = ok && !t.Failed()
}
return
}