]> Cypherpunks repositories - gostls13.git/commitdiff
testing: finish implementation of subtests
authorMarcel van Lohuizen <mpvl@golang.org>
Mon, 25 Jan 2016 15:27:23 +0000 (16:27 +0100)
committerMarcel van Lohuizen <mpvl@golang.org>
Fri, 18 Mar 2016 12:30:39 +0000 (12:30 +0000)
API not exposed yet.

Change-Id: Iaba0adc0fa1ae8075e6b56796f99ee8db9177a78
Reviewed-on: https://go-review.googlesource.com/18896
Reviewed-by: Russ Cox <rsc@golang.org>
src/testing/sub_test.go
src/testing/testing.go

index c98ce58307e3848c6466d5f68286ce66ad947fa9..f9c3f4176abd1c2461546f924b406f25acae5828 100644 (file)
@@ -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) }
 
index 13739ccd9d2331b6e782ec423cbd9972fc8507c4..0aa60d9ddc6c5994b3be77c0584440dc42d65d9a 100644 (file)
@@ -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)