mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
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>
This commit is contained in:
parent
1857bfca13
commit
2330ae8cf8
2 changed files with 249 additions and 0 deletions
|
|
@ -5,6 +5,8 @@
|
||||||
package testing
|
package testing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"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
|
// 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 (b *B) Run(name string, f func(b *B)) bool { return b.runBench(name, f) }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -273,6 +273,29 @@ func (c *common) flushToParent(format string, args ...interface{}) {
|
||||||
c.output = c.output[:0]
|
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".
|
// fmtDuration returns a string representing d in the form "87.00s".
|
||||||
func fmtDuration(d time.Duration) string {
|
func fmtDuration(d time.Duration) string {
|
||||||
return fmt.Sprintf("%.2fs", d.Seconds())
|
return fmt.Sprintf("%.2fs", d.Seconds())
|
||||||
|
|
@ -542,6 +565,7 @@ func (t *T) run(name string, f func(t *T)) bool {
|
||||||
},
|
},
|
||||||
context: t.context,
|
context: t.context,
|
||||||
}
|
}
|
||||||
|
t.w = indenter{&t.common}
|
||||||
|
|
||||||
if *chatty {
|
if *chatty {
|
||||||
fmt.Printf("=== RUN %s\n", t.name)
|
fmt.Printf("=== RUN %s\n", t.name)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue