diff options
| author | Marcel van Lohuizen <mpvl@golang.org> | 2016-01-25 16:27:23 +0100 |
|---|---|---|
| committer | Marcel van Lohuizen <mpvl@golang.org> | 2016-03-18 12:30:39 +0000 |
| commit | 2330ae8cf80e4fa5e6e2909e0c8562fd3d9beec6 (patch) | |
| tree | e3ba4059278342c56806d26f27343209686b12dd /src/testing/sub_test.go | |
| parent | 1857bfca134261ab2e0fc1adcf6a974f550d430a (diff) | |
| download | go-2330ae8cf80e4fa5e6e2909e0c8562fd3d9beec6.tar.xz | |
testing: finish implementation of subtests
API not exposed yet.
Change-Id: Iaba0adc0fa1ae8075e6b56796f99ee8db9177a78
Reviewed-on: https://go-review.googlesource.com/18896
Reviewed-by: Russ Cox <rsc@golang.org>
Diffstat (limited to 'src/testing/sub_test.go')
| -rw-r--r-- | src/testing/sub_test.go | 225 |
1 files changed, 225 insertions, 0 deletions
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" ) @@ -105,6 +107,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) } func TestBRun(t *T) { |
