f: func(t *T) {
t.Skip()
},
+ }, {
+ desc: "panic on goroutine fail after test exit",
+ ok: false,
+ maxPar: 4,
+ f: func(t *T) {
+ ch := make(chan bool)
+ t.Run("", func(t *T) {
+ go func() {
+ <-ch
+ defer func() {
+ if r := recover(); r == nil {
+ realTest.Errorf("expected panic")
+ }
+ ch <- true
+ }()
+ t.Errorf("failed after success")
+ }()
+ })
+ ch <- true
+ <-ch
+ },
}}
for _, tc := range testCases {
ctx := newTestContext(tc.maxPar, newMatcher(regexp.MatchString, "", ""))
// common holds the elements common between T and B and
// captures common methods such as Errorf.
type common struct {
- mu sync.RWMutex // guards output and failed
+ mu sync.RWMutex // guards output, failed, and done.
output []byte // Output generated by test or benchmark.
w io.Writer // For flushToParent.
chatty bool // A copy of the chatty flag.
failed bool // Test or benchmark has failed.
skipped bool // Test of benchmark has been skipped.
- finished bool
+ finished bool // Test function has completed.
+ done bool // Test is finished and all subtests have completed.
parent *common
level int // Nesting depth of test or benchmark.
}
c.mu.Lock()
defer c.mu.Unlock()
+ // c.done needs to be locked to synchronize checks to c.done in parent tests.
+ if c.done {
+ panic("Fail in goroutine after " + c.name + " has completed")
+ }
c.failed = true
}
}
t.report() // Report after all subtests have finished.
+ // Do not lock t.done to allow race detector to detect race in case
+ // the user does not appropriately synchronizes a goroutine.
+ t.done = true
t.signal <- true
}()