misc/android: enable many more tests on GOOS=android

Android tests are built on the host and run on the device. To do
that, the exec wrapper copies the test binary and testdata to the device.
To enable many more tests, make the copied environment more like the host:

- Copy all of pkg from GOROOT, not just the android pkg directory.
- Copy any parent testdata directories as well as the package's own.
- Copy *.go files from the package directory. This enables misc/cgo/stdio
and misc/cgo/life tests that were invisible before so disable them explicitly.
- Always copy the GOROOT, even for tests outside GOROOT. This is expensive
 but only done once per make.bash.
- Build the go tool for the device and put it in PATH. Set GOCACHE
to a writable directory and disable cgo.

While here, use a single directory for all the exec wrapper files and
delete that once per make.bash as well.

In total, this CL enables many tests in the subrepos that would need skips
without it, in particular the x/tools tests.

Fixes #11452
Updates #23824
Updates #11811

Change-Id: I2e50d8b57db9bc4637f25272a5360c8b2cf4e627
Reviewed-on: https://go-review.googlesource.com/c/go/+/165797
Reviewed-by: Bryan C. Mills <bcmills@google.com>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
This commit is contained in:
Elias Naur 2019-03-06 12:53:56 +01:00
parent d3dd2588eb
commit 632162ccbc
3 changed files with 81 additions and 34 deletions

View file

