app: basic android app lifecycle test
Change-Id: I3db2d631e9be50b34ee9ac6df9615575cc1d6e33 Reviewed-on: https://go-review.googlesource.com/12642 Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
This commit is contained in:
parent
af3e09ede0
commit
d691afa36e
|
@ -0,0 +1,138 @@
|
|||
// Copyright 2015 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.
|
||||
|
||||
package app_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/mobile/app/internal/apptest"
|
||||
)
|
||||
|
||||
// TestAndroidApp tests the lifecycle, event, and window semantics of a
|
||||
// simple android app.
|
||||
//
|
||||
// Beyond testing the app package, the goal is to eventually have
|
||||
// helper libraries that make tests like these easy to write. Hopefully
|
||||
// having a user of such a fictional package will help illuminate the way.
|
||||
func TestAndroidApp(t *testing.T) {
|
||||
if _, err := exec.Command("which", "adb").CombinedOutput(); err != nil {
|
||||
t.Skip("command adb not found, skipping")
|
||||
}
|
||||
|
||||
run(t, "gomobile", "version")
|
||||
|
||||
origWD, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "app-test-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
if err := os.Chdir(tmpdir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Chdir(origWD)
|
||||
|
||||
run(t, "gomobile", "install", "golang.org/x/mobile/app/internal/testapp")
|
||||
|
||||
ln, err := net.Listen("tcp4", "localhost:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer ln.Close()
|
||||
localaddr := fmt.Sprintf("tcp:%d", ln.Addr().(*net.TCPAddr).Port)
|
||||
t.Logf("local address: %s", localaddr)
|
||||
|
||||
exec.Command("adb", "reverse", "--remove", "tcp:"+apptest.Port).Run() // ignore failure
|
||||
run(t, "adb", "reverse", "tcp:"+apptest.Port, localaddr)
|
||||
|
||||
const (
|
||||
KeycodePower = "26"
|
||||
KeycodeUnlock = "82"
|
||||
)
|
||||
|
||||
run(t, "adb", "shell", "input", "keyevent", KeycodePower)
|
||||
run(t, "adb", "shell", "input", "keyevent", KeycodeUnlock)
|
||||
|
||||
// start testapp
|
||||
run(t,
|
||||
"adb", "shell", "am", "start", "-n",
|
||||
"org.golang.testapp/org.golang.app.GoNativeActivity",
|
||||
)
|
||||
|
||||
var conn net.Conn
|
||||
connDone := make(chan struct{})
|
||||
go func() {
|
||||
conn, err = ln.Accept()
|
||||
connDone <- struct{}{}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("timeout waiting for testapp to dial host")
|
||||
case <-connDone:
|
||||
if err != nil {
|
||||
t.Fatalf("ln.Accept: %v", err)
|
||||
}
|
||||
}
|
||||
defer conn.Close()
|
||||
comm := &apptest.Comm{
|
||||
Conn: conn,
|
||||
Fatalf: t.Fatalf,
|
||||
Printf: t.Logf,
|
||||
}
|
||||
|
||||
var PixelsPerPt float32
|
||||
|
||||
comm.Recv("hello_from_testapp")
|
||||
comm.Send("hello_from_host")
|
||||
comm.Recv("lifecycle_visible")
|
||||
comm.Recv("config", &PixelsPerPt)
|
||||
if PixelsPerPt < 0.1 {
|
||||
t.Fatalf("bad PixelsPerPt: %f", PixelsPerPt)
|
||||
}
|
||||
comm.Recv("paint")
|
||||
|
||||
var x, y int
|
||||
var ty string
|
||||
|
||||
tap(t, 50, 60)
|
||||
comm.Recv("touch", &ty, &x, &y)
|
||||
if ty != "begin" || x != 50 || y != 60 {
|
||||
t.Errorf("want touch begin(50, 60), got %s(%d,%d)", ty, x, y)
|
||||
}
|
||||
comm.Recv("touch", &ty, &x, &y)
|
||||
if ty != "end" || x != 50 || y != 60 {
|
||||
t.Errorf("want touch end(50, 60), got %s(%d,%d)", ty, x, y)
|
||||
}
|
||||
|
||||
// TODO: screenshot of gl.Clear to test painting
|
||||
// TODO: lifecycle testing (NOTE: adb shell input keyevent 4 is the back button)
|
||||
// TODO: orientation testing
|
||||
}
|
||||
|
||||
func tap(t *testing.T, x, y int) {
|
||||
run(t, "adb", "shell", "input", "tap", fmt.Sprintf("%d", x), fmt.Sprintf("%d", y))
|
||||
}
|
||||
|
||||
func run(t *testing.T, cmdName string, arg ...string) {
|
||||
cmd := exec.Command(cmdName, arg...)
|
||||
t.Log(strings.Join(cmd.Args, " "))
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("%s %v: %s", strings.Join(cmd.Args, " "), err, out)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
// Copyright 2015 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.
|
||||
|
||||
// Package apptest provides utilities for testing an app.
|
||||
//
|
||||
// It is extremely incomplete, hence it being internal.
|
||||
// For starters, it should support iOS.
|
||||
package apptest
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
// Port is the TCP port used to communicate with the test app.
|
||||
//
|
||||
// TODO(crawshaw): find a way to make this configurable. adb am extras?
|
||||
const Port = "12533"
|
||||
|
||||
// Comm is a simple text-based communication protocol.
|
||||
//
|
||||
// Assumes all sides are friendly and cooperative and that the
|
||||
// communication is over at the first sign of trouble.
|
||||
type Comm struct {
|
||||
Conn net.Conn
|
||||
Fatalf func(format string, args ...interface{})
|
||||
Printf func(format string, args ...interface{})
|
||||
|
||||
scanner *bufio.Scanner
|
||||
}
|
||||
|
||||
func (c *Comm) Send(cmd string, args ...interface{}) {
|
||||
buf := new(bytes.Buffer)
|
||||
buf.WriteString(cmd)
|
||||
for _, arg := range args {
|
||||
buf.WriteRune(' ')
|
||||
fmt.Fprintf(buf, "%v", arg)
|
||||
}
|
||||
buf.WriteRune('\n')
|
||||
b := buf.Bytes()
|
||||
c.Printf("comm.send: %s\n", b)
|
||||
if _, err := c.Conn.Write(b); err != nil {
|
||||
c.Fatalf("failed to send %s: %v", b, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Comm) Recv(cmd string, a ...interface{}) {
|
||||
if c.scanner == nil {
|
||||
c.scanner = bufio.NewScanner(c.Conn)
|
||||
}
|
||||
if !c.scanner.Scan() {
|
||||
c.Fatalf("failed to recv %q: %v", cmd, c.scanner.Err())
|
||||
}
|
||||
text := c.scanner.Text()
|
||||
c.Printf("comm.recv: %s\n", text)
|
||||
var recvCmd string
|
||||
args := append([]interface{}{&recvCmd}, a...)
|
||||
if _, err := fmt.Sscan(text, args...); err != nil {
|
||||
c.Fatalf("cannot scan recv command %s: %q: %v", cmd, text, err)
|
||||
}
|
||||
if cmd != recvCmd {
|
||||
c.Fatalf("expecting recv %q, got %v", cmd, text)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2015 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.
|
||||
-->
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.golang.testapp"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0">
|
||||
|
||||
<uses-sdk android:minSdkVersion="9" />
|
||||
<!-- to talk to the host -->
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<application android:label="testapp" android:debuggable="true">
|
||||
<activity android:name="org.golang.app.GoNativeActivity"
|
||||
android:label="testapp"
|
||||
android:configChanges="orientation|keyboardHidden">
|
||||
<meta-data android:name="android.app.lib_name" android:value="testapp" />
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
|
@ -0,0 +1,66 @@
|
|||
// Copyright 2015 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.
|
||||
|
||||
// Small test app used by app/app_test.go.
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"golang.org/x/mobile/app"
|
||||
"golang.org/x/mobile/app/internal/apptest"
|
||||
"golang.org/x/mobile/event/config"
|
||||
"golang.org/x/mobile/event/lifecycle"
|
||||
"golang.org/x/mobile/event/paint"
|
||||
"golang.org/x/mobile/event/touch"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app.Main(func(a app.App) {
|
||||
addr := "127.0.0.1:" + apptest.Port
|
||||
log.Printf("addr: %s", addr)
|
||||
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
log.Printf("dialled")
|
||||
comm := &apptest.Comm{
|
||||
Conn: conn,
|
||||
Fatalf: log.Panicf,
|
||||
Printf: log.Printf,
|
||||
}
|
||||
|
||||
comm.Send("hello_from_testapp")
|
||||
comm.Recv("hello_from_host")
|
||||
|
||||
sendPainting := false
|
||||
var c config.Event
|
||||
for e := range a.Events() {
|
||||
switch e := app.Filter(e).(type) {
|
||||
case lifecycle.Event:
|
||||
switch e.Crosses(lifecycle.StageVisible) {
|
||||
case lifecycle.CrossOn:
|
||||
comm.Send("lifecycle_visible")
|
||||
sendPainting = true
|
||||
case lifecycle.CrossOff:
|
||||
comm.Send("lifecycle_not_visible")
|
||||
}
|
||||
case config.Event:
|
||||
c = e
|
||||
comm.Send("config", c.PixelsPerPt)
|
||||
case paint.Event:
|
||||
if sendPainting {
|
||||
comm.Send("paint")
|
||||
sendPainting = false
|
||||
}
|
||||
a.EndPaint()
|
||||
case touch.Event:
|
||||
comm.Send("touch", e.Type, e.Loc.X.Px(c.PixelsPerPt), e.Loc.Y.Px(c.PixelsPerPt))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue