mirror of
https://github.com/golang/go
synced 2024-11-17 16:54:44 -07:00
testing: use subprocesses in TestTBHelper and TestTBHelperParallel
These tests are checking the output of test functions that call the Helper methods. However, they were reaching into package internals instead of running those test functions as actual tests. That not only produced significant differences in formatting (such as indentation for subtests), but also caused test flags such as "-failfast" passed for the overall test run to interfere with the output formatting. Now, we run the test functions as real tests in a subprocess, so that we get the real output and formatting of those tests. This makes the tests not only more realistic, but also less sensitive to otherwise-irrelevant implementation details (such as the names and signatures of unexported types and functions in the testing package). Fixes #61016. Change-Id: I646fbbd7cfeb00382054677f726c05fc9d35d0dc Reviewed-on: https://go-review.googlesource.com/c/go/+/506955 Auto-Submit: Bryan Mills <bcmills@google.com> Reviewed-by: Alan Donovan <adonovan@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
parent
bbcd85528c
commit
0cb45bac01
@ -2,98 +2,107 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package testing
|
||||
package testing_test
|
||||
|
||||
import (
|
||||
"internal/testenv"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTBHelper(t *T) {
|
||||
var buf strings.Builder
|
||||
ctx := newTestContext(1, allMatcher())
|
||||
t1 := &T{
|
||||
common: common{
|
||||
signal: make(chan bool),
|
||||
w: &buf,
|
||||
},
|
||||
context: ctx,
|
||||
}
|
||||
t1.Run("Test", testHelper)
|
||||
func TestTBHelper(t *testing.T) {
|
||||
if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" {
|
||||
testTestHelper(t)
|
||||
|
||||
want := `--- FAIL: Test (?s)
|
||||
helperfuncs_test.go:12: 0
|
||||
helperfuncs_test.go:40: 1
|
||||
helperfuncs_test.go:21: 2
|
||||
helperfuncs_test.go:42: 3
|
||||
helperfuncs_test.go:49: 4
|
||||
--- FAIL: Test/sub (?s)
|
||||
helperfuncs_test.go:52: 5
|
||||
helperfuncs_test.go:21: 6
|
||||
helperfuncs_test.go:51: 7
|
||||
helperfuncs_test.go:63: 8
|
||||
--- FAIL: Test/sub2 (?s)
|
||||
helperfuncs_test.go:78: 11
|
||||
helperfuncs_test.go:82: recover 12
|
||||
helperfuncs_test.go:84: GenericFloat64
|
||||
helperfuncs_test.go:85: GenericInt
|
||||
helperfuncs_test.go:71: 9
|
||||
helperfuncs_test.go:67: 10
|
||||
// Check that calling Helper from inside a top-level test function
|
||||
// has no effect.
|
||||
t.Helper()
|
||||
t.Error("8")
|
||||
return
|
||||
}
|
||||
|
||||
testenv.MustHaveExec(t)
|
||||
t.Parallel()
|
||||
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cmd := testenv.Command(t, exe, "-test.run=^TestTBHelper$")
|
||||
cmd = testenv.CleanCmdEnv(cmd)
|
||||
cmd.Env = append(cmd.Env, "GO_WANT_HELPER_PROCESS=1")
|
||||
out, _ := cmd.CombinedOutput()
|
||||
|
||||
want := `--- FAIL: TestTBHelper \([^)]+\)
|
||||
helperfuncs_test.go:15: 0
|
||||
helperfuncs_test.go:47: 1
|
||||
helperfuncs_test.go:24: 2
|
||||
helperfuncs_test.go:49: 3
|
||||
helperfuncs_test.go:56: 4
|
||||
--- FAIL: TestTBHelper/sub \([^)]+\)
|
||||
helperfuncs_test.go:59: 5
|
||||
helperfuncs_test.go:24: 6
|
||||
helperfuncs_test.go:58: 7
|
||||
--- FAIL: TestTBHelper/sub2 \([^)]+\)
|
||||
helperfuncs_test.go:80: 11
|
||||
helperfuncs_test.go:84: recover 12
|
||||
helperfuncs_test.go:86: GenericFloat64
|
||||
helperfuncs_test.go:87: GenericInt
|
||||
helper_test.go:22: 8
|
||||
helperfuncs_test.go:73: 9
|
||||
helperfuncs_test.go:69: 10
|
||||
`
|
||||
lines := strings.Split(buf.String(), "\n")
|
||||
durationRE := regexp.MustCompile(`\(.*\)$`)
|
||||
for i, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
line = durationRE.ReplaceAllString(line, "(?s)")
|
||||
lines[i] = line
|
||||
}
|
||||
got := strings.Join(lines, "\n")
|
||||
if got != want {
|
||||
t.Errorf("got output:\n\n%s\nwant:\n\n%s", got, want)
|
||||
if !regexp.MustCompile(want).Match(out) {
|
||||
t.Errorf("got output:\n\n%s\nwant matching:\n\n%s", out, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTBHelperParallel(t *T) {
|
||||
var buf strings.Builder
|
||||
ctx := newTestContext(1, newMatcher(regexp.MatchString, "", "", ""))
|
||||
t1 := &T{
|
||||
common: common{
|
||||
signal: make(chan bool),
|
||||
w: &buf,
|
||||
},
|
||||
context: ctx,
|
||||
func TestTBHelperParallel(t *testing.T) {
|
||||
if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" {
|
||||
parallelTestHelper(t)
|
||||
return
|
||||
}
|
||||
t1.Run("Test", parallelTestHelper)
|
||||
|
||||
lines := strings.Split(strings.TrimSpace(buf.String()), "\n")
|
||||
if len(lines) != 6 {
|
||||
t.Fatalf("parallelTestHelper gave %d lines of output; want 6", len(lines))
|
||||
testenv.MustHaveExec(t)
|
||||
t.Parallel()
|
||||
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := "helperfuncs_test.go:21: parallel"
|
||||
|
||||
cmd := testenv.Command(t, exe, "-test.run=^TestTBHelperParallel$")
|
||||
cmd = testenv.CleanCmdEnv(cmd)
|
||||
cmd.Env = append(cmd.Env, "GO_WANT_HELPER_PROCESS=1")
|
||||
out, _ := cmd.CombinedOutput()
|
||||
|
||||
t.Logf("output:\n%s", out)
|
||||
|
||||
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
|
||||
|
||||
// We expect to see one "--- FAIL" line at the start
|
||||
// of the log, five lines of "parallel" logging,
|
||||
// and a final "FAIL" line at the end of the test.
|
||||
const wantLines = 7
|
||||
|
||||
if len(lines) != wantLines {
|
||||
t.Fatalf("parallelTestHelper gave %d lines of output; want %d", len(lines), wantLines)
|
||||
}
|
||||
want := "helperfuncs_test.go:24: parallel"
|
||||
if got := strings.TrimSpace(lines[1]); got != want {
|
||||
t.Errorf("got output line %q; want %q", got, want)
|
||||
t.Errorf("got second output line %q; want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
type noopWriter int
|
||||
|
||||
func (nw *noopWriter) Write(b []byte) (int, error) { return len(b), nil }
|
||||
|
||||
func BenchmarkTBHelper(b *B) {
|
||||
w := noopWriter(0)
|
||||
ctx := newTestContext(1, allMatcher())
|
||||
t1 := &T{
|
||||
common: common{
|
||||
signal: make(chan bool),
|
||||
w: &w,
|
||||
},
|
||||
context: ctx,
|
||||
}
|
||||
func BenchmarkTBHelper(b *testing.B) {
|
||||
f1 := func() {
|
||||
t1.Helper()
|
||||
b.Helper()
|
||||
}
|
||||
f2 := func() {
|
||||
t1.Helper()
|
||||
b.Helper()
|
||||
}
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
@ -2,38 +2,45 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package testing
|
||||
package testing_test
|
||||
|
||||
import "sync"
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// The line numbering of this file is important for TestTBHelper.
|
||||
|
||||
func notHelper(t *T, msg string) {
|
||||
func notHelper(t *testing.T, msg string) {
|
||||
t.Error(msg)
|
||||
}
|
||||
|
||||
func helper(t *T, msg string) {
|
||||
func helper(t *testing.T, msg string) {
|
||||
t.Helper()
|
||||
t.Error(msg)
|
||||
}
|
||||
|
||||
func notHelperCallingHelper(t *T, msg string) {
|
||||
func notHelperCallingHelper(t *testing.T, msg string) {
|
||||
helper(t, msg)
|
||||
}
|
||||
|
||||
func helperCallingHelper(t *T, msg string) {
|
||||
func helperCallingHelper(t *testing.T, msg string) {
|
||||
t.Helper()
|
||||
helper(t, msg)
|
||||
}
|
||||
|
||||
func genericHelper[G any](t *T, msg string) {
|
||||
func genericHelper[G any](t *testing.T, msg string) {
|
||||
t.Helper()
|
||||
t.Error(msg)
|
||||
}
|
||||
|
||||
var genericIntHelper = genericHelper[int]
|
||||
|
||||
func testHelper(t *T) {
|
||||
func testTestHelper(t *testing.T) {
|
||||
testHelper(t)
|
||||
}
|
||||
|
||||
func testHelper(t *testing.T) {
|
||||
// Check combinations of directly and indirectly
|
||||
// calling helper functions.
|
||||
notHelper(t, "0")
|
||||
@ -48,7 +55,7 @@ func testHelper(t *T) {
|
||||
}
|
||||
fn("4")
|
||||
|
||||
t.Run("sub", func(t *T) {
|
||||
t.Run("sub", func(t *testing.T) {
|
||||
helper(t, "5")
|
||||
notHelperCallingHelper(t, "6")
|
||||
// Check that calling Helper from inside a subtest entry function
|
||||
@ -57,11 +64,6 @@ func testHelper(t *T) {
|
||||
t.Error("7")
|
||||
})
|
||||
|
||||
// Check that calling Helper from inside a top-level test function
|
||||
// has no effect.
|
||||
t.Helper()
|
||||
t.Error("8")
|
||||
|
||||
// Check that right caller is reported for func passed to Cleanup when
|
||||
// multiple cleanup functions have been registered.
|
||||
t.Cleanup(func() {
|
||||
@ -85,7 +87,7 @@ func testHelper(t *T) {
|
||||
genericIntHelper(t, "GenericInt")
|
||||
}
|
||||
|
||||
func parallelTestHelper(t *T) {
|
||||
func parallelTestHelper(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 5; i++ {
|
||||
wg.Add(1)
|
||||
@ -97,15 +99,15 @@ func parallelTestHelper(t *T) {
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func helperSubCallingHelper(t *T, msg string) {
|
||||
func helperSubCallingHelper(t *testing.T, msg string) {
|
||||
t.Helper()
|
||||
t.Run("sub2", func(t *T) {
|
||||
t.Run("sub2", func(t *testing.T) {
|
||||
t.Helper()
|
||||
t.Fatal(msg)
|
||||
})
|
||||
}
|
||||
|
||||
func recoverHelper(t *T, msg string) {
|
||||
func recoverHelper(t *testing.T, msg string) {
|
||||
t.Helper()
|
||||
defer func() {
|
||||
t.Helper()
|
||||
@ -116,7 +118,7 @@ func recoverHelper(t *T, msg string) {
|
||||
doPanic(t, msg)
|
||||
}
|
||||
|
||||
func doPanic(t *T, msg string) {
|
||||
func doPanic(t *testing.T, msg string) {
|
||||
t.Helper()
|
||||
panic(msg)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user