2016-04-10 15:32:26 -06:00
|
|
|
// Copyright 2014 The Go Authors. All rights reserved.
|
2014-07-09 04:56:49 -06:00
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
// 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"
|
2015-01-15 14:47:41 -07:00
|
|
|
"go/build"
|
2014-07-09 04:56:49 -06:00
|
|
|
"io"
|
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
2018-05-23 08:31:36 -06:00
|
|
|
"os/signal"
|
2014-07-09 04:56:49 -06:00
|
|
|
"path/filepath"
|
|
|
|
"runtime"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
2018-05-23 08:31:36 -06:00
|
|
|
"syscall"
|
2014-07-09 04:56:49 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
func run(args ...string) string {
|
2018-01-20 11:56:51 -07:00
|
|
|
if flags := os.Getenv("GOANDROID_ADB_FLAGS"); flags != "" {
|
|
|
|
args = append(strings.Split(flags, " "), args...)
|
|
|
|
}
|
2014-07-09 04:56:49 -06:00
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
cmd := exec.Command("adb", args...)
|
|
|
|
cmd.Stdout = io.MultiWriter(os.Stdout, buf)
|
2017-05-01 12:35:08 -06:00
|
|
|
// 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}
|
2014-07-09 04:56:49 -06:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
|
2015-01-15 14:47:41 -07:00
|
|
|
const (
|
|
|
|
// Directory structure on the target device androidtest.bash assumes.
|
|
|
|
deviceGoroot = "/data/local/tmp/goroot"
|
|
|
|
deviceGopath = "/data/local/tmp/gopath"
|
|
|
|
)
|
|
|
|
|
2014-07-09 04:56:49 -06:00
|
|
|
func main() {
|
|
|
|
log.SetFlags(0)
|
|
|
|
log.SetPrefix("go_android_exec: ")
|
|
|
|
|
2015-01-15 14:47:41 -07:00
|
|
|
// 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("shell", "mkdir", "-p", deviceGotmp)
|
|
|
|
|
|
|
|
// Determine the package by examining the current working
|
2014-07-09 04:56:49 -06:00
|
|
|
// directory, which will look something like
|
2015-01-15 14:47:41 -07:00
|
|
|
// "$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(deviceGoroot, subdir)
|
|
|
|
if !inGoRoot {
|
|
|
|
deviceCwd = filepath.Join(deviceGopath, subdir)
|
2014-07-09 04:56:49 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// Binary names can conflict.
|
|
|
|
// E.g. template.test from the {html,text}/template packages.
|
2018-05-23 08:31:36 -06:00
|
|
|
binName := fmt.Sprintf("%s-%d", filepath.Base(os.Args[1]), os.Getpid())
|
|
|
|
deviceBin := fmt.Sprintf("%s/%s", deviceGotmp, binName)
|
2014-07-09 04:56:49 -06:00
|
|
|
|
|
|
|
// The push of the binary happens in parallel with other tests.
|
|
|
|
// Unfortunately, a simultaneous call to adb shell hold open
|
|
|
|
// file descriptors, so it is necessary to push then move to
|
|
|
|
// avoid a "text file busy" error on execution.
|
|
|
|
// https://code.google.com/p/android/issues/detail?id=65857
|
|
|
|
run("push", os.Args[1], deviceBin+"-tmp")
|
|
|
|
run("shell", "cp '"+deviceBin+"-tmp' '"+deviceBin+"'")
|
|
|
|
run("shell", "rm '"+deviceBin+"-tmp'")
|
|
|
|
|
2018-05-23 08:31:36 -06:00
|
|
|
// 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("shell", "killall -QUIT "+binName)
|
|
|
|
}
|
|
|
|
}()
|
2014-07-09 04:56:49 -06:00
|
|
|
// The adb shell command will return an exit code of 0 regardless
|
|
|
|
// of the command run. E.g.
|
2015-01-15 14:47:41 -07:00
|
|
|
// $ adb shell false
|
|
|
|
// $ echo $?
|
|
|
|
// 0
|
2014-07-09 04:56:49 -06:00
|
|
|
// https://code.google.com/p/android/issues/detail?id=3254
|
|
|
|
// So we append the exitcode to the output and parse it from there.
|
|
|
|
const exitstr = "exitcode="
|
2015-01-15 14:47:41 -07:00
|
|
|
cmd := `export TMPDIR="` + deviceGotmp + `"` +
|
2014-07-09 04:56:49 -06:00
|
|
|
`; export GOROOT="` + deviceGoroot + `"` +
|
2015-01-15 14:47:41 -07:00
|
|
|
`; export GOPATH="` + deviceGopath + `"` +
|
|
|
|
`; cd "` + deviceCwd + `"` +
|
2014-07-09 04:56:49 -06:00
|
|
|
"; '" + deviceBin + "' " + strings.Join(os.Args[2:], " ") +
|
|
|
|
"; echo -n " + exitstr + "$?"
|
|
|
|
output := run("shell", cmd)
|
2018-05-23 08:31:36 -06:00
|
|
|
signal.Reset(syscall.SIGQUIT)
|
|
|
|
close(quit)
|
2015-01-15 14:47:41 -07:00
|
|
|
|
|
|
|
run("shell", "rm", "-rf", deviceGotmp) // Clean up.
|
|
|
|
|
2016-06-03 03:41:26 -06:00
|
|
|
exitIdx := strings.LastIndex(output, exitstr)
|
|
|
|
if exitIdx == -1 {
|
2014-07-09 04:56:49 -06:00
|
|
|
log.Fatalf("no exit code: %q", output)
|
|
|
|
}
|
2016-06-03 03:41:26 -06:00
|
|
|
code, err := strconv.Atoi(output[exitIdx+len(exitstr):])
|
2014-07-09 04:56:49 -06:00
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("bad exit code: %v", err)
|
|
|
|
}
|
|
|
|
os.Exit(code)
|
|
|
|
}
|
2015-01-15 14:47:41 -07:00
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
if root := runtime.GOROOT(); strings.HasPrefix(cwd, root) {
|
|
|
|
subdir, err := filepath.Rel(root, cwd)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
return subdir, true
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, p := range filepath.SplitList(build.Default.GOPATH) {
|
|
|
|
if !strings.HasPrefix(cwd, p) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
subdir, err := filepath.Rel(p, 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
|
|
|
|
}
|