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