From: Marcel van Lohuizen Date: Mon, 25 Jan 2016 15:27:23 +0000 (+0100) Subject: testing: finish implementation of subtests X-Git-Tag: go1.7beta1~1234 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=2330ae8cf80e4fa5e6e2909e0c8562fd3d9beec6;p=gostls13.git testing: finish implementation of subtests API not exposed yet. Change-Id: Iaba0adc0fa1ae8075e6b56796f99ee8db9177a78 Reviewed-on: https://go-review.googlesource.com/18896 Reviewed-by: Russ Cox --- diff --git a/src/testing/sub_test.go b/src/testing/sub_test.go index c98ce58307..f9c3f4176a 100644 --- a/src/testing/sub_test.go +++ b/src/testing/sub_test.go @@ -5,6 +5,8 @@ package testing import ( + "io/ioutil" + "sync/atomic" "time" ) @@ -104,6 +106,229 @@ func TestTestContext(t *T) { } } +// TODO: remove this stub when API is exposed +func (t *T) Run(name string, f func(t *T)) bool { return t.run(name, f) } + +func TestTRun(t *T) { + realTest := t + testCases := []struct { + desc string + ok bool + maxPar int + f func(*T) + }{{ + desc: "failnow skips future sequential and parallel tests at same level", + ok: false, + maxPar: 1, + 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, + f: func(t *T) { + t.Run("", func(t *T) { + t.Parallel() + t.Run("par", func(t *T) { + t.Parallel() + t.Fail() + }) + }) + }, + }, { + 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: "run no more than *parallel tests concurrently", + ok: true, + maxPar: 4, + f: func(t *T) { + max := 0 + in := make(chan int) + out := make(chan int) + ctx := t.context + t.Run("wait", func(t *T) { + t.Run("controller", func(t *T) { + // Verify sequential tests don't skew counts. + t.Run("seq1", func(t *T) {}) + t.Run("seq2", func(t *T) {}) + t.Run("seq3", func(t *T) {}) + t.Parallel() + for i := 0; i < 80; i++ { + ctx.mu.Lock() + if ctx.running > max { + max = ctx.running + } + ctx.mu.Unlock() + <-in + // force a minimum to avoid a race, although it works + // without it. + if i >= ctx.maxParallel-2 { // max - this - 1 + out <- i + } + } + close(out) + }) + // Ensure we don't exceed the maximum even with nested parallelism. + for i := 0; i < 2; i++ { + t.Run("", func(t *T) { + t.Parallel() + for j := 0; j < 40; j++ { + t.Run("", func(t *T) { + t.Run("seq1", func(t *T) {}) + t.Run("seq2", func(t *T) {}) + t.Parallel() + in <- j + <-out + }) + } + }) + } + }) + if max != ctx.maxParallel { + realTest.Errorf("max: got %d; want: %d", max, ctx.maxParallel) + } + }, + }, { + 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", + // 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) {}) + }) + } + + }) + } + }) + } + }, + }} + for _, tc := range testCases { + ctx := newTestContext(tc.maxPar) + root := &T{ + common: common{ + barrier: make(chan bool), + w: ioutil.Discard, + }, + 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) + } + } +} + // TODO: remove this stub when API is exposed func (b *B) Run(name string, f func(b *B)) bool { return b.runBench(name, f) } diff --git a/src/testing/testing.go b/src/testing/testing.go index 13739ccd9d..0aa60d9ddc 100644 --- a/src/testing/testing.go +++ b/src/testing/testing.go @@ -273,6 +273,29 @@ func (c *common) flushToParent(format string, args ...interface{}) { 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()) @@ -542,6 +565,7 @@ func (t *T) run(name string, f func(t *T)) bool { }, context: t.context, } + t.w = indenter{&t.common} if *chatty { fmt.Printf("=== RUN %s\n", t.name)