github.com/shranet/mobile@v0.0.0-20200814083559-5702cdcd481b/app/app_test.go (about)

     1  // Copyright 2015 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package app_test
     6  
     7  import (
     8  	"fmt"
     9  	"image"
    10  	"image/color"
    11  	_ "image/png"
    12  	"io/ioutil"
    13  	"net"
    14  	"os"
    15  	"os/exec"
    16  	"strings"
    17  	"testing"
    18  	"time"
    19  
    20  	"github.com/shranet/mobile/app/internal/apptest"
    21  	"github.com/shranet/mobile/event/size"
    22  )
    23  
    24  // TestAndroidApp tests the lifecycle, event, and window semantics of a
    25  // simple android app.
    26  //
    27  // Beyond testing the app package, the goal is to eventually have
    28  // helper libraries that make tests like these easy to write. Hopefully
    29  // having a user of such a fictional package will help illuminate the way.
    30  func TestAndroidApp(t *testing.T) {
    31  	t.Skip("see issue #23835")
    32  	if _, err := exec.Command("which", "adb").CombinedOutput(); err != nil {
    33  		t.Skip("command adb not found, skipping")
    34  	}
    35  	devicesTxt, err := exec.Command("adb", "devices").CombinedOutput()
    36  	if err != nil {
    37  		t.Errorf("adb devices failed: %v: %v", err, devicesTxt)
    38  	}
    39  	deviceCount := 0
    40  	for _, d := range strings.Split(strings.TrimSpace(string(devicesTxt)), "\n") {
    41  		if strings.Contains(d, "List of devices") {
    42  			continue
    43  		}
    44  		// TODO(crawshaw): I believe some unusable devices can appear in the
    45  		// list with note on them, but I cannot reproduce this right now.
    46  		deviceCount++
    47  	}
    48  	if deviceCount == 0 {
    49  		t.Skip("no android devices attached")
    50  	}
    51  
    52  	run(t, "gomobile", "version")
    53  
    54  	origWD, err := os.Getwd()
    55  	if err != nil {
    56  		t.Fatal(err)
    57  	}
    58  
    59  	tmpdir, err := ioutil.TempDir("", "app-test-")
    60  	if err != nil {
    61  		t.Fatal(err)
    62  	}
    63  	defer os.RemoveAll(tmpdir)
    64  
    65  	if err := os.Chdir(tmpdir); err != nil {
    66  		t.Fatal(err)
    67  	}
    68  	defer os.Chdir(origWD)
    69  
    70  	run(t, "gomobile", "install", "github.com/shranet/mobile/app/internal/testapp")
    71  
    72  	ln, err := net.Listen("tcp4", "localhost:0")
    73  	if err != nil {
    74  		t.Fatal(err)
    75  	}
    76  	defer ln.Close()
    77  	localaddr := fmt.Sprintf("tcp:%d", ln.Addr().(*net.TCPAddr).Port)
    78  	t.Logf("local address: %s", localaddr)
    79  
    80  	exec.Command("adb", "reverse", "--remove", "tcp:"+apptest.Port).Run() // ignore failure
    81  	run(t, "adb", "reverse", "tcp:"+apptest.Port, localaddr)
    82  
    83  	const (
    84  		KeycodePower  = "26"
    85  		KeycodeUnlock = "82"
    86  	)
    87  
    88  	run(t, "adb", "shell", "input", "keyevent", KeycodePower)
    89  	run(t, "adb", "shell", "input", "keyevent", KeycodeUnlock)
    90  
    91  	const (
    92  		rotationPortrait  = "0"
    93  		rotationLandscape = "1"
    94  	)
    95  
    96  	rotate := func(rotation string) {
    97  		run(t, "adb", "shell", "content", "insert", "--uri", "content://settings/system", "--bind", "name:s:user_rotation", "--bind", "value:i:"+rotation)
    98  	}
    99  
   100  	// turn off automatic rotation and start in portrait
   101  	run(t, "adb", "shell", "content", "insert", "--uri", "content://settings/system", "--bind", "name:s:accelerometer_rotation", "--bind", "value:i:0")
   102  	rotate(rotationPortrait)
   103  
   104  	// start testapp
   105  	run(t,
   106  		"adb", "shell", "am", "start", "-n",
   107  		"org.golang.testapp/org.golang.app.GoNativeActivity",
   108  	)
   109  
   110  	var conn net.Conn
   111  	connDone := make(chan struct{})
   112  	go func() {
   113  		conn, err = ln.Accept()
   114  		connDone <- struct{}{}
   115  	}()
   116  
   117  	select {
   118  	case <-time.After(5 * time.Second):
   119  		t.Fatal("timeout waiting for testapp to dial host")
   120  	case <-connDone:
   121  		if err != nil {
   122  			t.Fatalf("ln.Accept: %v", err)
   123  		}
   124  	}
   125  	defer conn.Close()
   126  	comm := &apptest.Comm{
   127  		Conn:   conn,
   128  		Fatalf: t.Fatalf,
   129  		Printf: t.Logf,
   130  	}
   131  
   132  	var pixelsPerPt float32
   133  	var orientation size.Orientation
   134  
   135  	comm.Recv("hello_from_testapp")
   136  	comm.Send("hello_from_host")
   137  	comm.Recv("lifecycle_visible")
   138  	comm.Recv("size", &pixelsPerPt, &orientation)
   139  	if pixelsPerPt < 0.1 {
   140  		t.Fatalf("bad pixelsPerPt: %f", pixelsPerPt)
   141  	}
   142  
   143  	// A single paint event is sent when the lifecycle enters
   144  	// StageVisible, and after the end of a touch event.
   145  	var color string
   146  	comm.Recv("paint", &color)
   147  	// Ignore the first paint color, it may be slow making it to the screen.
   148  
   149  	rotate(rotationLandscape)
   150  	comm.Recv("size", &pixelsPerPt, &orientation)
   151  	if want := size.OrientationLandscape; orientation != want {
   152  		t.Errorf("want orientation %d, got %d", want, orientation)
   153  	}
   154  
   155  	var x, y int
   156  	var ty string
   157  
   158  	tap(t, 50, 260)
   159  	comm.Recv("touch", &ty, &x, &y)
   160  	if ty != "begin" || x != 50 || y != 260 {
   161  		t.Errorf("want touch begin(50, 260), got %s(%d,%d)", ty, x, y)
   162  	}
   163  	comm.Recv("touch", &ty, &x, &y)
   164  	if ty != "end" || x != 50 || y != 260 {
   165  		t.Errorf("want touch end(50, 260), got %s(%d,%d)", ty, x, y)
   166  	}
   167  
   168  	comm.Recv("paint", &color)
   169  	if gotColor := currentColor(t); color != gotColor {
   170  		t.Errorf("app reports color %q, but saw %q", color, gotColor)
   171  	}
   172  
   173  	rotate(rotationPortrait)
   174  	comm.Recv("size", &pixelsPerPt, &orientation)
   175  	if want := size.OrientationPortrait; orientation != want {
   176  		t.Errorf("want orientation %d, got %d", want, orientation)
   177  	}
   178  
   179  	tap(t, 50, 260)
   180  	comm.Recv("touch", &ty, &x, &y) // touch begin
   181  	comm.Recv("touch", &ty, &x, &y) // touch end
   182  	comm.Recv("paint", &color)
   183  	if gotColor := currentColor(t); color != gotColor {
   184  		t.Errorf("app reports color %q, but saw %q", color, gotColor)
   185  	}
   186  
   187  	// TODO: lifecycle testing (NOTE: adb shell input keyevent 4 is the back button)
   188  }
   189  
   190  func currentColor(t *testing.T) string {
   191  	file := fmt.Sprintf("app-screen-%d.png", time.Now().Unix())
   192  
   193  	run(t, "adb", "shell", "screencap", "-p", "/data/local/tmp/"+file)
   194  	run(t, "adb", "pull", "/data/local/tmp/"+file)
   195  	run(t, "adb", "shell", "rm", "/data/local/tmp/"+file)
   196  	defer os.Remove(file)
   197  
   198  	f, err := os.Open(file)
   199  	if err != nil {
   200  		t.Errorf("currentColor: cannot open screencap: %v", err)
   201  		return ""
   202  	}
   203  	m, _, err := image.Decode(f)
   204  	if err != nil {
   205  		t.Errorf("currentColor: cannot decode screencap: %v", err)
   206  		return ""
   207  	}
   208  	var center color.Color
   209  	{
   210  		b := m.Bounds()
   211  		x, y := b.Min.X+(b.Max.X-b.Min.X)/2, b.Min.Y+(b.Max.Y-b.Min.Y)/2
   212  		center = m.At(x, y)
   213  	}
   214  	r, g, b, _ := center.RGBA()
   215  	switch {
   216  	case r == 0xffff && g == 0x0000 && b == 0x0000:
   217  		return "red"
   218  	case r == 0x0000 && g == 0xffff && b == 0x0000:
   219  		return "green"
   220  	case r == 0x0000 && g == 0x0000 && b == 0xffff:
   221  		return "blue"
   222  	default:
   223  		return fmt.Sprintf("indeterminate: %v", center)
   224  	}
   225  }
   226  
   227  func tap(t *testing.T, x, y int) {
   228  	run(t, "adb", "shell", "input", "tap", fmt.Sprintf("%d", x), fmt.Sprintf("%d", y))
   229  }
   230  
   231  func run(t *testing.T, cmdName string, arg ...string) {
   232  	cmd := exec.Command(cmdName, arg...)
   233  	t.Log(strings.Join(cmd.Args, " "))
   234  	out, err := cmd.CombinedOutput()
   235  	if err != nil {
   236  		t.Fatalf("%s %v: %s", strings.Join(cmd.Args, " "), err, out)
   237  	}
   238  }