cmd/dist: convert host tests to use goTest

This adds support for host tests to goTest and registerTest and
modifies all uses of registerHostTest to use goTest and registerTest.

This eliminates the last case where go test command lines are
constructed by hand. Next we'll clean up all of the infrastructure
support for that.

I traced all exec calls from cmd/dist on linux/amd64 and this makes
only no-op changes (such as re-arranging the order of flags).

Preparation for #37486.

Change-Id: Icb7ec8efdac72bdb819ae24b2f585375d9d9d5b9
Reviewed-on: https://go-review.googlesource.com/c/go/+/450019
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Run-TryBot: Austin Clements <austin@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
This commit is contained in:
Austin Clements 2022-11-10 13:54:25 -05:00
parent 898d000c39
commit bf5b587089
1 changed files with 101 additions and 54 deletions

155
src/cmd/dist/test.go vendored
View File

@ -69,6 +69,9 @@ type tester struct {
cgoEnabled bool
partial bool
goExe string // For host tests
goTmpDir string // For host tests
tests []distTest
timeoutScale int
@ -97,13 +100,20 @@ func (t *tester) run() {
os.Setenv("PATH", fmt.Sprintf("%s%c%s", gorootBin, os.PathListSeparator, os.Getenv("PATH")))
cmd := exec.Command(gorootBinGo, "env", "CGO_ENABLED")
cmd := exec.Command(gorootBinGo, "env", "CGO_ENABLED", "GOEXE", "GOTMPDIR")
cmd.Stderr = new(bytes.Buffer)
slurp, err := cmd.Output()
if err != nil {
fatalf("Error running go env CGO_ENABLED: %v\n%s", err, cmd.Stderr)
fatalf("Error running %s: %v\n%s", cmd, err, cmd.Stderr)
}
t.cgoEnabled, _ = strconv.ParseBool(strings.TrimSpace(string(slurp)))
parts := strings.Split(string(slurp), "\n")
if len(parts) < 3 {
fatalf("Error running %s: output contains <3 lines\n%s", cmd, cmd.Stderr)
}
t.cgoEnabled, _ = strconv.ParseBool(parts[0])
t.goExe = parts[1]
t.goTmpDir = parts[2]
if flag.NArg() > 0 && t.runRxStr != "" {
fatalf("the -run regular expression flag is mutually exclusive with test name arguments")
}
@ -384,6 +394,73 @@ func (opts *goTest) run(t *tester) error {
return opts.command(t).Run()
}
// runHostTest runs a test that should be built and run on the host GOOS/GOARCH,
// but run with GOOS/GOARCH set to the target GOOS/GOARCH. This is for tests
// that do nothing but compile and run other binaries. If the host and target
// are different, then the assumption is that the target is running in an
// emulator and does not have a Go toolchain at all, so the test needs to run on
// the host, but its resulting binaries will be run through a go_exec wrapper
// that runs them on the target.
func (opts *goTest) runHostTest(t *tester) error {
goCmd, build, run, pkgs, setupCmd := opts.buildArgs(t)
// Build the host test binary
if len(pkgs) != 1 {
// We can't compile more than one package.
panic("host tests must have a single test package")
}
if len(opts.env) != 0 {
// It's not clear if these are for the host or the target.
panic("host tests must not have environment variables")
}
f, err := os.CreateTemp(t.goTmpDir, "test.test-*"+t.goExe)
if err != nil {
fatalf("failed to create temporary file: %s", err)
}
bin := f.Name()
f.Close()
xatexit(func() { os.Remove(bin) })
args := append([]string{"test", "-c", "-o", bin}, build...)
args = append(args, pkgs...)
cmd := exec.Command(goCmd, args...)
setupCmd(cmd)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
setEnv(cmd, "GOARCH", gohostarch)
setEnv(cmd, "GOOS", gohostos)
if vflag > 1 {
errprintf("%s\n", cmd)
}
if err := cmd.Run(); err != nil {
return err
}
if t.compileOnly {
return nil
}
// Transform run flags to be passed directly to a test binary.
for i, f := range run {
if !strings.HasPrefix(f, "-") {
panic("run flag does not start with -: " + f)
}
run[i] = "-test." + f[1:]
}
// Run the test
args = append(run, opts.testFlags...)
cmd = exec.Command(bin, args...)
setupCmd(cmd)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if vflag > 1 {
errprintf("%s\n", cmd)
}
return cmd.Run()
}
// buildArgs is in internal helper for goTest that constructs the elements of
// the "go test" command line. goCmd is the path to the go command to use. build
// is the flags for building the test. run is the flags for running the test.
@ -834,10 +911,10 @@ func (t *tester) registerTests() {
if t.cgoEnabled && !t.iOS() {
// Disabled on iOS. golang.org/issue/15919
t.registerHostTest("cgo_stdio", "../misc/cgo/stdio", "misc/cgo/stdio", ".")
t.registerHostTest("cgo_life", "../misc/cgo/life", "misc/cgo/life", ".")
t.registerTest("cgo_stdio", "", &goTest{dir: "../misc/cgo/stdio", timeout: 5 * time.Minute}, rtHostTest{})
t.registerTest("cgo_life", "", &goTest{dir: "../misc/cgo/life", timeout: 5 * time.Minute}, rtHostTest{})
if goos != "android" {
t.registerHostTest("cgo_fortran", "../misc/cgo/fortran", "misc/cgo/fortran", ".")
t.registerTest("cgo_fortran", "", &goTest{dir: "../misc/cgo/fortran", timeout: 5 * time.Minute}, rtHostTest{})
}
if t.hasSwig() && goos != "android" {
t.registerTest("swig_stdio", "", &goTest{dir: "../misc/swig/stdio"})
@ -865,15 +942,15 @@ func (t *tester) registerTests() {
// recompile the entire standard library. If make.bash ran with
// special -gcflags, that's not true.
if t.cgoEnabled && gogcflags == "" {
t.registerHostTest("testgodefs", "../misc/cgo/testgodefs", "misc/cgo/testgodefs", ".")
t.registerTest("testgodefs", "", &goTest{dir: "../misc/cgo/testgodefs", timeout: 5 * time.Minute}, rtHostTest{})
t.registerTest("testso", "", &goTest{dir: "../misc/cgo/testso", timeout: 600 * time.Second})
t.registerTest("testsovar", "", &goTest{dir: "../misc/cgo/testsovar", timeout: 600 * time.Second})
if t.supportedBuildmode("c-archive") {
t.registerHostTest("testcarchive", "../misc/cgo/testcarchive", "misc/cgo/testcarchive", ".")
t.registerTest("testcarchive", "", &goTest{dir: "../misc/cgo/testcarchive", timeout: 5 * time.Minute}, rtHostTest{})
}
if t.supportedBuildmode("c-shared") {
t.registerHostTest("testcshared", "../misc/cgo/testcshared", "misc/cgo/testcshared", ".")
t.registerTest("testcshared", "", &goTest{dir: "../misc/cgo/testcshared", timeout: 5 * time.Minute}, rtHostTest{})
}
if t.supportedBuildmode("shared") {
t.registerTest("testshared", "", &goTest{dir: "../misc/cgo/testshared", timeout: 600 * time.Second})
@ -884,10 +961,10 @@ func (t *tester) registerTests() {
if goos == "linux" || (goos == "freebsd" && goarch == "amd64") {
// because Pdeathsig of syscall.SysProcAttr struct used in misc/cgo/testsanitizers is only
// supported on Linux and FreeBSD.
t.registerHostTest("testsanitizers", "../misc/cgo/testsanitizers", "misc/cgo/testsanitizers", ".")
t.registerTest("testsanitizers", "", &goTest{dir: "../misc/cgo/testsanitizers", timeout: 5 * time.Minute}, rtHostTest{})
}
if t.hasBash() && goos != "android" && !t.iOS() && gohostos != "windows" {
t.registerHostTest("cgo_errors", "../misc/cgo/errors", "misc/cgo/errors", ".")
t.registerTest("cgo_errors", "", &goTest{dir: "../misc/cgo/errors", timeout: 5 * time.Minute}, rtHostTest{})
}
}
@ -939,7 +1016,7 @@ func (t *tester) registerTests() {
// Ensure that the toolchain can bootstrap itself.
// This test adds another ~45s to all.bash if run sequentially, so run it only on the builders.
if os.Getenv("GO_BUILDER_NAME") != "" && goos != "android" && !t.iOS() {
t.registerHostTest("reboot", "../misc/reboot", "misc/reboot", ".")
t.registerTest("reboot", "", &goTest{dir: "../misc/reboot", timeout: 5 * time.Minute}, rtHostTest{})
}
}
@ -972,11 +1049,18 @@ type rtPreFunc struct {
func (rtPreFunc) isRegisterTestOpt() {}
// rtHostTest is a registerTest option that indicates this is a host test that
// should be run using goTest.runHostTest. It implies rtSequential.
type rtHostTest struct{}
func (rtHostTest) isRegisterTestOpt() {}
// registerTest registers a test that runs the given goTest.
//
// If heading is "", it uses test.dir as the heading.
func (t *tester) registerTest(name, heading string, test *goTest, opts ...registerTestOpt) {
seq := false
hostTest := false
var preFunc func(*distTest) bool
for _, opt := range opts {
switch opt := opt.(type) {
@ -984,6 +1068,8 @@ func (t *tester) registerTest(name, heading string, test *goTest, opts ...regist
seq = true
case rtPreFunc:
preFunc = opt.pre
case rtHostTest:
seq, hostTest = true, true
}
}
if t.isRegisteredTestName(name) {
@ -1001,6 +1087,9 @@ func (t *tester) registerTest(name, heading string, test *goTest, opts ...regist
}
if seq {
t.runPending(dt)
if hostTest {
return test.runHostTest(t)
}
return test.run(t)
}
w := &work{
@ -1228,48 +1317,6 @@ func (t *tester) supportedBuildmode(mode string) bool {
}
}
func (t *tester) registerHostTest(name, heading, dir, pkg string) {
t.tests = append(t.tests, distTest{
name: name,
heading: heading,
fn: func(dt *distTest) error {
t.runPending(dt)
timelog("start", name)
defer timelog("end", name)
return t.runHostTest(dir, pkg)
},
})
}
func (t *tester) runHostTest(dir, pkg string) error {
out, err := exec.Command(gorootBinGo, "env", "GOEXE", "GOTMPDIR").Output()
if err != nil {
return err
}
parts := strings.Split(string(out), "\n")
if len(parts) < 2 {
return fmt.Errorf("'go env GOEXE GOTMPDIR' output contains <2 lines")
}
GOEXE := strings.TrimSpace(parts[0])
GOTMPDIR := strings.TrimSpace(parts[1])
f, err := os.CreateTemp(GOTMPDIR, "test.test-*"+GOEXE)
if err != nil {
return err
}
f.Close()
defer os.Remove(f.Name())
cmd := t.dirCmd(dir, t.goTest(), "-c", "-o", f.Name(), pkg)
setEnv(cmd, "GOARCH", gohostarch)
setEnv(cmd, "GOOS", gohostos)
if err := cmd.Run(); err != nil {
return err
}
return t.dirCmd(dir, f.Name(), "-test.short="+short(), "-test.timeout="+t.timeoutDuration(300).String()).Run()
}
func (t *tester) registerCgoTests() {
cgoTest := func(name string, subdir, linkmode, buildmode string, opts ...registerTestOpt) *goTest {
gt := &goTest{