From 89cda2db007c8389ba39d292c6372ff0c6a7622f Mon Sep 17 00:00:00 2001 From: Marcel van Lohuizen Date: Fri, 29 Jan 2016 16:55:35 +0100 Subject: [PATCH] testing: hoisted chunks of code to prepare for Run method testing.go: - run method will evolve into the Run method. - added level field in common benchmark.go: - benchContext will be central to distinguish handling of benchmarks between normal Run methods and ones called from within Benchmark function. - expandCPU will evolve into the processing hook for Run methods called within normal processing. - runBench will evolve into the Run method. Change-Id: I1816f9985d5ba94deb0ad062302ea9aee0bb5338 Reviewed-on: https://go-review.googlesource.com/18894 Reviewed-by: Russ Cox --- src/testing/benchmark.go | 99 ++++++++++++++++++++++++++-------------- src/testing/testing.go | 59 ++++++++++++++---------- 2 files changed, 100 insertions(+), 58 deletions(-) diff --git a/src/testing/benchmark.go b/src/testing/benchmark.go index b092a9d9e2..4dac1e6d63 100644 --- a/src/testing/benchmark.go +++ b/src/testing/benchmark.go @@ -46,6 +46,7 @@ type InternalBenchmark struct { // affecting benchmark results. type B struct { common + context *benchContext N int previousN int // number of iterations in the previous run previousDuration time.Duration // total duration of the previous run @@ -299,6 +300,10 @@ func benchmarkName(name string, n int) string { return name } +type benchContext struct { + maxLen int // The largest recorded benchmark name. +} + // An internal function but exported because it is cross-package; part of the implementation // of the "go test" command. func RunBenchmarks(matchString func(pat, str string) (bool, error), benchmarks []InternalBenchmark) { @@ -334,46 +339,72 @@ func runBenchmarksInternal(matchString func(pat, str string) (bool, error), benc } } ok := true + main := &B{ + common: common{name: "Main"}, + context: &benchContext{ + maxLen: maxlen, + }, + } for _, Benchmark := range bs { - for _, procs := range cpuList { - runtime.GOMAXPROCS(procs) - b := &B{ - common: common{ - signal: make(chan bool), - name: Benchmark.Name, - }, - benchFunc: Benchmark.F, - } - benchName := benchmarkName(Benchmark.Name, procs) - fmt.Printf("%-*s\t", maxlen, benchName) - r := b.run() - if b.failed { - ok = false - // The output could be very long here, but probably isn't. - // We print it all, regardless, because we don't want to trim the reason - // the benchmark failed. - fmt.Printf("--- FAIL: %s\n%s", benchName, b.output) - continue - } - results := r.String() - if *benchmarkMemory || b.showAllocResult { - results += "\t" + r.MemString() - } - fmt.Println(results) - // Unlike with tests, we ignore the -chatty flag and always print output for - // benchmarks since the output generation time will skew the results. - if len(b.output) > 0 { - b.trimOutput() - fmt.Printf("--- BENCH: %s\n%s", benchName, b.output) - } - if p := runtime.GOMAXPROCS(-1); p != procs { - fmt.Fprintf(os.Stderr, "testing: %s left GOMAXPROCS set to %d\n", benchName, p) - } + ok = ok && expandCPU(main, Benchmark) + } + return ok +} + +func expandCPU(parent *B, Benchmark InternalBenchmark) bool { + ok := true + for _, procs := range cpuList { + runtime.GOMAXPROCS(procs) + benchName := benchmarkName(Benchmark.Name, procs) + fmt.Printf("%-*s\t", parent.context.maxLen, benchName) + b := parent.runBench(Benchmark.Name, Benchmark.F) + r := b.result + if b.failed { + ok = false + // The output could be very long here, but probably isn't. + // We print it all, regardless, because we don't want to trim the reason + // the benchmark failed. + fmt.Printf("--- FAIL: %s\n%s", benchName, b.output) + continue + } + results := r.String() + if *benchmarkMemory || b.showAllocResult { + results += "\t" + r.MemString() + } + fmt.Println(results) + // Unlike with tests, we ignore the -chatty flag and always print output for + // benchmarks since the output generation time will skew the results. + if len(b.output) > 0 { + b.trimOutput() + fmt.Printf("--- BENCH: %s\n%s", benchName, b.output) + } + if p := runtime.GOMAXPROCS(-1); p != procs { + fmt.Fprintf(os.Stderr, "testing: %s left GOMAXPROCS set to %d\n", benchName, p) } } return ok } +// runBench benchmarks f as a subbenchmark with the given name. It reports +// whether there were any failures. +// +// A subbenchmark is like any other benchmark. A benchmark that calls Run at +// least once will not be measured itself and will only run for one iteration. +func (b *B) runBench(name string, f func(b *B)) *B { + sub := &B{ + common: common{ + signal: make(chan bool), + name: name, + parent: &b.common, + level: b.level + 1, + }, + benchFunc: f, + context: b.context, + } + sub.run() + return sub +} + // trimOutput shortens the output from a benchmark, which can be very long. func (b *B) trimOutput() { // The output is likely to appear multiple times because the benchmark diff --git a/src/testing/testing.go b/src/testing/testing.go index edd6c4fe74..13739ccd9d 100644 --- a/src/testing/testing.go +++ b/src/testing/testing.go @@ -204,6 +204,7 @@ type common struct { finished bool 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 @@ -524,6 +525,37 @@ func tRunner(t *T, fn func(t *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 := name + if t.level > 0 { + testName = t.name + "/" + name + } + t = &T{ + common: common{ + barrier: make(chan bool), + signal: make(chan bool), + name: testName, + parent: &t.common, + level: t.level + 1, + }, + context: t.context, + } + + if *chatty { + fmt.Printf("=== 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 { @@ -660,11 +692,10 @@ func RunTests(matchString func(pat, str string) (bool, error), tests []InternalT }, context: ctx, } - tRunner(t, func(t *T) { - for i := 0; i < len(tests); i++ { + for _, test := range tests { // TODO: a version of this will be the Run method. - matched, err := matchString(*match, tests[i].Name) + matched, err := matchString(*match, test.Name) if err != nil { fmt.Fprintf(os.Stderr, "testing: invalid regexp for -test.run: %s\n", err) os.Exit(1) @@ -672,27 +703,7 @@ func RunTests(matchString func(pat, str string) (bool, error), tests []InternalT if !matched { continue } - testName := tests[i].Name - t := &T{ - common: common{ - barrier: make(chan bool), - signal: make(chan bool), - name: testName, - parent: &t.common, - }, - context: t.context, - } - - if *chatty { - fmt.Printf("=== 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, tests[i].F) - <-t.signal + 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 -- 2.48.1