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