mirror of
https://github.com/golang/go
synced 2024-11-06 01:46:12 -07:00
632162ccbc
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>
272 lines
8.0 KiB
Go
272 lines
8.0 KiB
Go
// Copyright 2014 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// +build ignore
|
|
|
|
// This program can be used as go_android_GOARCH_exec by the Go tool.
|
|
// It executes binaries on an android device using adb.
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"go/build"
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"os/signal"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
)
|
|
|
|
func run(args ...string) string {
|
|
if flags := os.Getenv("GOANDROID_ADB_FLAGS"); flags != "" {
|
|
args = append(strings.Split(flags, " "), args...)
|
|
}
|
|
buf := new(bytes.Buffer)
|
|
cmd := exec.Command("adb", args...)
|
|
cmd.Stdout = io.MultiWriter(os.Stdout, buf)
|
|
// If the adb subprocess somehow hangs, go test will kill this wrapper
|
|
// and wait for our os.Stderr (and os.Stdout) to close as a result.
|
|
// However, if the os.Stderr (or os.Stdout) file descriptors are
|
|
// passed on, the hanging adb subprocess will hold them open and
|
|
// go test will hang forever.
|
|
//
|
|
// Avoid that by wrapping stderr, breaking the short circuit and
|
|
// forcing cmd.Run to use another pipe and goroutine to pass
|
|
// along stderr from adb.
|
|
cmd.Stderr = struct{ io.Writer }{os.Stderr}
|
|
log.Printf("adb %s", strings.Join(args, " "))
|
|
err := cmd.Run()
|
|
if err != nil {
|
|
log.Fatalf("adb %s: %v", strings.Join(args, " "), err)
|
|
}
|
|
return buf.String()
|
|
}
|
|
|
|
const (
|
|
deviceRoot = "/data/local/tmp/go_exec_android"
|
|
deviceGoroot = deviceRoot + "/goroot"
|
|
)
|
|
|
|
func main() {
|
|
log.SetFlags(0)
|
|
log.SetPrefix("go_android_exec: ")
|
|
|
|
// Concurrent use of adb is flaky, so serialize adb commands.
|
|
// See https://github.com/golang/go/issues/23795 or
|
|
// https://issuetracker.google.com/issues/73230216.
|
|
lockPath := filepath.Join(os.TempDir(), "go_android_exec-adb-lock")
|
|
lock, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0666)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer lock.Close()
|
|
if err := syscall.Flock(int(lock.Fd()), syscall.LOCK_EX); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
// In case we're booting a device or emulator alongside all.bash, wait for
|
|
// it to be ready. adb wait-for-device is not enough, we have to
|
|
// 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.
|
|
// 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
|
|
// "$GOROOT/src/mime/multipart" or "$GOPATH/src/golang.org/x/mobile".
|
|
// 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(deviceGopath, subdir)
|
|
if inGoRoot {
|
|
deviceCwd = filepath.Join(deviceGoroot, subdir)
|
|
} else {
|
|
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...)
|
|
}
|
|
}
|
|
|
|
deviceBin := fmt.Sprintf("%s/%s", deviceGotmp, binName)
|
|
run("push", os.Args[1], deviceBin)
|
|
|
|
// Forward SIGQUIT from the go command to show backtraces from
|
|
// the binary instead of from this wrapper.
|
|
quit := make(chan os.Signal, 1)
|
|
signal.Notify(quit, syscall.SIGQUIT)
|
|
go func() {
|
|
for range quit {
|
|
// We don't have the PID of the running process; use the
|
|
// binary name instead.
|
|
run("exec-out", "killall -QUIT "+binName)
|
|
}
|
|
}()
|
|
// In light of
|
|
// https://code.google.com/p/android/issues/detail?id=3254
|
|
// dont trust the exitcode of adb. Instead, append the exitcode to
|
|
// the output and parse it from there.
|
|
const exitstr = "exitcode="
|
|
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 + "$?"
|
|
output := run("exec-out", cmd)
|
|
signal.Reset(syscall.SIGQUIT)
|
|
close(quit)
|
|
|
|
exitIdx := strings.LastIndex(output, exitstr)
|
|
if exitIdx == -1 {
|
|
log.Fatalf("no exit code: %q", output)
|
|
}
|
|
code, err := strconv.Atoi(output[exitIdx+len(exitstr):])
|
|
if err != nil {
|
|
log.Fatalf("bad exit code: %v", err)
|
|
}
|
|
os.Exit(code)
|
|
}
|
|
|
|
// subdir determines the package based on the current working directory,
|
|
// and returns the path to the package source relative to $GOROOT (or $GOPATH).
|
|
func subdir() (pkgpath string, underGoRoot bool) {
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
cwd, err = filepath.EvalSymlinks(cwd)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
goroot, err := filepath.EvalSymlinks(runtime.GOROOT())
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
if strings.HasPrefix(cwd, goroot) {
|
|
subdir, err := filepath.Rel(goroot, cwd)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
return subdir, true
|
|
}
|
|
|
|
for _, p := range filepath.SplitList(build.Default.GOPATH) {
|
|
pabs, err := filepath.EvalSymlinks(p)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
if !strings.HasPrefix(cwd, pabs) {
|
|
continue
|
|
}
|
|
subdir, err := filepath.Rel(pabs, cwd)
|
|
if err == nil {
|
|
return subdir, false
|
|
}
|
|
}
|
|
log.Fatalf("the current path %q is not in either GOROOT(%q) or GOPATH(%q)",
|
|
cwd, runtime.GOROOT(), build.Default.GOPATH)
|
|
return "", false
|
|
}
|
|
|
|
// 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)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer stat.Close()
|
|
// Serialize check and copying.
|
|
if err := syscall.Flock(int(stat.Fd()), syscall.LOCK_EX); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
s, err := ioutil.ReadAll(stat)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
if string(s) == "done" {
|
|
return
|
|
}
|
|
// 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")
|
|
tmpGo, err := ioutil.TempFile("", "go_android_exec-cmd-go-*")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
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)
|
|
}
|
|
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))
|
|
}
|
|
|
|
if _, err := stat.Write([]byte("done")); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|