Commit 89cda2db authored by Marcel van Lohuizen's avatar Marcel van Lohuizen

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/18894Reviewed-by: default avatarRuss Cox <rsc@golang.org>
parent 5c83e651
...@@ -46,6 +46,7 @@ type InternalBenchmark struct { ...@@ -46,6 +46,7 @@ type InternalBenchmark struct {
// affecting benchmark results. // affecting benchmark results.
type B struct { type B struct {
common common
context *benchContext
N int N int
previousN int // number of iterations in the previous run previousN int // number of iterations in the previous run
previousDuration time.Duration // total duration of the previous run previousDuration time.Duration // total duration of the previous run
...@@ -299,6 +300,10 @@ func benchmarkName(name string, n int) string { ...@@ -299,6 +300,10 @@ func benchmarkName(name string, n int) string {
return name 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 // An internal function but exported because it is cross-package; part of the implementation
// of the "go test" command. // of the "go test" command.
func RunBenchmarks(matchString func(pat, str string) (bool, error), benchmarks []InternalBenchmark) { 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 ...@@ -334,46 +339,72 @@ func runBenchmarksInternal(matchString func(pat, str string) (bool, error), benc
} }
} }
ok := true ok := true
main := &B{
common: common{name: "Main"},
context: &benchContext{
maxLen: maxlen,
},
}
for _, Benchmark := range bs { for _, Benchmark := range bs {
for _, procs := range cpuList { ok = ok && expandCPU(main, Benchmark)
runtime.GOMAXPROCS(procs) }
b := &B{ return ok
common: common{ }
signal: make(chan bool),
name: Benchmark.Name, func expandCPU(parent *B, Benchmark InternalBenchmark) bool {
}, ok := true
benchFunc: Benchmark.F, for _, procs := range cpuList {
} runtime.GOMAXPROCS(procs)
benchName := benchmarkName(Benchmark.Name, procs) benchName := benchmarkName(Benchmark.Name, procs)
fmt.Printf("%-*s\t", maxlen, benchName) fmt.Printf("%-*s\t", parent.context.maxLen, benchName)
r := b.run() b := parent.runBench(Benchmark.Name, Benchmark.F)
if b.failed { r := b.result
ok = false if b.failed {
// The output could be very long here, but probably isn't. ok = false
// We print it all, regardless, because we don't want to trim the reason // The output could be very long here, but probably isn't.
// the benchmark failed. // We print it all, regardless, because we don't want to trim the reason
fmt.Printf("--- FAIL: %s\n%s", benchName, b.output) // the benchmark failed.
continue fmt.Printf("--- FAIL: %s\n%s", benchName, b.output)
} continue
results := r.String() }
if *benchmarkMemory || b.showAllocResult { results := r.String()
results += "\t" + r.MemString() if *benchmarkMemory || b.showAllocResult {
} results += "\t" + r.MemString()
fmt.Println(results) }
// Unlike with tests, we ignore the -chatty flag and always print output for fmt.Println(results)
// benchmarks since the output generation time will skew the results. // Unlike with tests, we ignore the -chatty flag and always print output for
if len(b.output) > 0 { // benchmarks since the output generation time will skew the results.
b.trimOutput() if len(b.output) > 0 {
fmt.Printf("--- BENCH: %s\n%s", benchName, b.output) 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) if p := runtime.GOMAXPROCS(-1); p != procs {
} fmt.Fprintf(os.Stderr, "testing: %s left GOMAXPROCS set to %d\n", benchName, p)
} }
} }
return ok 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. // trimOutput shortens the output from a benchmark, which can be very long.
func (b *B) trimOutput() { func (b *B) trimOutput() {
// The output is likely to appear multiple times because the benchmark // The output is likely to appear multiple times because the benchmark
......
...@@ -204,6 +204,7 @@ type common struct { ...@@ -204,6 +204,7 @@ type common struct {
finished bool finished bool
parent *common parent *common
level int // Nesting depth of test or benchmark.
name string // Name of test or benchmark. name string // Name of test or benchmark.
start time.Time // Time test or benchmark started start time.Time // Time test or benchmark started
duration time.Duration duration time.Duration
...@@ -524,6 +525,37 @@ func tRunner(t *T, fn func(t *T)) { ...@@ -524,6 +525,37 @@ func tRunner(t *T, fn func(t *T)) {
t.finished = true 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 // testContext holds all fields that are common to all tests. This includes
// synchronization primitives to run at most *parallel tests. // synchronization primitives to run at most *parallel tests.
type testContext struct { type testContext struct {
...@@ -660,11 +692,10 @@ func RunTests(matchString func(pat, str string) (bool, error), tests []InternalT ...@@ -660,11 +692,10 @@ func RunTests(matchString func(pat, str string) (bool, error), tests []InternalT
}, },
context: ctx, context: ctx,
} }
tRunner(t, func(t *T) { 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. // 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 { if err != nil {
fmt.Fprintf(os.Stderr, "testing: invalid regexp for -test.run: %s\n", err) fmt.Fprintf(os.Stderr, "testing: invalid regexp for -test.run: %s\n", err)
os.Exit(1) os.Exit(1)
...@@ -672,27 +703,7 @@ func RunTests(matchString func(pat, str string) (bool, error), tests []InternalT ...@@ -672,27 +703,7 @@ func RunTests(matchString func(pat, str string) (bool, error), tests []InternalT
if !matched { if !matched {
continue continue
} }
testName := tests[i].Name t.run(test.Name, test.F)
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
} }
// Run catching the signal rather than the tRunner as a separate // Run catching the signal rather than the tRunner as a separate
// goroutine to avoid adding a goroutine during the sequential // goroutine to avoid adding a goroutine during the sequential
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment