github.com/SkycoinProject/gomobile@v0.0.0-20190312151609-d3739f865fa6/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 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", "golang.org/x/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 }