mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
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 <rsc@golang.org>
This commit is contained in:
parent
5c83e651ad
commit
89cda2db00
2 changed files with 100 additions and 58 deletions
|
|
@ -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 {
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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 {
|
||||||
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)) {
|
||||||
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
|
||||||
},
|
},
|
||||||
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
|
||||||
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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue