| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237 |
- // 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"
- "image"
- "image/color"
- _ "image/png"
- "net"
- "os"
- "os/exec"
- "strings"
- "testing"
- "time"
- "golang.org/x/mobile/app/internal/apptest"
- "golang.org/x/mobile/event/size"
- )
- // 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) {
- t.Skip("see issue #23835")
- if _, err := exec.Command("which", "adb").CombinedOutput(); err != nil {
- t.Skip("command adb not found, skipping")
- }
- devicesTxt, err := exec.Command("adb", "devices").CombinedOutput()
- if err != nil {
- t.Errorf("adb devices failed: %v: %v", err, devicesTxt)
- }
- deviceCount := 0
- for _, d := range strings.Split(strings.TrimSpace(string(devicesTxt)), "\n") {
- if strings.Contains(d, "List of devices") {
- continue
- }
- // TODO(crawshaw): I believe some unusable devices can appear in the
- // list with note on them, but I cannot reproduce this right now.
- deviceCount++
- }
- if deviceCount == 0 {
- t.Skip("no android devices attached")
- }
- run(t, "gomobile", "version")
- origWD, err := os.Getwd()
- if err != nil {
- t.Fatal(err)
- }
- tmpdir, err := os.MkdirTemp("", "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)
- const (
- rotationPortrait = "0"
- rotationLandscape = "1"
- )
- rotate := func(rotation string) {
- run(t, "adb", "shell", "content", "insert", "--uri", "content://settings/system", "--bind", "name:s:user_rotation", "--bind", "value:i:"+rotation)
- }
- // turn off automatic rotation and start in portrait
- run(t, "adb", "shell", "content", "insert", "--uri", "content://settings/system", "--bind", "name:s:accelerometer_rotation", "--bind", "value:i:0")
- rotate(rotationPortrait)
- // 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
- var orientation size.Orientation
- comm.Recv("hello_from_testapp")
- comm.Send("hello_from_host")
- comm.Recv("lifecycle_visible")
- comm.Recv("size", &pixelsPerPt, &orientation)
- if pixelsPerPt < 0.1 {
- t.Fatalf("bad pixelsPerPt: %f", pixelsPerPt)
- }
- // A single paint event is sent when the lifecycle enters
- // StageVisible, and after the end of a touch event.
- var color string
- comm.Recv("paint", &color)
- // Ignore the first paint color, it may be slow making it to the screen.
- rotate(rotationLandscape)
- comm.Recv("size", &pixelsPerPt, &orientation)
- if want := size.OrientationLandscape; orientation != want {
- t.Errorf("want orientation %d, got %d", want, orientation)
- }
- var x, y int
- var ty string
- tap(t, 50, 260)
- comm.Recv("touch", &ty, &x, &y)
- if ty != "begin" || x != 50 || y != 260 {
- t.Errorf("want touch begin(50, 260), got %s(%d,%d)", ty, x, y)
- }
- comm.Recv("touch", &ty, &x, &y)
- if ty != "end" || x != 50 || y != 260 {
- t.Errorf("want touch end(50, 260), got %s(%d,%d)", ty, x, y)
- }
- comm.Recv("paint", &color)
- if gotColor := currentColor(t); color != gotColor {
- t.Errorf("app reports color %q, but saw %q", color, gotColor)
- }
- rotate(rotationPortrait)
- comm.Recv("size", &pixelsPerPt, &orientation)
- if want := size.OrientationPortrait; orientation != want {
- t.Errorf("want orientation %d, got %d", want, orientation)
- }
- tap(t, 50, 260)
- comm.Recv("touch", &ty, &x, &y) // touch begin
- comm.Recv("touch", &ty, &x, &y) // touch end
- comm.Recv("paint", &color)
- if gotColor := currentColor(t); color != gotColor {
- t.Errorf("app reports color %q, but saw %q", color, gotColor)
- }
- // TODO: lifecycle testing (NOTE: adb shell input keyevent 4 is the back button)
- }
- func currentColor(t *testing.T) string {
- file := fmt.Sprintf("app-screen-%d.png", time.Now().Unix())
- run(t, "adb", "shell", "screencap", "-p", "/data/local/tmp/"+file)
- run(t, "adb", "pull", "/data/local/tmp/"+file)
- run(t, "adb", "shell", "rm", "/data/local/tmp/"+file)
- defer os.Remove(file)
- f, err := os.Open(file)
- if err != nil {
- t.Errorf("currentColor: cannot open screencap: %v", err)
- return ""
- }
- m, _, err := image.Decode(f)
- if err != nil {
- t.Errorf("currentColor: cannot decode screencap: %v", err)
- return ""
- }
- var center color.Color
- {
- b := m.Bounds()
- x, y := b.Min.X+(b.Max.X-b.Min.X)/2, b.Min.Y+(b.Max.Y-b.Min.Y)/2
- center = m.At(x, y)
- }
- r, g, b, _ := center.RGBA()
- switch {
- case r == 0xffff && g == 0x0000 && b == 0x0000:
- return "red"
- case r == 0x0000 && g == 0xffff && b == 0x0000:
- return "green"
- case r == 0x0000 && g == 0x0000 && b == 0xffff:
- return "blue"
- default:
- return fmt.Sprintf("indeterminate: %v", center)
- }
- }
- 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)
- }
- }
|