package testing
import (
+ "io/ioutil"
+ "sync/atomic"
"time"
)
}
}
+// 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) }