build/androidtest.bash: script to run all tests in a repository on an
android device.
It assumes it runs in golang.org/x/mobile repository.
This script copies the repository source tree into the connected device
and invokes go test that runs using the binary compiled from
go_android_exec.go. go_android_exec arranges to push and run the test
binary to the right place in the android device.
Change-Id: I4ac24bcfd8e26e01cccd3d7c2dddaf6b3c14951e
Reviewed-on: https://go-review.googlesource.com/1680
Reviewed-by: David Crawshaw <crawshaw@golang.org>
diff --git a/bind/java/test.bash b/bind/java/test.bash
index 53faa6d..6c1944a 100755
--- a/bind/java/test.bash
+++ b/bind/java/test.bash
@@ -18,8 +18,12 @@
rm -rf "$ANDROID_APP"
}
+if [ -z "$TMPDIR" ]; then
+ TMPDIR="/tmp"
+fi
+
if [ -z "$ANDROID_APP" ]; then
- ANDROID_APP=`mktemp -d /tmp/android-java.XXXXX` || die 'failed to create a temporary directory'
+ ANDROID_APP=`mktemp -d ${TMPDIR}/android-java.XXXXX` || die 'failed to create a temporary directory'
echo "Temporary directory for test: $ANDROID_APP"
trap cleanup EXIT
fi
@@ -56,7 +60,7 @@
# If there is no connected device, this will fail after creating the test apk.
# The apk is located in $ANDROID_APP/build/outputs/apk directory.
-./gradlew connectedAndroidTest && echo "PASS"
+./gradlew connectedAndroidTest && echo "PASS" && exit 0
# TODO(hyangah): copy the gradle's test output directory in case of test failure?
diff --git a/build/androidtest.bash b/build/androidtest.bash
new file mode 100755
index 0000000..5a80a21
--- /dev/null
+++ b/build/androidtest.bash
@@ -0,0 +1,90 @@
+#!/usr/bin/env bash
+
+# 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.
+
+# For testing Android. Run from a repository root.
+# build/androidtest.bash
+# The compiler runs locally, pushes the copy of the repository
+# to a target device using adb, and runs tests in the device
+# using adb shell commands.
+
+set -e
+ulimit -c 0 # no core files
+
+function die() {
+ echo "FAIL: $1"
+ exit 1
+}
+
+if [ -z "${GOPATH}" ]; then
+ die 'GOPATH must be set.'
+fi
+
+readonly CURDIR=`pwd`
+if [ ! -f AUTHORS ]; then
+ die 'androidtest.bash must be run from the repository root.'
+fi
+
+function pkg() {
+ local paths=(${GOPATH//:/ })
+ for e in "${paths[@]}"; do
+ e="${e%/}"
+ local relpath="${CURDIR#"${e}/src/"}"
+ if [ "${relpath}" != "${CURDIR}" ]; then
+ echo "${relpath}"
+ break
+ fi
+ done
+}
+
+export PKG="$(pkg)"
+if [ -z "${PKG}" ]; then
+ die 'androidtest.bash failed to determine the repository package name.'
+fi
+
+export DEVICEDIR=/data/local/tmp/androidtest-$$
+export TMPDIR=`mktemp -d /tmp/androidtest.XXXXX`
+
+function cleanup() {
+ echo '# Cleaning up...'
+ rm -rf "$TMPDIR"
+ adb shell rm -rf "${DEVICEDIR}"
+}
+trap cleanup EXIT
+
+# 'adb sync' syncs data in ANDROID_PRODUCT_OUT directory.
+# We copy the entire golang.org/x/mobile/... (with -p option to preserve
+# file properties) to the directory assuming tests may depend only
+# on data in the same subrepository.
+echo '# Syncing test files to android device'
+export ANDROID_PRODUCT_OUT="${TMPDIR}/androidtest-$$"
+readonly LOCALDIR="${ANDROID_PRODUCT_OUT}/${DEVICEDIR}"
+
+mkdir -p "${LOCALDIR}/${PKG}"
+echo "cp -R --preserve=all ./* ${LOCALDIR}/${PKG}/"
+cp -R --preserve=all ./* "${LOCALDIR}/${PKG}/"
+
+time adb sync data &> /dev/null
+echo ''
+
+echo '# Run tests on android (arm7)'
+# Build go_android_${GOARCH}_exec that will be invoked for
+# GOOS=android GOARCH=$GOARCH go test/run.
+mkdir -p "${TMPDIR}/bin"
+export PATH=${TMPDIR}/bin:${PATH}
+GOOS="${GOHOSTOS}" GOARCH="${GOHOSTARCH}" go build \
+ -o "${TMPDIR}/bin/go_android_arm_exec" \
+ "${GOPATH}/src/golang.org/x/mobile/build/go_android_exec.go"
+CGO_ENABLED=1 GOOS=android GOARCH=arm GOARM=7 \
+ go test ./...
+
+# Special tests for mobile subrepository.
+if [ "$PKG" = "golang.org/x/mobile" ]; then
+ echo '# Run mobile=repository specific android tests.'
+
+ cd "${CURDIR}/bind/java"; ./test.bash
+fi
+
+exit 0
diff --git a/build/go_android_exec.go b/build/go_android_exec.go
new file mode 100644
index 0000000..fbeaf7f
--- /dev/null
+++ b/build/go_android_exec.go
@@ -0,0 +1,136 @@
+// 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.
+// This program is supposed to be called by
+// golang.org/x/mobile/build/androidtest.bash that arranges to copy
+// the tested repository source tree to the android device and invokes
+// go test. This program depends on PKG and DEVICEDIR environment variables
+// to identify the tested repository (e.g. golang.org/x/mobile) and
+// to find the source directory in the android device. The androidtest.bash
+// script is responsible for setting the environment variables.
+package main
+
+// This is adopted from golang.org/x/go/misc/android/go_android_exec.go.
+
+import (
+ "bytes"
+ "fmt"
+ "go/build"
+ "io"
+ "log"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "text/template"
+)
+
+func run(args ...string) string {
+ buf := new(bytes.Buffer)
+ cmd := exec.Command("adb", args...)
+ cmd.Stdout = io.MultiWriter(os.Stdout, buf)
+ cmd.Stderr = 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()
+}
+
+func rel(cwd, pkg string) (string, error) {
+ paths := build.Default.GOPATH
+ for _, p := range filepath.SplitList(paths) {
+ r, err := filepath.Rel(filepath.Join(p, "src", pkg), cwd)
+ if err == nil {
+ return r, nil
+ }
+ }
+ return "", fmt.Errorf("%q is not under GOPATH(%q)", cwd, paths)
+}
+
+func main() {
+ log.SetFlags(0)
+ log.SetPrefix("go_android_exec: ")
+
+ deviceRoot := "/data/local/tmp/"
+ if v := os.Getenv("DEVICEDIR"); v != "" {
+ deviceRoot = v
+ }
+
+ pkg := "golang.org/x/mobile"
+ if v := os.Getenv("PKG"); v != "" {
+ pkg = v
+ }
+
+ // Binary names can conflict.
+ // E.g. template.test from the {html,text}/template packages.
+ binName := filepath.Base(os.Args[1])
+ deviceBin := fmt.Sprintf("%s/%s-%d", deviceRoot, binName, os.Getpid())
+
+ // 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", "-p", os.Args[1], deviceBin+"-tmp")
+ run("shell", "cp '"+deviceBin+"-tmp' '"+deviceBin+"'")
+ run("shell", "rm '"+deviceBin+"-tmp'")
+
+ cwd, err := os.Getwd()
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ subdir, err := rel(cwd, pkg)
+ if err != nil {
+ log.Fatal(err)
+ }
+ subdir = filepath.Join(deviceRoot, pkg, subdir)
+
+ // The adb shell command will return an exit code of 0 regardless
+ // of the command run. E.g.
+ // $ adb shell false
+ // $ echo $?
+ // 0
+ // https://code.google.com/p/android/issues/detail?id=3254
+ // So we append the exitcode to the output and parse it from there.
+ t := template.Must(template.New("cmd").Parse(
+ `export TMPDIR={{.Root}}/tmp; \
+ mkdir -p "$TMPDIR"; \
+ cd "{{.SubDir}}"; \
+ {{.Bin}} {{.Args}}; \
+ echo -n {{.ExitStr}}$?`))
+
+ var cmd bytes.Buffer
+ const exitstr = "exitcode="
+ if err := t.Execute(&cmd, struct {
+ Root, SubDir, Bin, Args, ExitStr string
+ }{
+ Root: deviceRoot,
+ SubDir: subdir,
+ Bin: deviceBin,
+ Args: strings.Join(os.Args[2:], " "),
+ ExitStr: exitstr,
+ }); err != nil {
+ log.Panicf("template error: %v", err)
+ }
+
+ output := run("shell", cmd.String())
+ output = output[strings.LastIndex(output, "\n")+1:]
+
+ if !strings.HasPrefix(output, exitstr) {
+ log.Fatalf("no exit code: %q", output)
+ }
+ code, err := strconv.Atoi(output[len(exitstr):])
+ if err != nil {
+ log.Fatalf("bad exit code: %v", err)
+ }
+ os.Exit(code)
+}