mirror of https://github.com/golang/go.git
Revert "testing: add Output"
This reverts commit 8d189f188e.
Reason for revert: failing test
Change-Id: I951087eaef7818697acf87e3206003bcc8a81ee2
Reviewed-on: https://go-review.googlesource.com/c/go/+/672335
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
Auto-Submit: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
This commit is contained in:
parent
9bba799955
commit
afd6b0bc66
|
|
@ -1,3 +0,0 @@
|
||||||
pkg testing, method (*B) Output() io.Writer #59928
|
|
||||||
pkg testing, method (*F) Output() io.Writer #59928
|
|
||||||
pkg testing, method (*T) Output() io.Writer #59928
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
<!-- go.dev/issue/59928 -->
|
|
||||||
|
|
||||||
The new [Output] method of [testing.T], [testing.B] and [testing.F] provides a Writer
|
|
||||||
that writes to the same test output stream as [TB.Log], but omits the file and line number.
|
|
||||||
|
|
@ -755,7 +755,6 @@ func (s *benchState) processBench(b *B) {
|
||||||
benchFunc: b.benchFunc,
|
benchFunc: b.benchFunc,
|
||||||
benchTime: b.benchTime,
|
benchTime: b.benchTime,
|
||||||
}
|
}
|
||||||
b.setOutputWriter()
|
|
||||||
b.run1()
|
b.run1()
|
||||||
}
|
}
|
||||||
r := b.doBench()
|
r := b.doBench()
|
||||||
|
|
@ -832,7 +831,6 @@ func (b *B) Run(name string, f func(b *B)) bool {
|
||||||
benchTime: b.benchTime,
|
benchTime: b.benchTime,
|
||||||
bstate: b.bstate,
|
bstate: b.bstate,
|
||||||
}
|
}
|
||||||
sub.setOutputWriter()
|
|
||||||
if partial {
|
if partial {
|
||||||
// Partial name match, like -bench=X/Y matching BenchmarkX.
|
// Partial name match, like -bench=X/Y matching BenchmarkX.
|
||||||
// Only process sub-benchmarks, if any.
|
// Only process sub-benchmarks, if any.
|
||||||
|
|
@ -1009,7 +1007,6 @@ func Benchmark(f func(b *B)) BenchmarkResult {
|
||||||
benchFunc: f,
|
benchFunc: f,
|
||||||
benchTime: benchTime,
|
benchTime: benchTime,
|
||||||
}
|
}
|
||||||
b.setOutputWriter()
|
|
||||||
if b.run1() {
|
if b.run1() {
|
||||||
b.run()
|
b.run()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -320,7 +320,6 @@ func (f *F) Fuzz(ff any) {
|
||||||
t.parent.w = captureOut
|
t.parent.w = captureOut
|
||||||
}
|
}
|
||||||
t.w = indenter{&t.common}
|
t.w = indenter{&t.common}
|
||||||
t.setOutputWriter()
|
|
||||||
if t.chatty != nil {
|
if t.chatty != nil {
|
||||||
t.chatty.Updatef(t.name, "=== RUN %s\n", t.name)
|
t.chatty.Updatef(t.name, "=== RUN %s\n", t.name)
|
||||||
}
|
}
|
||||||
|
|
@ -530,7 +529,6 @@ func runFuzzTests(deps testDeps, fuzzTests []InternalFuzzTarget, deadline time.T
|
||||||
fstate: fstate,
|
fstate: fstate,
|
||||||
}
|
}
|
||||||
f.w = indenter{&f.common}
|
f.w = indenter{&f.common}
|
||||||
f.setOutputWriter()
|
|
||||||
if f.chatty != nil {
|
if f.chatty != nil {
|
||||||
f.chatty.Updatef(f.name, "=== RUN %s\n", f.name)
|
f.chatty.Updatef(f.name, "=== RUN %s\n", f.name)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,6 @@ func TestPanic(t *testing.T) {
|
||||||
want: `
|
want: `
|
||||||
--- FAIL: TestPanicHelper (N.NNs)
|
--- FAIL: TestPanicHelper (N.NNs)
|
||||||
panic_test.go:NNN: TestPanicHelper
|
panic_test.go:NNN: TestPanicHelper
|
||||||
TestPanicHelper
|
|
||||||
`,
|
`,
|
||||||
}, {
|
}, {
|
||||||
desc: "subtest panics",
|
desc: "subtest panics",
|
||||||
|
|
@ -42,10 +41,8 @@ func TestPanic(t *testing.T) {
|
||||||
want: `
|
want: `
|
||||||
--- FAIL: TestPanicHelper (N.NNs)
|
--- FAIL: TestPanicHelper (N.NNs)
|
||||||
panic_test.go:NNN: TestPanicHelper
|
panic_test.go:NNN: TestPanicHelper
|
||||||
TestPanicHelper
|
|
||||||
--- FAIL: TestPanicHelper/1 (N.NNs)
|
--- FAIL: TestPanicHelper/1 (N.NNs)
|
||||||
panic_test.go:NNN: TestPanicHelper/1
|
panic_test.go:NNN: TestPanicHelper/1
|
||||||
TestPanicHelper/1
|
|
||||||
`,
|
`,
|
||||||
}, {
|
}, {
|
||||||
desc: "subtest panics with cleanup",
|
desc: "subtest panics with cleanup",
|
||||||
|
|
@ -56,10 +53,8 @@ ran middle cleanup 1
|
||||||
ran outer cleanup
|
ran outer cleanup
|
||||||
--- FAIL: TestPanicHelper (N.NNs)
|
--- FAIL: TestPanicHelper (N.NNs)
|
||||||
panic_test.go:NNN: TestPanicHelper
|
panic_test.go:NNN: TestPanicHelper
|
||||||
TestPanicHelper
|
|
||||||
--- FAIL: TestPanicHelper/1 (N.NNs)
|
--- FAIL: TestPanicHelper/1 (N.NNs)
|
||||||
panic_test.go:NNN: TestPanicHelper/1
|
panic_test.go:NNN: TestPanicHelper/1
|
||||||
TestPanicHelper/1
|
|
||||||
`,
|
`,
|
||||||
}, {
|
}, {
|
||||||
desc: "subtest panics with outer cleanup panic",
|
desc: "subtest panics with outer cleanup panic",
|
||||||
|
|
@ -70,7 +65,6 @@ ran middle cleanup 1
|
||||||
ran outer cleanup
|
ran outer cleanup
|
||||||
--- FAIL: TestPanicHelper (N.NNs)
|
--- FAIL: TestPanicHelper (N.NNs)
|
||||||
panic_test.go:NNN: TestPanicHelper
|
panic_test.go:NNN: TestPanicHelper
|
||||||
TestPanicHelper
|
|
||||||
`,
|
`,
|
||||||
}, {
|
}, {
|
||||||
desc: "subtest panics with middle cleanup panic",
|
desc: "subtest panics with middle cleanup panic",
|
||||||
|
|
@ -81,10 +75,8 @@ ran middle cleanup 1
|
||||||
ran outer cleanup
|
ran outer cleanup
|
||||||
--- FAIL: TestPanicHelper (N.NNs)
|
--- FAIL: TestPanicHelper (N.NNs)
|
||||||
panic_test.go:NNN: TestPanicHelper
|
panic_test.go:NNN: TestPanicHelper
|
||||||
TestPanicHelper
|
|
||||||
--- FAIL: TestPanicHelper/1 (N.NNs)
|
--- FAIL: TestPanicHelper/1 (N.NNs)
|
||||||
panic_test.go:NNN: TestPanicHelper/1
|
panic_test.go:NNN: TestPanicHelper/1
|
||||||
TestPanicHelper/1
|
|
||||||
`,
|
`,
|
||||||
}, {
|
}, {
|
||||||
desc: "subtest panics with inner cleanup panic",
|
desc: "subtest panics with inner cleanup panic",
|
||||||
|
|
@ -95,10 +87,8 @@ ran middle cleanup 1
|
||||||
ran outer cleanup
|
ran outer cleanup
|
||||||
--- FAIL: TestPanicHelper (N.NNs)
|
--- FAIL: TestPanicHelper (N.NNs)
|
||||||
panic_test.go:NNN: TestPanicHelper
|
panic_test.go:NNN: TestPanicHelper
|
||||||
TestPanicHelper
|
|
||||||
--- FAIL: TestPanicHelper/1 (N.NNs)
|
--- FAIL: TestPanicHelper/1 (N.NNs)
|
||||||
panic_test.go:NNN: TestPanicHelper/1
|
panic_test.go:NNN: TestPanicHelper/1
|
||||||
TestPanicHelper/1
|
|
||||||
`,
|
`,
|
||||||
}, {
|
}, {
|
||||||
desc: "parallel subtest panics with cleanup",
|
desc: "parallel subtest panics with cleanup",
|
||||||
|
|
@ -109,10 +99,8 @@ ran middle cleanup 1
|
||||||
ran outer cleanup
|
ran outer cleanup
|
||||||
--- FAIL: TestPanicHelper (N.NNs)
|
--- FAIL: TestPanicHelper (N.NNs)
|
||||||
panic_test.go:NNN: TestPanicHelper
|
panic_test.go:NNN: TestPanicHelper
|
||||||
TestPanicHelper
|
|
||||||
--- FAIL: TestPanicHelper/1 (N.NNs)
|
--- FAIL: TestPanicHelper/1 (N.NNs)
|
||||||
panic_test.go:NNN: TestPanicHelper/1
|
panic_test.go:NNN: TestPanicHelper/1
|
||||||
TestPanicHelper/1
|
|
||||||
`,
|
`,
|
||||||
}, {
|
}, {
|
||||||
desc: "parallel subtest panics with outer cleanup panic",
|
desc: "parallel subtest panics with outer cleanup panic",
|
||||||
|
|
@ -123,7 +111,6 @@ ran middle cleanup 1
|
||||||
ran outer cleanup
|
ran outer cleanup
|
||||||
--- FAIL: TestPanicHelper (N.NNs)
|
--- FAIL: TestPanicHelper (N.NNs)
|
||||||
panic_test.go:NNN: TestPanicHelper
|
panic_test.go:NNN: TestPanicHelper
|
||||||
TestPanicHelper
|
|
||||||
`,
|
`,
|
||||||
}, {
|
}, {
|
||||||
desc: "parallel subtest panics with middle cleanup panic",
|
desc: "parallel subtest panics with middle cleanup panic",
|
||||||
|
|
@ -134,10 +121,8 @@ ran middle cleanup 1
|
||||||
ran outer cleanup
|
ran outer cleanup
|
||||||
--- FAIL: TestPanicHelper (N.NNs)
|
--- FAIL: TestPanicHelper (N.NNs)
|
||||||
panic_test.go:NNN: TestPanicHelper
|
panic_test.go:NNN: TestPanicHelper
|
||||||
TestPanicHelper
|
|
||||||
--- FAIL: TestPanicHelper/1 (N.NNs)
|
--- FAIL: TestPanicHelper/1 (N.NNs)
|
||||||
panic_test.go:NNN: TestPanicHelper/1
|
panic_test.go:NNN: TestPanicHelper/1
|
||||||
TestPanicHelper/1
|
|
||||||
`,
|
`,
|
||||||
}, {
|
}, {
|
||||||
desc: "parallel subtest panics with inner cleanup panic",
|
desc: "parallel subtest panics with inner cleanup panic",
|
||||||
|
|
@ -148,10 +133,8 @@ ran middle cleanup 1
|
||||||
ran outer cleanup
|
ran outer cleanup
|
||||||
--- FAIL: TestPanicHelper (N.NNs)
|
--- FAIL: TestPanicHelper (N.NNs)
|
||||||
panic_test.go:NNN: TestPanicHelper
|
panic_test.go:NNN: TestPanicHelper
|
||||||
TestPanicHelper
|
|
||||||
--- FAIL: TestPanicHelper/1 (N.NNs)
|
--- FAIL: TestPanicHelper/1 (N.NNs)
|
||||||
panic_test.go:NNN: TestPanicHelper/1
|
panic_test.go:NNN: TestPanicHelper/1
|
||||||
TestPanicHelper/1
|
|
||||||
`,
|
`,
|
||||||
}}
|
}}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
|
@ -182,7 +165,6 @@ func TestPanicHelper(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
t.Log(t.Name())
|
t.Log(t.Name())
|
||||||
t.Output().Write([]byte(t.Name()))
|
|
||||||
if t.Name() == *testPanicTest {
|
if t.Name() == *testPanicTest {
|
||||||
panic("panic")
|
panic("panic")
|
||||||
}
|
}
|
||||||
|
|
@ -213,7 +195,6 @@ func TestPanicHelper(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
}
|
}
|
||||||
t.Log(t.Name())
|
t.Log(t.Name())
|
||||||
t.Output().Write([]byte(t.Name()))
|
|
||||||
if chosen {
|
if chosen {
|
||||||
if *testPanicCleanup {
|
if *testPanicCleanup {
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
|
|
|
||||||
|
|
@ -503,100 +503,6 @@ func TestTRun(t *T) {
|
||||||
t2.FailNow()
|
t2.FailNow()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
}, {
|
|
||||||
desc: "buffered output gets flushed at test end",
|
|
||||||
ok: false,
|
|
||||||
output: `
|
|
||||||
--- FAIL: buffered output gets flushed at test end (0.00s)
|
|
||||||
--- FAIL: buffered output gets flushed at test end/#00 (0.00s)
|
|
||||||
a
|
|
||||||
b`,
|
|
||||||
f: func(t *T) {
|
|
||||||
t.Run("", func(t *T) {
|
|
||||||
o := t.Output()
|
|
||||||
o.Write([]byte("a\n"))
|
|
||||||
o.Write([]byte("b"))
|
|
||||||
t.Fail()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
desc: "output with chatty",
|
|
||||||
ok: true,
|
|
||||||
chatty: true,
|
|
||||||
output: `
|
|
||||||
=== RUN output with chatty
|
|
||||||
=== RUN output with chatty/#00
|
|
||||||
a
|
|
||||||
b
|
|
||||||
--- PASS: output with chatty (0.00s)
|
|
||||||
--- PASS: output with chatty/#00 (0.00s)`,
|
|
||||||
f: func(t *T) {
|
|
||||||
t.Run("", func(t *T) {
|
|
||||||
o := t.Output()
|
|
||||||
o.Write([]byte("a\n"))
|
|
||||||
o.Write([]byte("b"))
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
desc: "output with chatty and json",
|
|
||||||
ok: true,
|
|
||||||
chatty: true,
|
|
||||||
json: true,
|
|
||||||
output: `
|
|
||||||
^V=== RUN output with chatty and json
|
|
||||||
^V=== RUN output with chatty and json/#00
|
|
||||||
a
|
|
||||||
b
|
|
||||||
^V--- PASS: output with chatty and json/#00 (0.00s)
|
|
||||||
^V=== NAME output with chatty and json
|
|
||||||
^V--- PASS: output with chatty and json (0.00s)
|
|
||||||
^V=== NAME
|
|
||||||
`,
|
|
||||||
f: func(t *T) {
|
|
||||||
t.Run("", func(t *T) {
|
|
||||||
o := t.Output()
|
|
||||||
o.Write([]byte("a\n"))
|
|
||||||
o.Write([]byte("b"))
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
desc: "output in finished sub test outputs to parent",
|
|
||||||
ok: false,
|
|
||||||
output: `
|
|
||||||
--- FAIL: output in finished sub test outputs to parent (N.NNs)
|
|
||||||
message2
|
|
||||||
message1
|
|
||||||
sub_test.go:NNN: error`,
|
|
||||||
f: func(t *T) {
|
|
||||||
ch := make(chan bool)
|
|
||||||
t.Run("sub", func(t2 *T) {
|
|
||||||
go func() {
|
|
||||||
<-ch
|
|
||||||
t2.Output().Write([]byte("message1\n"))
|
|
||||||
ch <- true
|
|
||||||
}()
|
|
||||||
})
|
|
||||||
t.Output().Write([]byte("message2\n"))
|
|
||||||
ch <- true
|
|
||||||
<-ch
|
|
||||||
t.Errorf("error")
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
desc: "newline between buffered log and log",
|
|
||||||
ok: false,
|
|
||||||
output: `
|
|
||||||
--- FAIL: newline between buffered log and log (0.00s)
|
|
||||||
--- FAIL: newline between buffered log and log/#00 (0.00s)
|
|
||||||
buffered message
|
|
||||||
sub_test.go:NNN: log`,
|
|
||||||
f: func(t *T) {
|
|
||||||
t.Run("", func(t *T) {
|
|
||||||
o := t.Output()
|
|
||||||
o.Write([]byte("buffered message"))
|
|
||||||
t.Log("log")
|
|
||||||
t.Fail()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.desc, func(t *T) {
|
t.Run(tc.desc, func(t *T) {
|
||||||
|
|
@ -934,7 +840,7 @@ func TestLogAfterComplete(t *T) {
|
||||||
}
|
}
|
||||||
s, ok := p.(string)
|
s, ok := p.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
c2 <- fmt.Sprintf("subtest panic with unexpected value %v of type %T", p, p)
|
c2 <- fmt.Sprintf("subtest panic with unexpected value %v", p)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const want = "Log in goroutine after TestLateLog has completed: log after test"
|
const want = "Log in goroutine after TestLateLog has completed: log after test"
|
||||||
|
|
@ -1083,170 +989,3 @@ func TestNestedCleanup(t *T) {
|
||||||
t.Errorf("unexpected cleanup count: got %d want 3", ranCleanup)
|
t.Errorf("unexpected cleanup count: got %d want 3", ranCleanup)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestOutput checks that log messages are written,
|
|
||||||
// formatted and buffered as expected by Output. It
|
|
||||||
// checks both the chatty and non-chatty cases.
|
|
||||||
func TestOutput(t *T) {
|
|
||||||
tstate := newTestState(1, allMatcher())
|
|
||||||
root := &T{
|
|
||||||
tstate: tstate,
|
|
||||||
}
|
|
||||||
root.setOutputWriter()
|
|
||||||
o := root.Output()
|
|
||||||
|
|
||||||
// Chatty case
|
|
||||||
tstateChatty := newTestState(1, allMatcher())
|
|
||||||
bufChatty := &strings.Builder{}
|
|
||||||
rootChatty := &T{
|
|
||||||
common: common{
|
|
||||||
w: bufChatty,
|
|
||||||
},
|
|
||||||
tstate: tstateChatty,
|
|
||||||
}
|
|
||||||
rootChatty.setOutputWriter()
|
|
||||||
rootChatty.chatty = newChattyPrinter(rootChatty.w)
|
|
||||||
oChatty := rootChatty.Output()
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
in string
|
|
||||||
out string
|
|
||||||
buf string
|
|
||||||
}{{
|
|
||||||
in: "a",
|
|
||||||
out: "",
|
|
||||||
buf: "a",
|
|
||||||
}, {
|
|
||||||
in: "b",
|
|
||||||
out: "",
|
|
||||||
buf: "ab",
|
|
||||||
}, {
|
|
||||||
in: "\n",
|
|
||||||
out: " ab\n",
|
|
||||||
buf: "",
|
|
||||||
}, {
|
|
||||||
in: "\nc",
|
|
||||||
out: " ab\n \n",
|
|
||||||
buf: "c",
|
|
||||||
}, {
|
|
||||||
in: "d",
|
|
||||||
out: " ab\n \n",
|
|
||||||
buf: "cd",
|
|
||||||
}}
|
|
||||||
for _, tc := range testCases {
|
|
||||||
o.Write([]byte(tc.in))
|
|
||||||
if string(root.output) != tc.out {
|
|
||||||
t.Errorf("output:\ngot:\n%s\nwant:\n%s", root.output, tc.out)
|
|
||||||
}
|
|
||||||
if string(root.o.partial) != tc.buf {
|
|
||||||
t.Errorf("buffer:\ngot:\n%s\nwant:\n%s", root.o.partial, tc.buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chatty case
|
|
||||||
oChatty.Write([]byte(tc.in))
|
|
||||||
if got := bufChatty.String(); got != tc.out {
|
|
||||||
t.Errorf("output:\ngot:\n%s\nwant:\n%s", got, tc.out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestOutputAfterComplete ensures that Output panics
|
|
||||||
// if called after a test function returns.
|
|
||||||
func TestOutputAfterComplete(t *T) {
|
|
||||||
tstate := newTestState(1, allMatcher())
|
|
||||||
var buf bytes.Buffer
|
|
||||||
t1 := &T{
|
|
||||||
common: common{
|
|
||||||
// Use a buffered channel so that tRunner can write
|
|
||||||
// to it although nothing is reading from it.
|
|
||||||
signal: make(chan bool, 1),
|
|
||||||
w: &buf,
|
|
||||||
},
|
|
||||||
tstate: tstate,
|
|
||||||
}
|
|
||||||
|
|
||||||
c1 := make(chan bool)
|
|
||||||
c2 := make(chan string)
|
|
||||||
tRunner(t1, func(t *T) {
|
|
||||||
t.Run("TestLateOutput", func(t *T) {
|
|
||||||
go func() {
|
|
||||||
defer close(c2)
|
|
||||||
defer func() {
|
|
||||||
p := recover()
|
|
||||||
if p == nil {
|
|
||||||
c2 <- "subtest did not panic"
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s, ok := p.(string)
|
|
||||||
if !ok {
|
|
||||||
c2 <- fmt.Sprintf("subtest panic with unexpected value %v of type %T", p, p)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const want = "Output called after TestLateOutput has completed"
|
|
||||||
if !strings.Contains(s, want) {
|
|
||||||
c2 <- fmt.Sprintf("subtest panic %q does not contain %q", s, want)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
<-c1
|
|
||||||
t.Output()
|
|
||||||
}()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
close(c1)
|
|
||||||
|
|
||||||
if s := <-c2; s != "" {
|
|
||||||
t.Error(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestOutputWriteAfterComplete ensures that Write panics
|
|
||||||
// if called on t.Output() of a finished test t.
|
|
||||||
func TestOutputWriteAfterComplete(t *T) {
|
|
||||||
tstate := newTestState(1, allMatcher())
|
|
||||||
var buf bytes.Buffer
|
|
||||||
t1 := &T{
|
|
||||||
common: common{
|
|
||||||
// Use a buffered channel so that tRunner can write
|
|
||||||
// to it although nothing is reading from it.
|
|
||||||
signal: make(chan bool, 1),
|
|
||||||
w: &buf,
|
|
||||||
},
|
|
||||||
tstate: tstate,
|
|
||||||
}
|
|
||||||
|
|
||||||
c1 := make(chan bool)
|
|
||||||
c2 := make(chan string)
|
|
||||||
tRunner(t1, func(t *T) {
|
|
||||||
t.Run("TestLateWrite", func(t *T) {
|
|
||||||
o := t.Output()
|
|
||||||
go func() {
|
|
||||||
defer close(c2)
|
|
||||||
defer func() {
|
|
||||||
p := recover()
|
|
||||||
if p == nil {
|
|
||||||
c2 <- "subtest did not panic"
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s, ok := p.(string)
|
|
||||||
if !ok {
|
|
||||||
c2 <- fmt.Sprintf("subtest panic with unexpected value %v of type %T", p, p)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const want = "Write called after TestLateWrite has completed"
|
|
||||||
if !strings.Contains(s, want) {
|
|
||||||
c2 <- fmt.Sprintf("subtest panic %q does not contain %q", s, want)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
<-c1
|
|
||||||
o.Write([]byte("write after test"))
|
|
||||||
}()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
close(c1)
|
|
||||||
|
|
||||||
if s := <-c2; s != "" {
|
|
||||||
t.Error(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -631,7 +631,6 @@ type common struct {
|
||||||
mu sync.RWMutex // guards this group of fields
|
mu sync.RWMutex // guards this group of fields
|
||||||
output []byte // Output generated by test or benchmark.
|
output []byte // Output generated by test or benchmark.
|
||||||
w io.Writer // For flushToParent.
|
w io.Writer // For flushToParent.
|
||||||
o *outputWriter // Writes output.
|
|
||||||
ran bool // Test or benchmark (or one of its subtests) was executed.
|
ran bool // Test or benchmark (or one of its subtests) was executed.
|
||||||
failed bool // Test or benchmark has failed.
|
failed bool // Test or benchmark has failed.
|
||||||
skipped bool // Test or benchmark has been skipped.
|
skipped bool // Test or benchmark has been skipped.
|
||||||
|
|
@ -800,6 +799,44 @@ func (c *common) frameSkip(skip int) runtime.Frame {
|
||||||
return firstFrame
|
return firstFrame
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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, skip int) string {
|
||||||
|
frame := c.frameSkip(skip)
|
||||||
|
file := frame.File
|
||||||
|
line := frame.Line
|
||||||
|
if file != "" {
|
||||||
|
if *fullPath {
|
||||||
|
// If relative path, truncate file name at last file name separator.
|
||||||
|
} else if index := strings.LastIndexAny(file, `/\`); index >= 0 {
|
||||||
|
file = file[index+1:]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
file = "???"
|
||||||
|
}
|
||||||
|
if line == 0 {
|
||||||
|
line = 1
|
||||||
|
}
|
||||||
|
buf := new(strings.Builder)
|
||||||
|
// Every line is indented at least 4 spaces.
|
||||||
|
buf.WriteString(" ")
|
||||||
|
fmt.Fprintf(buf, "%s:%d: ", file, line)
|
||||||
|
lines := strings.Split(s, "\n")
|
||||||
|
if l := len(lines); l > 1 && lines[l-1] == "" {
|
||||||
|
lines = lines[:l-1]
|
||||||
|
}
|
||||||
|
for i, line := range lines {
|
||||||
|
if i > 0 {
|
||||||
|
// Second and subsequent lines are indented an additional 4 spaces.
|
||||||
|
buf.WriteString("\n ")
|
||||||
|
}
|
||||||
|
buf.WriteString(line)
|
||||||
|
}
|
||||||
|
buf.WriteByte('\n')
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
// flushToParent writes c.output to the parent after first writing the header
|
// flushToParent writes c.output to the parent after first writing the header
|
||||||
// with the given format and arguments.
|
// with the given format and arguments.
|
||||||
func (c *common) flushToParent(testName, format string, args ...any) {
|
func (c *common) flushToParent(testName, format string, args ...any) {
|
||||||
|
|
@ -845,8 +882,6 @@ type indenter struct {
|
||||||
c *common
|
c *common
|
||||||
}
|
}
|
||||||
|
|
||||||
const indent = " "
|
|
||||||
|
|
||||||
func (w indenter) Write(b []byte) (n int, err error) {
|
func (w indenter) Write(b []byte) (n int, err error) {
|
||||||
n = len(b)
|
n = len(b)
|
||||||
for len(b) > 0 {
|
for len(b) > 0 {
|
||||||
|
|
@ -863,6 +898,7 @@ func (w indenter) Write(b []byte) (n int, err error) {
|
||||||
w.c.output = append(w.c.output, marker)
|
w.c.output = append(w.c.output, marker)
|
||||||
line = line[1:]
|
line = line[1:]
|
||||||
}
|
}
|
||||||
|
const indent = " "
|
||||||
w.c.output = append(w.c.output, indent...)
|
w.c.output = append(w.c.output, indent...)
|
||||||
w.c.output = append(w.c.output, line...)
|
w.c.output = append(w.c.output, line...)
|
||||||
b = b[end:]
|
b = b[end:]
|
||||||
|
|
@ -1007,156 +1043,41 @@ func (c *common) FailNow() {
|
||||||
runtime.Goexit()
|
runtime.Goexit()
|
||||||
}
|
}
|
||||||
|
|
||||||
// log generates the output. It is always at the same stack depth. log inserts
|
// log generates the output. It's always at the same stack depth.
|
||||||
// indentation and the final newline if necessary. It prefixes the string
|
|
||||||
// with the file and line of the call site.
|
|
||||||
func (c *common) log(s string) {
|
func (c *common) log(s string) {
|
||||||
s = strings.TrimSuffix(s, "\n")
|
c.logDepth(s, 3) // logDepth + log + public function
|
||||||
|
|
||||||
// Second and subsequent lines are indented 4 spaces. This is in addition to
|
|
||||||
// the indentation provided by outputWriter.
|
|
||||||
s = strings.ReplaceAll(s, "\n", "\n"+indent)
|
|
||||||
s += "\n"
|
|
||||||
|
|
||||||
n := c.destination()
|
|
||||||
if n == nil {
|
|
||||||
// The test and all its parents are done. The log cannot be output.
|
|
||||||
panic("Log in goroutine after " + c.name + " has completed: " + s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prefix with the call site. It is located by skipping 3 functions:
|
|
||||||
// callSite + log + public function
|
|
||||||
s = n.callSite(3) + s
|
|
||||||
|
|
||||||
// Output buffered logs.
|
|
||||||
n.flushPartial()
|
|
||||||
|
|
||||||
n.o.Write([]byte(s))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// destination selects the test to which output should be appended. It returns the
|
// logDepth generates the output at an arbitrary stack depth.
|
||||||
// test if it is incomplete. Otherwise, it finds its closest incomplete parent.
|
func (c *common) logDepth(s string, depth int) {
|
||||||
func (c *common) destination() *common {
|
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
if c.done {
|
||||||
if !c.done {
|
// This test has already finished. Try and log this message
|
||||||
return c
|
// with our parent. If we don't have a parent, panic.
|
||||||
}
|
|
||||||
for parent := c.parent; parent != nil; parent = parent.parent {
|
for parent := c.parent; parent != nil; parent = parent.parent {
|
||||||
parent.mu.Lock()
|
parent.mu.Lock()
|
||||||
defer parent.mu.Unlock()
|
defer parent.mu.Unlock()
|
||||||
if !parent.done {
|
if !parent.done {
|
||||||
return parent
|
parent.output = append(parent.output, parent.decorate(s, depth+1)...)
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// callSite retrieves and formats the file and line of the call site.
|
|
||||||
func (c *common) callSite(skip int) string {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
frame := c.frameSkip(skip)
|
|
||||||
file := frame.File
|
|
||||||
line := frame.Line
|
|
||||||
if file != "" {
|
|
||||||
if *fullPath {
|
|
||||||
// If relative path, truncate file name at last file name separator.
|
|
||||||
} else {
|
|
||||||
file = filepath.Base(file)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
file = "???"
|
|
||||||
}
|
|
||||||
if line == 0 {
|
|
||||||
line = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("%s:%d: ", file, line)
|
|
||||||
}
|
|
||||||
|
|
||||||
// flushPartial checks the buffer for partial logs and outputs them.
|
|
||||||
func (c *common) flushPartial() {
|
|
||||||
partial := func() bool {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
return (c.o != nil) && (len(c.o.partial) > 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
if partial() {
|
|
||||||
c.o.Write([]byte("\n"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output returns a Writer that writes to the same test output stream as TB.Log.
|
|
||||||
// The output is indented like TB.Log lines, but Output does not
|
|
||||||
// add source locations or newlines. The output is internally line
|
|
||||||
// buffered, and a call to TB.Log or the end of the test will implicitly
|
|
||||||
// flush the buffer, followed by a newline. After a test function and all its
|
|
||||||
// parents return, neither Output nor the Write method may be called.
|
|
||||||
func (c *common) Output() io.Writer {
|
|
||||||
c.checkFuzzFn("Output")
|
|
||||||
n := c.destination()
|
|
||||||
if n == nil {
|
|
||||||
panic("Output called after " + c.name + " has completed")
|
|
||||||
}
|
|
||||||
return n.o
|
|
||||||
}
|
|
||||||
|
|
||||||
// setOutputWriter initializes an outputWriter and sets it as a common field.
|
|
||||||
func (c *common) setOutputWriter() {
|
|
||||||
c.o = &outputWriter{c: c}
|
|
||||||
}
|
|
||||||
|
|
||||||
// outputWriter buffers, formats and writes log messages.
|
|
||||||
type outputWriter struct {
|
|
||||||
c *common
|
|
||||||
partial []byte // incomplete ('\n'-free) suffix of last Write
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write writes a log message to the test's output stream, properly formatted and
|
|
||||||
// indented. It may not be called after a test function and all its parents return.
|
|
||||||
func (o *outputWriter) Write(p []byte) (int, error) {
|
|
||||||
if o.c.destination() == nil {
|
|
||||||
panic("Write called after " + o.c.name + " has completed")
|
|
||||||
}
|
|
||||||
|
|
||||||
o.c.mu.Lock()
|
|
||||||
defer o.c.mu.Unlock()
|
|
||||||
|
|
||||||
// The last element is a partial line.
|
|
||||||
lines := bytes.SplitAfter(p, []byte("\n"))
|
|
||||||
last := len(lines) - 1 // Inv: 0 <= last
|
|
||||||
for i, line := range lines[:last] {
|
|
||||||
// Emit partial line from previous call.
|
|
||||||
if i == 0 && len(o.partial) > 0 {
|
|
||||||
line = slices.Concat(o.partial, line)
|
|
||||||
o.partial = o.partial[:0]
|
|
||||||
}
|
|
||||||
o.writeLine(line)
|
|
||||||
}
|
|
||||||
// Save partial line for next call.
|
|
||||||
o.partial = append(o.partial, lines[last]...)
|
|
||||||
|
|
||||||
return len(p), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeLine generates the output for a given line.
|
|
||||||
func (o *outputWriter) writeLine(b []byte) {
|
|
||||||
if !o.c.done && (o.c.chatty != nil) {
|
|
||||||
if o.c.bench {
|
|
||||||
// Benchmarks don't print === CONT, so we should skip the test
|
|
||||||
// printer and just print straight to stdout.
|
|
||||||
fmt.Printf("%s%s", indent, b)
|
|
||||||
} else {
|
|
||||||
o.c.chatty.Printf(o.c.name, "%s%s", indent, b)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
o.c.output = append(o.c.output, indent...)
|
}
|
||||||
o.c.output = append(o.c.output, b...)
|
panic("Log in goroutine after " + c.name + " has completed: " + s)
|
||||||
|
} else {
|
||||||
|
if c.chatty != nil {
|
||||||
|
if c.bench {
|
||||||
|
// Benchmarks don't print === CONT, so we should skip the test
|
||||||
|
// printer and just print straight to stdout.
|
||||||
|
fmt.Print(c.decorate(s, depth+1))
|
||||||
|
} else {
|
||||||
|
c.chatty.Printf(c.name, "%s", c.decorate(s, depth+1))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.output = append(c.output, c.decorate(s, depth+1)...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log formats its arguments using default formatting, analogous to [fmt.Println],
|
// Log formats its arguments using default formatting, analogous to [fmt.Println],
|
||||||
|
|
@ -1818,8 +1739,6 @@ func tRunner(t *T, fn func(t *T)) {
|
||||||
root.duration += highPrecisionTimeSince(root.start)
|
root.duration += highPrecisionTimeSince(root.start)
|
||||||
d := root.duration
|
d := root.duration
|
||||||
root.mu.Unlock()
|
root.mu.Unlock()
|
||||||
// Output buffered logs.
|
|
||||||
root.flushPartial()
|
|
||||||
root.flushToParent(root.name, "--- FAIL: %s (%s)\n", root.name, fmtDuration(d))
|
root.flushToParent(root.name, "--- FAIL: %s (%s)\n", root.name, fmtDuration(d))
|
||||||
if r := root.parent.runCleanup(recoverAndReturnPanic); r != nil {
|
if r := root.parent.runCleanup(recoverAndReturnPanic); r != nil {
|
||||||
fmt.Fprintf(root.parent.w, "cleanup panicked with %v", r)
|
fmt.Fprintf(root.parent.w, "cleanup panicked with %v", r)
|
||||||
|
|
@ -1867,10 +1786,6 @@ func tRunner(t *T, fn func(t *T)) {
|
||||||
// test. See comment in Run method.
|
// test. See comment in Run method.
|
||||||
t.tstate.release()
|
t.tstate.release()
|
||||||
}
|
}
|
||||||
// Output buffered logs.
|
|
||||||
for root := &t.common; root.parent != nil; root = root.parent {
|
|
||||||
root.flushPartial()
|
|
||||||
}
|
|
||||||
t.report() // Report after all subtests have finished.
|
t.report() // Report after all subtests have finished.
|
||||||
|
|
||||||
// Do not lock t.done to allow race detector to detect race in case
|
// Do not lock t.done to allow race detector to detect race in case
|
||||||
|
|
@ -1936,7 +1851,6 @@ func (t *T) Run(name string, f func(t *T)) bool {
|
||||||
tstate: t.tstate,
|
tstate: t.tstate,
|
||||||
}
|
}
|
||||||
t.w = indenter{&t.common}
|
t.w = indenter{&t.common}
|
||||||
t.setOutputWriter()
|
|
||||||
|
|
||||||
if t.chatty != nil {
|
if t.chatty != nil {
|
||||||
t.chatty.Updatef(t.name, "=== RUN %s\n", t.name)
|
t.chatty.Updatef(t.name, "=== RUN %s\n", t.name)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue