app_test.go 6.3 KB

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