diff options
author | Ian Lance Taylor <ian@gcc.gnu.org> | 2016-07-22 18:15:38 +0000 |
---|---|---|
committer | Ian Lance Taylor <ian@gcc.gnu.org> | 2016-07-22 18:15:38 +0000 |
commit | 22b955cca564a9a3a5b8c9d9dd1e295b7943c128 (patch) | |
tree | abdbd898676e1f853fca2d7e031d105d7ebcf676 /libgo/go/testing | |
parent | 9d04a3af4c6491536badf6bde9707c907e4d196b (diff) | |
download | gcc-22b955cca564a9a3a5b8c9d9dd1e295b7943c128.zip gcc-22b955cca564a9a3a5b8c9d9dd1e295b7943c128.tar.gz gcc-22b955cca564a9a3a5b8c9d9dd1e295b7943c128.tar.bz2 |
libgo: update to go1.7rc3
Reviewed-on: https://go-review.googlesource.com/25150
From-SVN: r238662
Diffstat (limited to 'libgo/go/testing')
-rw-r--r-- | libgo/go/testing/allocs.go | 4 | ||||
-rw-r--r-- | libgo/go/testing/allocs_test.go | 2 | ||||
-rw-r--r-- | libgo/go/testing/benchmark.go | 281 | ||||
-rw-r--r-- | libgo/go/testing/example.go | 26 | ||||
-rw-r--r-- | libgo/go/testing/iotest/reader.go | 2 | ||||
-rw-r--r-- | libgo/go/testing/match.go | 167 | ||||
-rw-r--r-- | libgo/go/testing/match_test.go | 185 | ||||
-rw-r--r-- | libgo/go/testing/quick/quick.go | 44 | ||||
-rw-r--r-- | libgo/go/testing/sub_test.go | 517 | ||||
-rw-r--r-- | libgo/go/testing/testing.go | 355 | ||||
-rw-r--r-- | libgo/go/testing/testing_test.go | 2 |
11 files changed, 1408 insertions, 177 deletions
diff --git a/libgo/go/testing/allocs.go b/libgo/go/testing/allocs.go index 9ec47bd..1eeb2d4 100644 --- a/libgo/go/testing/allocs.go +++ b/libgo/go/testing/allocs.go @@ -1,4 +1,4 @@ -// Copyright 2013 The Go Authors. All rights reserved. +// Copyright 2013 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. @@ -12,7 +12,7 @@ import ( // Although the return value has type float64, it will always be an integral value. // // To compute the number of allocations, the function will first be run once as -// a warm-up. The average number of allocations over the specified number of +// a warm-up. The average number of allocations over the specified number of // runs will then be measured and returned. // // AllocsPerRun sets GOMAXPROCS to 1 during its measurement and will restore diff --git a/libgo/go/testing/allocs_test.go b/libgo/go/testing/allocs_test.go index ec17daa..5b346aa 100644 --- a/libgo/go/testing/allocs_test.go +++ b/libgo/go/testing/allocs_test.go @@ -1,4 +1,4 @@ -// Copyright 2014 The Go Authors. All rights reserved. +// Copyright 2014 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/libgo/go/testing/benchmark.go b/libgo/go/testing/benchmark.go index 85178c2..5d58b85 100644 --- a/libgo/go/testing/benchmark.go +++ b/libgo/go/testing/benchmark.go @@ -14,7 +14,7 @@ import ( "time" ) -var matchBenchmarks = flag.String("test.bench", "", "regular expression to select benchmarks to run") +var matchBenchmarks = flag.String("test.bench", "", "regular expression per path component to select benchmarks to run") var benchTime = flag.Duration("test.benchtime", 1*time.Second, "approximate run time for each benchmark") var benchmarkMemory = flag.Bool("test.benchmem", false, "print memory allocations for benchmarks") @@ -46,13 +46,17 @@ 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 - benchmark InternalBenchmark + benchFunc func(b *B) + benchTime time.Duration bytes int64 + missingBytes bool // one of the subbenchmarks does not have bytes set. timerOn bool showAllocResult bool + hasSub bool result BenchmarkResult parallelism int // RunParallel creates parallelism*GOMAXPROCS goroutines // The initial states of memStats.Mallocs and memStats.TotalAlloc. @@ -63,7 +67,7 @@ type B struct { netBytes uint64 } -// StartTimer starts timing a test. This function is called automatically +// StartTimer starts timing a test. This function is called automatically // before a benchmark starts, but it can also used to resume timing after // a call to StopTimer. func (b *B) StartTimer() { @@ -76,7 +80,7 @@ func (b *B) StartTimer() { } } -// StopTimer stops timing a test. This can be used to pause the timer +// StopTimer stops timing a test. This can be used to pause the timer // while performing complex initialization that you don't // want to measure. func (b *B) StopTimer() { @@ -132,7 +136,7 @@ func (b *B) runN(n int) { b.parallelism = 1 b.ResetTimer() b.StartTimer() - b.benchmark.F(b) + b.benchFunc(b) b.StopTimer() b.previousN = n b.previousDuration = b.duration @@ -185,32 +189,78 @@ func roundUp(n int) int { } } -// run times the benchmark function in a separate goroutine. +// run1 runs the first iteration of benchFunc. It returns whether more +// iterations of this benchmarks should be run. +func (b *B) run1() bool { + if ctx := b.context; ctx != nil { + // Extend maxLen, if needed. + if n := len(b.name) + ctx.extLen + 1; n > ctx.maxLen { + ctx.maxLen = n + 8 // Add additional slack to avoid too many jumps in size. + } + } + go func() { + // Signal that we're done whether we return normally + // or by FailNow's runtime.Goexit. + defer func() { + b.signal <- true + }() + + b.runN(1) + }() + <-b.signal + if b.failed { + fmt.Fprintf(b.w, "--- FAIL: %s\n%s", b.name, b.output) + return false + } + // Only print the output if we know we are not going to proceed. + // Otherwise it is printed in processBench. + if b.hasSub || b.finished { + tag := "BENCH" + if b.skipped { + tag = "SKIP" + } + if b.chatty && (len(b.output) > 0 || b.finished) { + b.trimOutput() + fmt.Fprintf(b.w, "--- %s: %s\n%s", tag, b.name, b.output) + } + return false + } + return true +} + +// run executes the benchmark in a separate goroutine, including all of its +// subbenchmarks. b must not have subbenchmarks. func (b *B) run() BenchmarkResult { + if b.context != nil { + // Running go test --test.bench + b.context.processBench(b) // Must call doBench. + } else { + // Running func Benchmark. + b.doBench() + } + return b.result +} + +func (b *B) doBench() BenchmarkResult { go b.launch() <-b.signal return b.result } -// launch launches the benchmark function. It gradually increases the number +// launch launches the benchmark function. It gradually increases the number // of benchmark iterations until the benchmark runs for the requested benchtime. -// It prints timing information in this form -// testing.BenchmarkHello 100000 19 ns/op -// launch is run by the run function as a separate goroutine. +// launch is run by the doBench function as a separate goroutine. +// run1 must have been called on b. func (b *B) launch() { - // Run the benchmark for a single iteration in case it's expensive. - n := 1 - // Signal that we're done whether we return normally // or by FailNow's runtime.Goexit. defer func() { - b.signal <- b + b.signal <- true }() - b.runN(n) // Run the benchmark for at least the specified amount of time. - d := *benchTime - for !b.failed && b.duration < d && n < 1e9 { + d := b.benchTime + for n := 1; !b.failed && b.duration < d && n < 1e9; { last := n // Predict required iterations. if b.nsPerOp() == 0 { @@ -299,12 +349,23 @@ func benchmarkName(name string, n int) string { return name } +type benchContext struct { + match *matcher + + maxLen int // The largest recorded benchmark name. + extLen int // Maximum extension length. +} + // 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) { + runBenchmarksInternal(matchString, benchmarks) +} + +func runBenchmarksInternal(matchString func(pat, str string) (bool, error), benchmarks []InternalBenchmark) bool { // If no flag was specified, don't run benchmarks. if len(*matchBenchmarks) == 0 { - return + return true } // Collect matching benchmarks and determine longest name. maxprocs := 1 @@ -313,59 +374,144 @@ func RunBenchmarks(matchString func(pat, str string) (bool, error), benchmarks [ maxprocs = procs } } - maxlen := 0 + ctx := &benchContext{ + match: newMatcher(matchString, *matchBenchmarks, "-test.bench"), + extLen: len(benchmarkName("", maxprocs)), + } var bs []InternalBenchmark for _, Benchmark := range benchmarks { - matched, err := matchString(*matchBenchmarks, Benchmark.Name) - if err != nil { - fmt.Fprintf(os.Stderr, "testing: invalid regexp for -test.bench: %s\n", err) - os.Exit(1) - } - if matched { + if _, matched := ctx.match.fullName(nil, Benchmark.Name); matched { bs = append(bs, Benchmark) benchName := benchmarkName(Benchmark.Name, maxprocs) - if l := len(benchName); l > maxlen { - maxlen = l + if l := len(benchName) + ctx.extLen + 1; l > ctx.maxLen { + ctx.maxLen = l } } } - for _, Benchmark := range bs { - for _, procs := range cpuList { - runtime.GOMAXPROCS(procs) - b := &B{ + main := &B{ + common: common{ + name: "Main", + w: os.Stdout, + chatty: *chatty, + }, + benchFunc: func(b *B) { + for _, Benchmark := range bs { + b.Run(Benchmark.Name, Benchmark.F) + } + }, + benchTime: *benchTime, + context: ctx, + } + main.runN(1) + return !main.failed +} + +// processBench runs bench b for the configured CPU counts and prints the results. +func (ctx *benchContext) processBench(b *B) { + for i, procs := range cpuList { + runtime.GOMAXPROCS(procs) + benchName := benchmarkName(b.name, procs) + fmt.Fprintf(b.w, "%-*s\t", ctx.maxLen, benchName) + // Recompute the running time for all but the first iteration. + if i > 0 { + b = &B{ common: common{ - signal: make(chan interface{}), + signal: make(chan bool), + name: b.name, + w: b.w, + chatty: b.chatty, }, - benchmark: Benchmark, - } - benchName := benchmarkName(Benchmark.Name, procs) - fmt.Printf("%-*s\t", maxlen, benchName) - r := b.run() - if b.failed { - // 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) + benchFunc: b.benchFunc, + benchTime: b.benchTime, } + b.run1() + } + r := b.doBench() + if b.failed { + // 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.Fprintf(b.w, "--- FAIL: %s\n%s", benchName, b.output) + continue + } + results := r.String() + if *benchmarkMemory || b.showAllocResult { + results += "\t" + r.MemString() + } + fmt.Fprintln(b.w, 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.Fprintf(b.w, "--- 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) } } } +// Run 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 be called once with N=1. +func (b *B) Run(name string, f func(b *B)) bool { + // Since b has subbenchmarks, we will no longer run it as a benchmark itself. + // Release the lock and acquire it on exit to ensure locks stay paired. + b.hasSub = true + benchmarkLock.Unlock() + defer benchmarkLock.Lock() + + benchName, ok := b.name, true + if b.context != nil { + benchName, ok = b.context.match.fullName(&b.common, name) + } + if !ok { + return true + } + sub := &B{ + common: common{ + signal: make(chan bool), + name: benchName, + parent: &b.common, + level: b.level + 1, + w: b.w, + chatty: b.chatty, + }, + benchFunc: f, + benchTime: b.benchTime, + context: b.context, + } + if sub.run1() { + sub.run() + } + b.add(sub.result) + return !sub.failed +} + +// add simulates running benchmarks in sequence in a single iteration. It is +// used to give some meaningful results in case func Benchmark is used in +// combination with Run. +func (b *B) add(other BenchmarkResult) { + r := &b.result + // The aggregated BenchmarkResults resemble running all subbenchmarks as + // in sequence in a single benchmark. + r.N = 1 + r.T += time.Duration(other.NsPerOp()) + if other.Bytes == 0 { + // Summing Bytes is meaningless in aggregate if not all subbenchmarks + // set it. + b.missingBytes = true + r.Bytes = 0 + } + if !b.missingBytes { + r.Bytes += other.Bytes + } + r.MemAllocs += uint64(other.AllocsPerOp()) + r.MemBytes += uint64(other.AllocedBytesPerOp()) +} + // 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 @@ -416,8 +562,11 @@ func (pb *PB) Next() bool { // The body function will be run in each goroutine. It should set up any // goroutine-local state and then iterate until pb.Next returns false. // It should not use the StartTimer, StopTimer, or ResetTimer functions, -// because they have global effect. +// because they have global effect. It should also not call Run. func (b *B) RunParallel(body func(*PB)) { + if b.N == 0 { + return // Nothing to do when probing. + } // Calculate grain size as number of iterations that take ~100µs. // 100µs is enough to amortize the overhead and provide sufficient // dynamic load balancing. @@ -466,12 +615,24 @@ func (b *B) SetParallelism(p int) { // Benchmark benchmarks a single function. Useful for creating // custom benchmarks that do not use the "go test" command. +// +// If f calls Run, the result will be an estimate of running all its +// subbenchmarks that don't call Run in sequence in a single benchmark. func Benchmark(f func(b *B)) BenchmarkResult { b := &B{ common: common{ - signal: make(chan interface{}), + signal: make(chan bool), + w: discard{}, }, - benchmark: InternalBenchmark{"", f}, + benchFunc: f, + benchTime: *benchTime, + } + if !b.run1() { + return BenchmarkResult{} } return b.run() } + +type discard struct{} + +func (discard) Write(b []byte) (n int, err error) { return len(b), nil } diff --git a/libgo/go/testing/example.go b/libgo/go/testing/example.go index 30baf27..fd8343f 100644 --- a/libgo/go/testing/example.go +++ b/libgo/go/testing/example.go @@ -9,14 +9,16 @@ import ( "fmt" "io" "os" + "sort" "strings" "time" ) type InternalExample struct { - Name string - F func() - Output string + Name string + F func() + Output string + Unordered bool } func RunExamples(matchString func(pat, str string) (bool, error), examples []InternalExample) (ok bool) { @@ -41,6 +43,12 @@ func RunExamples(matchString func(pat, str string) (bool, error), examples []Int return } +func sortLines(output string) string { + lines := strings.Split(output, "\n") + sort.Strings(lines) + return strings.Join(lines, "\n") +} + func runExample(eg InternalExample) (ok bool) { if *chatty { fmt.Printf("=== RUN %s\n", eg.Name) @@ -80,8 +88,16 @@ func runExample(eg InternalExample) (ok bool) { var fail string err := recover() - if g, e := strings.TrimSpace(out), strings.TrimSpace(eg.Output); g != e && err == nil { - fail = fmt.Sprintf("got:\n%s\nwant:\n%s\n", g, e) + got := strings.TrimSpace(out) + want := strings.TrimSpace(eg.Output) + if eg.Unordered { + if sortLines(got) != sortLines(want) && err == nil { + fail = fmt.Sprintf("got:\n%s\nwant (unordered):\n%s\n", out, eg.Output) + } + } else { + if got != want && err == nil { + fail = fmt.Sprintf("got:\n%s\nwant:\n%s\n", got, want) + } } if fail != "" || err != nil { fmt.Printf("--- FAIL: %s (%s)\n%s", eg.Name, dstr, fail) diff --git a/libgo/go/testing/iotest/reader.go b/libgo/go/testing/iotest/reader.go index a5bccca..8d82018 100644 --- a/libgo/go/testing/iotest/reader.go +++ b/libgo/go/testing/iotest/reader.go @@ -71,7 +71,7 @@ func (r *dataErrReader) Read(p []byte) (n int, err error) { var ErrTimeout = errors.New("timeout") // TimeoutReader returns ErrTimeout on the second read -// with no data. Subsequent calls to read succeed. +// with no data. Subsequent calls to read succeed. func TimeoutReader(r io.Reader) io.Reader { return &timeoutReader{r, 0} } type timeoutReader struct { diff --git a/libgo/go/testing/match.go b/libgo/go/testing/match.go new file mode 100644 index 0000000..7751035 --- /dev/null +++ b/libgo/go/testing/match.go @@ -0,0 +1,167 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package testing + +import ( + "fmt" + "os" + "strconv" + "strings" + "sync" +) + +// matcher sanitizes, uniques, and filters names of subtests and subbenchmarks. +type matcher struct { + filter []string + matchFunc func(pat, str string) (bool, error) + + mu sync.Mutex + subNames map[string]int64 +} + +// TODO: fix test_main to avoid race and improve caching, also allowing to +// eliminate this Mutex. +var matchMutex sync.Mutex + +func newMatcher(matchString func(pat, str string) (bool, error), patterns, name string) *matcher { + var filter []string + if patterns != "" { + filter = splitRegexp(patterns) + for i, s := range filter { + filter[i] = rewrite(s) + } + // Verify filters before doing any processing. + for i, s := range filter { + if _, err := matchString(s, "non-empty"); err != nil { + fmt.Fprintf(os.Stderr, "testing: invalid regexp for element %d of %s (%q): %s\n", i, name, s, err) + os.Exit(1) + } + } + } + return &matcher{ + filter: filter, + matchFunc: matchString, + subNames: map[string]int64{}, + } +} + +func (m *matcher) fullName(c *common, subname string) (name string, ok bool) { + name = subname + + m.mu.Lock() + defer m.mu.Unlock() + + if c != nil && c.level > 0 { + name = m.unique(c.name, rewrite(subname)) + } + + matchMutex.Lock() + defer matchMutex.Unlock() + + // We check the full array of paths each time to allow for the case that + // a pattern contains a '/'. + for i, s := range strings.Split(name, "/") { + if i >= len(m.filter) { + break + } + if ok, _ := m.matchFunc(m.filter[i], s); !ok { + return name, false + } + } + return name, true +} + +func splitRegexp(s string) []string { + a := make([]string, 0, strings.Count(s, "/")) + cs := 0 + cp := 0 + for i := 0; i < len(s); { + switch s[i] { + case '[': + cs++ + case ']': + if cs--; cs < 0 { // An unmatched ']' is legal. + cs = 0 + } + case '(': + if cs == 0 { + cp++ + } + case ')': + if cs == 0 { + cp-- + } + case '\\': + i++ + case '/': + if cs == 0 && cp == 0 { + a = append(a, s[:i]) + s = s[i+1:] + i = 0 + continue + } + } + i++ + } + return append(a, s) +} + +// unique creates a unique name for the given parent and subname by affixing it +// with one ore more counts, if necessary. +func (m *matcher) unique(parent, subname string) string { + name := fmt.Sprintf("%s/%s", parent, subname) + empty := subname == "" + for { + next, exists := m.subNames[name] + if !empty && !exists { + m.subNames[name] = 1 // next count is 1 + return name + } + // Name was already used. We increment with the count and append a + // string with the count. + m.subNames[name] = next + 1 + + // Add a count to guarantee uniqueness. + name = fmt.Sprintf("%s#%02d", name, next) + empty = false + } +} + +// rewrite rewrites a subname to having only printable characters and no white +// space. +func rewrite(s string) string { + b := []byte{} + for _, r := range s { + switch { + case isSpace(r): + b = append(b, '_') + case !strconv.IsPrint(r): + s := strconv.QuoteRune(r) + b = append(b, s[1:len(s)-1]...) + default: + b = append(b, string(r)...) + } + } + return string(b) +} + +func isSpace(r rune) bool { + if r < 0x2000 { + switch r { + // Note: not the same as Unicode Z class. + case '\t', '\n', '\v', '\f', '\r', ' ', 0x85, 0xA0, 0x1680: + return true + } + } else { + if r <= 0x200a { + return true + } + switch r { + case 0x2028, 0x2029, 0x202f, 0x205f, 0x3000: + return true + } + } + return false +} diff --git a/libgo/go/testing/match_test.go b/libgo/go/testing/match_test.go new file mode 100644 index 0000000..8c1c5f4 --- /dev/null +++ b/libgo/go/testing/match_test.go @@ -0,0 +1,185 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package testing + +import ( + "reflect" + "regexp" + "unicode" +) + +// Verify that our IsSpace agrees with unicode.IsSpace. +func TestIsSpace(t *T) { + n := 0 + for r := rune(0); r <= unicode.MaxRune; r++ { + if isSpace(r) != unicode.IsSpace(r) { + t.Errorf("IsSpace(%U)=%t incorrect", r, isSpace(r)) + n++ + if n > 10 { + return + } + } + } +} + +func TestSplitRegexp(t *T) { + res := func(s ...string) []string { return s } + testCases := []struct { + pattern string + result []string + }{ + // Correct patterns + // If a regexp pattern is correct, all split regexps need to be correct + // as well. + {"", res("")}, + {"/", res("", "")}, + {"//", res("", "", "")}, + {"A", res("A")}, + {"A/B", res("A", "B")}, + {"A/B/", res("A", "B", "")}, + {"/A/B/", res("", "A", "B", "")}, + {"[A]/(B)", res("[A]", "(B)")}, + {"[/]/[/]", res("[/]", "[/]")}, + {"[/]/[:/]", res("[/]", "[:/]")}, + {"/]", res("", "]")}, + {"]/", res("]", "")}, + {"]/[/]", res("]", "[/]")}, + {`([)/][(])`, res(`([)/][(])`)}, + {"[(]/[)]", res("[(]", "[)]")}, + + // Faulty patterns + // Errors in original should produce at least one faulty regexp in results. + {")/", res(")/")}, + {")/(/)", res(")/(", ")")}, + {"a[/)b", res("a[/)b")}, + {"(/]", res("(/]")}, + {"(/", res("(/")}, + {"[/]/[/", res("[/]", "[/")}, + {`\p{/}`, res(`\p{`, "}")}, + {`\p/`, res(`\p`, "")}, + {`[[:/:]]`, res(`[[:/:]]`)}, + } + for _, tc := range testCases { + a := splitRegexp(tc.pattern) + if !reflect.DeepEqual(a, tc.result) { + t.Errorf("splitRegexp(%q) = %#v; want %#v", tc.pattern, a, tc.result) + } + + // If there is any error in the pattern, one of the returned subpatterns + // needs to have an error as well. + if _, err := regexp.Compile(tc.pattern); err != nil { + ok := true + for _, re := range a { + if _, err := regexp.Compile(re); err != nil { + ok = false + } + } + if ok { + t.Errorf("%s: expected error in any of %q", tc.pattern, a) + } + } + } +} + +func TestMatcher(t *T) { + testCases := []struct { + pattern string + parent, sub string + ok bool + }{ + // Behavior without subtests. + {"", "", "TestFoo", true}, + {"TestFoo", "", "TestFoo", true}, + {"TestFoo/", "", "TestFoo", true}, + {"TestFoo/bar/baz", "", "TestFoo", true}, + {"TestFoo", "", "TestBar", false}, + {"TestFoo/", "", "TestBar", false}, + {"TestFoo/bar/baz", "", "TestBar/bar/baz", false}, + + // with subtests + {"", "TestFoo", "x", true}, + {"TestFoo", "TestFoo", "x", true}, + {"TestFoo/", "TestFoo", "x", true}, + {"TestFoo/bar/baz", "TestFoo", "bar", true}, + // Subtest with a '/' in its name still allows for copy and pasted names + // to match. + {"TestFoo/bar/baz", "TestFoo", "bar/baz", true}, + {"TestFoo/bar/baz", "TestFoo/bar", "baz", true}, + {"TestFoo/bar/baz", "TestFoo", "x", false}, + {"TestFoo", "TestBar", "x", false}, + {"TestFoo/", "TestBar", "x", false}, + {"TestFoo/bar/baz", "TestBar", "x/bar/baz", false}, + + // subtests only + {"", "TestFoo", "x", true}, + {"/", "TestFoo", "x", true}, + {"./", "TestFoo", "x", true}, + {"./.", "TestFoo", "x", true}, + {"/bar/baz", "TestFoo", "bar", true}, + {"/bar/baz", "TestFoo", "bar/baz", true}, + {"//baz", "TestFoo", "bar/baz", true}, + {"//", "TestFoo", "bar/baz", true}, + {"/bar/baz", "TestFoo/bar", "baz", true}, + {"//foo", "TestFoo", "bar/baz", false}, + {"/bar/baz", "TestFoo", "x", false}, + {"/bar/baz", "TestBar", "x/bar/baz", false}, + } + + for _, tc := range testCases { + m := newMatcher(regexp.MatchString, tc.pattern, "-test.run") + + parent := &common{name: tc.parent} + if tc.parent != "" { + parent.level = 1 + } + if n, ok := m.fullName(parent, tc.sub); ok != tc.ok { + t.Errorf("for pattern %q, fullName(parent=%q, sub=%q) = %q, ok %v; want ok %v", + tc.pattern, tc.parent, tc.sub, n, ok, tc.ok) + } + } +} + +func TestNaming(t *T) { + m := newMatcher(regexp.MatchString, "", "") + + parent := &common{name: "x", level: 1} // top-level test. + + // Rig the matcher with some preloaded values. + m.subNames["x/b"] = 1000 + + testCases := []struct { + name, want string + }{ + // Uniqueness + {"", "x/#00"}, + {"", "x/#01"}, + + {"t", "x/t"}, + {"t", "x/t#01"}, + {"t", "x/t#02"}, + + {"a#01", "x/a#01"}, // user has subtest with this name. + {"a", "x/a"}, // doesn't conflict with this name. + {"a", "x/a#01#01"}, // conflict, add disambiguating string. + {"a", "x/a#02"}, // This string is claimed now, so resume + {"a", "x/a#03"}, // with counting. + {"a#02", "x/a#02#01"}, + + {"b", "x/b#1000"}, // rigged, see above + {"b", "x/b#1001"}, + + // // Sanitizing + {"A:1 B:2", "x/A:1_B:2"}, + {"s\t\r\u00a0", "x/s___"}, + {"\x01", `x/\x01`}, + {"\U0010ffff", `x/\U0010ffff`}, + } + + for i, tc := range testCases { + if got, _ := m.fullName(parent, tc.name); got != tc.want { + t.Errorf("%d:%s: got %q; want %q", i, tc.name, got, tc.want) + } + } +} diff --git a/libgo/go/testing/quick/quick.go b/libgo/go/testing/quick/quick.go index 187195c..798d41a 100644 --- a/libgo/go/testing/quick/quick.go +++ b/libgo/go/testing/quick/quick.go @@ -239,8 +239,8 @@ func (s *CheckEqualError) Error() string { } // Check looks for an input to f, any function that returns bool, -// such that f returns false. It calls f repeatedly, with arbitrary -// values for each argument. If f returns false on a given input, +// such that f returns false. It calls f repeatedly, with arbitrary +// values for each argument. If f returns false on a given input, // Check returns that input as a *CheckError. // For example: // @@ -253,24 +253,21 @@ func (s *CheckEqualError) Error() string { // t.Error(err) // } // } -func Check(f interface{}, config *Config) (err error) { +func Check(f interface{}, config *Config) error { if config == nil { config = &defaultConfig } fVal, fType, ok := functionAndType(f) if !ok { - err = SetupError("argument is not a function") - return + return SetupError("argument is not a function") } if fType.NumOut() != 1 { - err = SetupError("function does not return one value") - return + return SetupError("function does not return one value") } if fType.Out(0).Kind() != reflect.Bool { - err = SetupError("function does not return a bool") - return + return SetupError("function does not return a bool") } arguments := make([]reflect.Value, fType.NumIn()) @@ -278,43 +275,39 @@ func Check(f interface{}, config *Config) (err error) { maxCount := config.getMaxCount() for i := 0; i < maxCount; i++ { - err = arbitraryValues(arguments, fType, config, rand) + err := arbitraryValues(arguments, fType, config, rand) if err != nil { - return + return err } if !fVal.Call(arguments)[0].Bool() { - err = &CheckError{i + 1, toInterfaces(arguments)} - return + return &CheckError{i + 1, toInterfaces(arguments)} } } - return + return nil } // CheckEqual looks for an input on which f and g return different results. // It calls f and g repeatedly with arbitrary values for each argument. // If f and g return different answers, CheckEqual returns a *CheckEqualError // describing the input and the outputs. -func CheckEqual(f, g interface{}, config *Config) (err error) { +func CheckEqual(f, g interface{}, config *Config) error { if config == nil { config = &defaultConfig } x, xType, ok := functionAndType(f) if !ok { - err = SetupError("f is not a function") - return + return SetupError("f is not a function") } y, yType, ok := functionAndType(g) if !ok { - err = SetupError("g is not a function") - return + return SetupError("g is not a function") } if xType != yType { - err = SetupError("functions have different types") - return + return SetupError("functions have different types") } arguments := make([]reflect.Value, xType.NumIn()) @@ -322,21 +315,20 @@ func CheckEqual(f, g interface{}, config *Config) (err error) { maxCount := config.getMaxCount() for i := 0; i < maxCount; i++ { - err = arbitraryValues(arguments, xType, config, rand) + err := arbitraryValues(arguments, xType, config, rand) if err != nil { - return + return err } xOut := toInterfaces(x.Call(arguments)) yOut := toInterfaces(y.Call(arguments)) if !reflect.DeepEqual(xOut, yOut) { - err = &CheckEqualError{CheckError{i + 1, toInterfaces(arguments)}, xOut, yOut} - return + return &CheckEqualError{CheckError{i + 1, toInterfaces(arguments)}, xOut, yOut} } } - return + return nil } // arbitraryValues writes Values to args such that args contains Values diff --git a/libgo/go/testing/sub_test.go b/libgo/go/testing/sub_test.go new file mode 100644 index 0000000..2a24aaa --- /dev/null +++ b/libgo/go/testing/sub_test.go @@ -0,0 +1,517 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package testing + +import ( + "bytes" + "regexp" + "strings" + "sync/atomic" + "time" +) + +func TestTestContext(t *T) { + const ( + add1 = 0 + done = 1 + ) + // After each of the calls are applied to the context, the + type call struct { + typ int // run or done + // result from applying the call + running int + waiting int + started bool + } + testCases := []struct { + max int + run []call + }{{ + max: 1, + run: []call{ + {typ: add1, running: 1, waiting: 0, started: true}, + {typ: done, running: 0, waiting: 0, started: false}, + }, + }, { + max: 1, + run: []call{ + {typ: add1, running: 1, waiting: 0, started: true}, + {typ: add1, running: 1, waiting: 1, started: false}, + {typ: done, running: 1, waiting: 0, started: true}, + {typ: done, running: 0, waiting: 0, started: false}, + {typ: add1, running: 1, waiting: 0, started: true}, + }, + }, { + max: 3, + run: []call{ + {typ: add1, running: 1, waiting: 0, started: true}, + {typ: add1, running: 2, waiting: 0, started: true}, + {typ: add1, running: 3, waiting: 0, started: true}, + {typ: add1, running: 3, waiting: 1, started: false}, + {typ: add1, running: 3, waiting: 2, started: false}, + {typ: add1, running: 3, waiting: 3, started: false}, + {typ: done, running: 3, waiting: 2, started: true}, + {typ: add1, running: 3, waiting: 3, started: false}, + {typ: done, running: 3, waiting: 2, started: true}, + {typ: done, running: 3, waiting: 1, started: true}, + {typ: done, running: 3, waiting: 0, started: true}, + {typ: done, running: 2, waiting: 0, started: false}, + {typ: done, running: 1, waiting: 0, started: false}, + {typ: done, running: 0, waiting: 0, started: false}, + }, + }} + for i, tc := range testCases { + ctx := &testContext{ + startParallel: make(chan bool), + maxParallel: tc.max, + } + for j, call := range tc.run { + doCall := func(f func()) chan bool { + done := make(chan bool) + go func() { + f() + done <- true + }() + return done + } + started := false + switch call.typ { + case add1: + signal := doCall(ctx.waitParallel) + select { + case <-signal: + started = true + case ctx.startParallel <- true: + <-signal + } + case done: + signal := doCall(ctx.release) + select { + case <-signal: + case <-ctx.startParallel: + started = true + <-signal + } + } + if started != call.started { + t.Errorf("%d:%d:started: got %v; want %v", i, j, started, call.started) + } + if ctx.running != call.running { + t.Errorf("%d:%d:running: got %v; want %v", i, j, ctx.running, call.running) + } + if ctx.numWaiting != call.waiting { + t.Errorf("%d:%d:waiting: got %v; want %v", i, j, ctx.numWaiting, call.waiting) + } + } + } +} + +func TestTRun(t *T) { + realTest := t + testCases := []struct { + desc string + ok bool + maxPar int + chatty bool + output string + f func(*T) + }{{ + desc: "failnow skips future sequential and parallel tests at same level", + ok: false, + maxPar: 1, + output: ` +--- FAIL: failnow skips future sequential and parallel tests at same level (N.NNs) + --- FAIL: failnow skips future sequential and parallel tests at same level/#00 (N.NNs) + `, + f: func(t *T) { + ranSeq := false + ranPar := false + t.Run("", func(t *T) { + t.Run("par", func(t *T) { + t.Parallel() + ranPar = true + }) + t.Run("seq", func(t *T) { + ranSeq = true + }) + t.FailNow() + t.Run("seq", func(t *T) { + realTest.Error("test must be skipped") + }) + t.Run("par", func(t *T) { + t.Parallel() + realTest.Error("test must be skipped.") + }) + }) + if !ranPar { + realTest.Error("parallel test was not run") + } + if !ranSeq { + realTest.Error("sequential test was not run") + } + }, + }, { + desc: "failure in parallel test propagates upwards", + ok: false, + maxPar: 1, + output: ` +--- FAIL: failure in parallel test propagates upwards (N.NNs) + --- FAIL: failure in parallel test propagates upwards/#00 (N.NNs) + --- FAIL: failure in parallel test propagates upwards/#00/par (N.NNs) + `, + f: func(t *T) { + t.Run("", func(t *T) { + t.Parallel() + t.Run("par", func(t *T) { + t.Parallel() + t.Fail() + }) + }) + }, + }, { + desc: "skipping without message, chatty", + ok: true, + chatty: true, + output: ` +=== RUN skipping without message, chatty +--- SKIP: skipping without message, chatty (N.NNs)`, + f: func(t *T) { t.SkipNow() }, + }, { + desc: "chatty with recursion", + ok: true, + chatty: true, + output: ` +=== RUN chatty with recursion +=== RUN chatty with recursion/#00 +=== RUN chatty with recursion/#00/#00 +--- PASS: chatty with recursion (N.NNs) + --- PASS: chatty with recursion/#00 (N.NNs) + --- PASS: chatty with recursion/#00/#00 (N.NNs)`, + f: func(t *T) { + t.Run("", func(t *T) { + t.Run("", func(t *T) {}) + }) + }, + }, { + desc: "skipping without message, not chatty", + ok: true, + f: func(t *T) { t.SkipNow() }, + }, { + desc: "skipping after error", + output: ` +--- FAIL: skipping after error (N.NNs) + sub_test.go:NNN: an error + sub_test.go:NNN: skipped`, + f: func(t *T) { + t.Error("an error") + t.Skip("skipped") + }, + }, { + desc: "use Run to locally synchronize parallelism", + ok: true, + maxPar: 1, + f: func(t *T) { + var count uint32 + t.Run("waitGroup", func(t *T) { + for i := 0; i < 4; i++ { + t.Run("par", func(t *T) { + t.Parallel() + atomic.AddUint32(&count, 1) + }) + } + }) + if count != 4 { + t.Errorf("count was %d; want 4", count) + } + }, + }, { + desc: "alternate sequential and parallel", + // Sequential tests should partake in the counting of running threads. + // Otherwise, if one runs parallel subtests in sequential tests that are + // itself subtests of parallel tests, the counts can get askew. + ok: true, + maxPar: 1, + f: func(t *T) { + t.Run("a", func(t *T) { + t.Parallel() + t.Run("b", func(t *T) { + // Sequential: ensure running count is decremented. + t.Run("c", func(t *T) { + t.Parallel() + }) + + }) + }) + }, + }, { + desc: "alternate sequential and parallel 2", + // Sequential tests should partake in the counting of running threads. + // Otherwise, if one runs parallel subtests in sequential tests that are + // itself subtests of parallel tests, the counts can get askew. + ok: true, + maxPar: 2, + f: func(t *T) { + for i := 0; i < 2; i++ { + t.Run("a", func(t *T) { + t.Parallel() + time.Sleep(time.Nanosecond) + for i := 0; i < 2; i++ { + t.Run("b", func(t *T) { + time.Sleep(time.Nanosecond) + for i := 0; i < 2; i++ { + t.Run("c", func(t *T) { + t.Parallel() + time.Sleep(time.Nanosecond) + }) + } + + }) + } + }) + } + }, + }, { + desc: "stress test", + ok: true, + maxPar: 4, + f: func(t *T) { + t.Parallel() + for i := 0; i < 12; i++ { + t.Run("a", func(t *T) { + t.Parallel() + time.Sleep(time.Nanosecond) + for i := 0; i < 12; i++ { + t.Run("b", func(t *T) { + time.Sleep(time.Nanosecond) + for i := 0; i < 12; i++ { + t.Run("c", func(t *T) { + t.Parallel() + time.Sleep(time.Nanosecond) + t.Run("d1", func(t *T) {}) + t.Run("d2", func(t *T) {}) + t.Run("d3", func(t *T) {}) + t.Run("d4", func(t *T) {}) + }) + } + }) + } + }) + } + }, + }, { + desc: "skip output", + ok: true, + maxPar: 4, + f: func(t *T) { + t.Skip() + }, + }, { + desc: "panic on goroutine fail after test exit", + ok: false, + maxPar: 4, + f: func(t *T) { + ch := make(chan bool) + t.Run("", func(t *T) { + go func() { + <-ch + defer func() { + if r := recover(); r == nil { + realTest.Errorf("expected panic") + } + ch <- true + }() + t.Errorf("failed after success") + }() + }) + ch <- true + <-ch + }, + }} + for _, tc := range testCases { + ctx := newTestContext(tc.maxPar, newMatcher(regexp.MatchString, "", "")) + buf := &bytes.Buffer{} + root := &T{ + common: common{ + signal: make(chan bool), + name: "Test", + w: buf, + chatty: tc.chatty, + }, + context: ctx, + } + ok := root.Run(tc.desc, tc.f) + ctx.release() + + if ok != tc.ok { + t.Errorf("%s:ok: got %v; want %v", tc.desc, ok, tc.ok) + } + if ok != !root.Failed() { + t.Errorf("%s:root failed: got %v; want %v", tc.desc, !ok, root.Failed()) + } + if ctx.running != 0 || ctx.numWaiting != 0 { + t.Errorf("%s:running and waiting non-zero: got %d and %d", tc.desc, ctx.running, ctx.numWaiting) + } + got := strings.TrimSpace(buf.String()) + want := strings.TrimSpace(tc.output) + re := makeRegexp(want) + if ok, err := regexp.MatchString(re, got); !ok || err != nil { + t.Errorf("%s:ouput:\ngot:\n%s\nwant:\n%s", tc.desc, got, want) + } + } +} + +func TestBRun(t *T) { + work := func(b *B) { + for i := 0; i < b.N; i++ { + time.Sleep(time.Nanosecond) + } + } + testCases := []struct { + desc string + failed bool + chatty bool + output string + f func(*B) + }{{ + desc: "simulate sequential run of subbenchmarks.", + f: func(b *B) { + b.Run("", func(b *B) { work(b) }) + time1 := b.result.NsPerOp() + b.Run("", func(b *B) { work(b) }) + time2 := b.result.NsPerOp() + if time1 >= time2 { + t.Errorf("no time spent in benchmark t1 >= t2 (%d >= %d)", time1, time2) + } + }, + }, { + desc: "bytes set by all benchmarks", + f: func(b *B) { + b.Run("", func(b *B) { b.SetBytes(10); work(b) }) + b.Run("", func(b *B) { b.SetBytes(10); work(b) }) + if b.result.Bytes != 20 { + t.Errorf("bytes: got: %d; want 20", b.result.Bytes) + } + }, + }, { + desc: "bytes set by some benchmarks", + // In this case the bytes result is meaningless, so it must be 0. + f: func(b *B) { + b.Run("", func(b *B) { b.SetBytes(10); work(b) }) + b.Run("", func(b *B) { work(b) }) + b.Run("", func(b *B) { b.SetBytes(10); work(b) }) + if b.result.Bytes != 0 { + t.Errorf("bytes: got: %d; want 0", b.result.Bytes) + } + }, + }, { + desc: "failure carried over to root", + failed: true, + output: "--- FAIL: root", + f: func(b *B) { b.Fail() }, + }, { + desc: "skipping without message, chatty", + chatty: true, + output: "--- SKIP: root", + f: func(b *B) { b.SkipNow() }, + }, { + desc: "skipping with message, chatty", + chatty: true, + output: ` +--- SKIP: root + sub_test.go:NNN: skipping`, + f: func(b *B) { b.Skip("skipping") }, + }, { + desc: "chatty with recursion", + chatty: true, + f: func(b *B) { + b.Run("", func(b *B) { + b.Run("", func(b *B) {}) + }) + }, + }, { + desc: "skipping without message, not chatty", + f: func(b *B) { b.SkipNow() }, + }, { + desc: "skipping after error", + failed: true, + output: ` +--- FAIL: root + sub_test.go:NNN: an error + sub_test.go:NNN: skipped`, + f: func(b *B) { + b.Error("an error") + b.Skip("skipped") + }, + }, { + desc: "memory allocation", + f: func(b *B) { + const bufSize = 256 + alloc := func(b *B) { + var buf [bufSize]byte + for i := 0; i < b.N; i++ { + _ = append([]byte(nil), buf[:]...) + } + } + b.Run("", func(b *B) { alloc(b) }) + b.Run("", func(b *B) { alloc(b) }) + // runtime.MemStats sometimes reports more allocations than the + // benchmark is responsible for. Luckily the point of this test is + // to ensure that the results are not underreported, so we can + // simply verify the lower bound. + if got := b.result.MemAllocs; got < 2 { + t.Errorf("MemAllocs was %v; want 2", got) + } + if got := b.result.MemBytes; got < 2*bufSize { + t.Errorf("MemBytes was %v; want %v", got, 2*bufSize) + } + }, + }} + for _, tc := range testCases { + var ok bool + buf := &bytes.Buffer{} + // This is almost like the Benchmark function, except that we override + // the benchtime and catch the failure result of the subbenchmark. + root := &B{ + common: common{ + signal: make(chan bool), + name: "root", + w: buf, + chatty: tc.chatty, + }, + benchFunc: func(b *B) { ok = b.Run("test", tc.f) }, // Use Run to catch failure. + benchTime: time.Microsecond, + } + root.runN(1) + if ok != !tc.failed { + t.Errorf("%s:ok: got %v; want %v", tc.desc, ok, !tc.failed) + } + if !ok != root.Failed() { + t.Errorf("%s:root failed: got %v; want %v", tc.desc, !ok, root.Failed()) + } + // All tests are run as subtests + if root.result.N != 1 { + t.Errorf("%s: N for parent benchmark was %d; want 1", tc.desc, root.result.N) + } + got := strings.TrimSpace(buf.String()) + want := strings.TrimSpace(tc.output) + re := makeRegexp(want) + if ok, err := regexp.MatchString(re, got); !ok || err != nil { + t.Errorf("%s:ouput:\ngot:\n%s\nwant:\n%s", tc.desc, got, want) + } + } +} + +func makeRegexp(s string) string { + s = strings.Replace(s, ":NNN:", `:\d\d\d:`, -1) + s = strings.Replace(s, "(N.NNs)", `\(\d*\.\d*s\)`, -1) + return s +} + +func TestBenchmarkOutput(t *T) { + // Ensure Benchmark initialized common.w by invoking it with an error and + // normal case. + Benchmark(func(b *B) { b.Error("do not print this output") }) + Benchmark(func(b *B) {}) +} 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 } diff --git a/libgo/go/testing/testing_test.go b/libgo/go/testing/testing_test.go index 87a5c16..45e4468 100644 --- a/libgo/go/testing/testing_test.go +++ b/libgo/go/testing/testing_test.go @@ -1,4 +1,4 @@ -// Copyright 2014 The Go Authors. All rights reserved. +// Copyright 2014 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. |