ch <- true
<-ch
},
+ }, {
+ desc: "log in finished sub test logs to parent",
+ ok: false,
+ output: `
+ --- FAIL: log in finished sub test logs to parent (N.NNs)
+ sub_test.go:NNN: message2
+ sub_test.go:NNN: message1
+ sub_test.go:NNN: error`,
+ maxPar: 1,
+ f: func(t *T) {
+ ch := make(chan bool)
+ t.Run("sub", func(t2 *T) {
+ go func() {
+ <-ch
+ t2.Log("message1")
+ ch <- true
+ }()
+ })
+ t.Log("message2")
+ ch <- true
+ <-ch
+ t.Errorf("error")
+ },
}}
for _, tc := range testCases {
ctx := newTestContext(tc.maxPar, newMatcher(regexp.MatchString, "", ""))
// decorate prefixes the string with the file and line of the call site
// and inserts the final newline if needed and indentation spaces for formatting.
// This function must be called with c.mu held.
-func (c *common) decorate(s string) string {
- frame := c.frameSkip(3) // decorate + log + public function.
+func (c *common) decorate(s string, skip int) string {
+ frame := c.frameSkip(skip)
file := frame.File
line := frame.Line
if file != "" {
// log generates the output. It's always at the same stack depth.
func (c *common) log(s string) {
+ c.logDepth(s, 3) // logDepth + log + public function
+}
+
+// logDepth generates the output. At an arbitary stack depth
+func (c *common) logDepth(s string, depth int) {
c.mu.Lock()
defer c.mu.Unlock()
- c.output = append(c.output, c.decorate(s)...)
+ // If this test has already finished try and log this message with our parent
+ // with this test name tagged so we know where it came from.
+ // If we don't have a parent panic.
+ if c.done {
+ if c.parent != nil {
+ c.parent.logDepth(s, depth+1)
+ } else {
+ panic("Log in goroutine after " + c.name + " has completed")
+ }
+ } else {
+ c.output = append(c.output, c.decorate(s, depth+1)...)
+ }
}
// Log formats its arguments using default formatting, analogous to Println,