cmd/gobind: exec the test binary as gobind and log only stderr by default

Using the test binary as the "gobind" command not only avoids the
overhead of recompiling the command, but also allows commands like "go
test -race" to actually test the requested configuration of the
command.

Logging stderr and stdout separately — and logging only stderr by
default — makes the failure messages much easier to spot. (Logging the
combined output as before produced a massive wall of text that tends
to bury the actual errors.)

For golang/go#56292.

Change-Id: Ia11fad19418d9b9004608c76fe512ceab4f247bc
Reviewed-on: https://go-review.googlesource.com/c/mobile/+/443655
Run-TryBot: Bryan Mills <bcmills@google.com>
Reviewed-by: Hajime Hoshi <hajimehoshi@gmail.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Auto-Submit: Bryan Mills <bcmills@google.com>
Reviewed-by: David Chase <drchase@google.com>
This commit is contained in:
Bryan C. Mills 2022-10-18 09:21:39 -04:00 коммит произвёл Gopher Robot
Родитель c746ac2283
Коммит 3eb82726e4
1 изменённых файлов: 45 добавлений и 36 удалений

Просмотреть файл

@ -7,8 +7,6 @@ package main
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"runtime"
@ -18,6 +16,19 @@ import (
"golang.org/x/tools/go/packages/packagestest"
)
func TestMain(m *testing.M) {
// To avoid recompiling the gobind command (and to support compiler options
// like -race and -coverage), allow the test binary itself to re-exec itself
// as the gobind command by setting an environment variable.
if os.Getenv("GOBIND_TEST_IS_GOBIND") != "" {
main()
os.Exit(0)
}
os.Setenv("GOBIND_TEST_IS_GOBIND", "1")
os.Exit(m.Run())
}
var tests = []struct {
name string
lang string
@ -64,55 +75,55 @@ var tests = []struct {
},
}
var gobindBin string
func TestMain(m *testing.M) {
os.Exit(testMain(m))
func mustHaveBindTestdata(t testing.TB) {
switch runtime.GOOS {
case "android", "ios":
t.Skipf("skipping: test cannot access ../../bind/testdata on %s/%s", runtime.GOOS, runtime.GOARCH)
}
}
func testMain(m *testing.M) int {
bin, err := ioutil.TempFile("", "*.exe")
func gobindBin(t testing.TB) string {
switch runtime.GOOS {
case "js", "ios":
t.Skipf("skipping: cannot exec subprocess on %s/%s", runtime.GOOS, runtime.GOARCH)
}
p, err := os.Executable()
if err != nil {
log.Fatal(err)
t.Fatal(err)
}
bin.Close()
defer os.Remove(bin.Name())
if runtime.GOOS != "android" {
if out, err := exec.Command("go", "build", "-o", bin.Name(), "golang.org/x/mobile/cmd/gobind").CombinedOutput(); err != nil {
log.Fatalf("gobind build failed: %v: %s", err, out)
}
gobindBin = bin.Name()
}
return m.Run()
return p
}
func runGobind(t testing.TB, lang, pkg, goos string, exported *packagestest.Exported) error {
if gobindBin == "" {
t.Skipf("gobind is not available on %s", runtime.GOOS)
}
cmd := exec.Command(gobindBin, "-lang", lang, pkg)
cmd := exec.Command(gobindBin(t), "-lang", lang, pkg)
cmd.Dir = exported.Config.Dir
cmd.Env = exported.Config.Env
if goos != "" {
// Add CGO_ENABLED=1 explicitly since Cgo is disabled when GOOS is different from host OS.
cmd.Env = append(cmd.Env, "GOOS="+goos, "CGO_ENABLED=1")
}
if out, err := cmd.CombinedOutput(); err != nil {
var cmd string
for _, env := range exported.Config.Env {
if strings.HasPrefix(env, "GO111MODULE=") {
cmd = env + " "
break
}
}
cmd += fmt.Sprintf("gobind -lang %s %s", lang, pkg)
return fmt.Errorf("%s failed: %v: %s", cmd, err, out)
stderr := new(strings.Builder)
cmd.Stderr = stderr
stdout := new(strings.Builder)
cmd.Stdout = stdout
err := cmd.Run()
if testing.Verbose() && stdout.Len() > 0 {
t.Logf("stdout (%v):\n%s", cmd, stderr)
}
if stderr.Len() > 0 {
t.Logf("stderr (%v):\n%s", cmd, stderr)
}
if err != nil {
return fmt.Errorf("%v: %w", cmd, err)
}
return nil
}
func TestGobind(t *testing.T) { packagestest.TestAll(t, testGobind) }
func testGobind(t *testing.T, exporter packagestest.Exporter) {
mustHaveBindTestdata(t)
_, javapErr := exec.LookPath("javap")
exported := packagestest.Export(t, exporter, []packagestest.Module{{
Name: "golang.org/x/mobile",
@ -137,9 +148,7 @@ func testGobind(t *testing.T, exporter packagestest.Exporter) {
func TestDocs(t *testing.T) { packagestest.TestAll(t, testDocs) }
func testDocs(t *testing.T, exporter packagestest.Exporter) {
if gobindBin == "" {
t.Skipf("gobind is not available on %s", runtime.GOOS)
}
mustHaveBindTestdata(t)
const docsrc = `
package doctest
@ -165,7 +174,7 @@ type Struct struct{
const comment = "This is a comment."
for _, lang := range []string{"java", "objc"} {
cmd := exec.Command(gobindBin, "-lang", lang, "example.com/doctest")
cmd := exec.Command(gobindBin(t), "-lang", lang, "example.com/doctest")
cmd.Dir = exported.Config.Dir
cmd.Env = exported.Config.Env
out, err := cmd.CombinedOutput()