@ -51,8 +51,8 @@ func run(args ...string) string {
}
const (
deviceGoroot = "/data/local/tmp/goroot"
deviceGopath = "/data/local/tmp/gopath"
deviceRoot = "/data/local/tmp/go_exec_android"
deviceGoroot = deviceRoot + "/goroot"
)
func main() {
@ -77,10 +77,16 @@ func main() {
// wait for sys.boot_completed.
run("wait-for-device", "exec-out", "while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;")
// Done once per make.bash.
adbCopyGoroot()
// Prepare a temporary directory that will be cleaned up at the end.
deviceGotmp := fmt.Sprintf("/data/local/tmp/%s-%d",
filepath.Base(os.Args[1]), os.Getpid())
run("exec-out", "mkdir", "-p", deviceGotmp)
// Binary names can conflict.
// E.g. template.test from the {html,text}/template packages.
binName := filepath.Base(os.Args[1])
deviceGotmp := fmt.Sprintf(deviceRoot+"/%s-%d", binName, os.Getpid())
deviceGopath := deviceGotmp + "/gopath"
defer run("exec-out", "rm", "-rf", deviceGotmp) // Clean up.
// Determine the package by examining the current working
// directory, which will look something like
@ -88,24 +94,27 @@ func main() {
// We extract everything after the $GOROOT or $GOPATH to run on the
// same relative directory on the target device.
subdir, inGoRoot := subdir()
deviceCwd := filepath.Join(deviceGoroot, subdir)
if !inGoRoot {
deviceCwd = filepath.Join(deviceGopath, subdir)
deviceCwd := filepath.Join(deviceGopath, subdir)
if inGoRoot {
deviceCwd = filepath.Join(deviceGoroot, subdir)
} else {
adbSyncGoroot()
}
run("exec-out", "mkdir", "-p", deviceCwd)
run("exec-out", "mkdir", "-p", deviceCwd)
adbCopyTestdata(deviceCwd, subdir)
// Copy .go files from the package.
goFiles, err := filepath.Glob("*.go")
if err != nil {
log.Fatal(err)
}
if len(goFiles) > 0 {
args := append(append([]string{"push"}, goFiles...), deviceCwd)
run(args...)
}
}
// Binary names can conflict.
// E.g. template.test from the {html,text}/template packages.
binName := fmt.Sprintf("%s-%d", filepath.Base(os.Args[1]), os.Getpid())
deviceBin := fmt.Sprintf("%s/%s", deviceGotmp, binName)
run("push", os.Args[1], deviceBin)
if _, err := os.Stat("testdata"); err == nil {
run("push", "--sync", "testdata", deviceCwd)
}
// Forward SIGQUIT from the go command to show backtraces from
// the binary instead of from this wrapper.
quit := make(chan os.Signal, 1)
@ -125,6 +134,9 @@ func main() {
cmd := `export TMPDIR="` + deviceGotmp + `"` +
`; export GOROOT="` + deviceGoroot + `"` +
`; export GOPATH="` + deviceGopath + `"` +
`; export CGO_ENABLED=0` +
`; export GOCACHE="` + deviceRoot + `/gocache"` +
`; export PATH=$PATH:"` + deviceGoroot + `/bin"` +
`; cd "` + deviceCwd + `"` +
"; '" + deviceBin + "' " + strings.Join(os.Args[2:], " ") +
"; echo -n " + exitstr + "$?"
@ -132,8 +144,6 @@ func main() {
signal.Reset(syscall.SIGQUIT)
close(quit)
run("exec-out", "rm", "-rf", deviceGotmp) // Clean up.
exitIdx := strings.LastIndex(output, exitstr)
if exitIdx == -1 {
log.Fatalf("no exit code: %q", output)
@ -186,9 +196,34 @@ func subdir() (pkgpath string, underGoRoot bool) {
return "", false
}
// adbSyncGoroot ensures that files necessary for testing the Go standard
// packages are present on the attached device.
func adbSyncGoroot() {
// adbCopyTestdata copies testdata directories from subdir to deviceCwd
// on the device.
// It is common for tests to reach out into testdata from parent
// packages, so copy testdata directories all the way up to the root
// of subdir.
func adbCopyTestdata(deviceCwd, subdir string) {
dir := ""
for {
testdata := filepath.Join(dir, "testdata")
if _, err := os.Stat(testdata); err == nil {
devicePath := filepath.Join(deviceCwd, dir)
run("exec-out", "mkdir", "-p", devicePath)
run("push", testdata, devicePath)
}
if subdir == "." {
break
}
subdir = filepath.Dir(subdir)
dir = filepath.Join(dir, "..")
}
}
// adbCopyGoroot clears deviceRoot for previous versions of GOROOT, GOPATH
// and temporary data. Then, it copies relevant parts of GOROOT to the device,
// including the go tool built for android.
// A lock file ensures this only happens once, even with concurrent exec
// wrappers.
func adbCopyGoroot() {
// Also known by cmd/dist. The bootstrap command deletes the file.
statPath := filepath.Join(os.TempDir(), "go_android_exec-adb-sync-status")
stat, err := os.OpenFile(statPath, os.O_CREATE|os.O_RDWR, 0666)
@ -196,7 +231,7 @@ func adbSyncGoroot() {
log.Fatal(err)
}
defer stat.Close()
// Serialize check and syncing.
// Serialize check and copying.
if err := syscall.Flock(int(stat.Fd()), syscall.LOCK_EX); err != nil {
log.Fatal(err)
}
@ -207,23 +242,29 @@ func adbSyncGoroot() {
if string(s) == "done" {
return
}
devRoot := "/data/local/tmp/goroot"
run("exec-out", "rm", "-rf", devRoot)
run("exec-out", "mkdir", "-p", devRoot+"/pkg")
// Delete GOROOT, GOPATH and any leftover test data.
run("exec-out", "rm", "-rf", deviceRoot)
deviceBin := filepath.Join(deviceGoroot, "bin")
run("exec-out", "mkdir", "-p", deviceBin)
goroot := runtime.GOROOT()
// Build go for android.
goCmd := filepath.Join(goroot, "bin", "go")
runtimea, err := exec.Command(goCmd, "list", "-f", "{{.Target}}", "runtime").Output()
tmpGo, err := ioutil.TempFile("", "go_android_exec-cmd-go-*")
if err != nil {
log.Fatal(err)
}
pkgdir := filepath.Dir(string(runtimea))
if pkgdir == "" {
log.Fatal("could not find android pkg dir")
tmpGo.Close()
defer os.Remove(tmpGo.Name())
if out, err := exec.Command(goCmd, "build", "-o", tmpGo.Name(), "cmd/go").CombinedOutput(); err != nil {
log.Fatalf("failed to build go tool for device: %s\n%v", out, err)
}
for _, dir := range []string{"src", "test", "lib"} {
run("push", filepath.Join(goroot, dir), filepath.Join(devRoot))
deviceGo := filepath.Join(deviceBin, "go")
run("push", tmpGo.Name(), deviceGo)
for _, dir := range []string{"pkg", "src", "test", "lib", "api"} {
run("push", filepath.Join(goroot, dir), filepath.Join(deviceGoroot))
}
run("push", filepath.Join(pkgdir), filepath.Join(devRoot, "pkg/"))
if _, err := stat.Write([]byte("done")); err != nil {
log.Fatal(err)
}

View file

@ -46,6 +46,9 @@ func testMain(m *testing.M) int {
}
func TestTestRun(t *testing.T) {
if os.Getenv("GOOS") == "android" {
t.Skip("the go tool runs with CGO_ENABLED=0 on the android device")
}
out, err := exec.Command("go", "env", "GOROOT").Output()
if err != nil {
t.Fatal(err)

View file

@ -46,6 +46,9 @@ func testMain(m *testing.M) int {
}
func TestTestRun(t *testing.T) {
if os.Getenv("GOOS") == "android" {
t.Skip("subpackage stdio is not available on android")
}
out, err := exec.Command("go", "env", "GOROOT").Output()
if err != nil {
t.Fatal(err)