OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 /* |
| 6 Run a Skia app to completion, piping the log to stdout. |
| 7 */ |
| 8 |
| 9 package main |
| 10 |
| 11 import ( |
| 12 "flag" |
| 13 "fmt" |
| 14 "io" |
| 15 "os" |
| 16 "os/exec" |
| 17 "os/signal" |
| 18 "strconv" |
| 19 "strings" |
| 20 "time" |
| 21 |
| 22 "github.com/skia-dev/glog" |
| 23 "go.skia.org/infra/go/common" |
| 24 ) |
| 25 |
| 26 const ( |
| 27 COM_SKIA = "com.skia" |
| 28 ) |
| 29 |
| 30 var ( |
| 31 adbPath = flag.String("adb", "", "Path to the ADB executable.") |
| 32 app = flag.String("app", "VisualBench", "Name of the app to run.") |
| 33 serial = flag.String("s", "", "Serial number for the Android device to
use.") |
| 34 ) |
| 35 |
| 36 // Struct used for running ADB commands. |
| 37 type ADB struct { |
| 38 path string |
| 39 serial string |
| 40 } |
| 41 |
| 42 // ADBFromFlags returns an ADB instance based on flags passed to the program. |
| 43 func ADBFromFlags() *ADB { |
| 44 rv := &ADB{ |
| 45 path: *adbPath, |
| 46 serial: *serial, |
| 47 } |
| 48 if rv.path == "" { |
| 49 rv.path = "adb" |
| 50 } |
| 51 return rv |
| 52 } |
| 53 |
| 54 // Cmd creates an exec.Cmd object for the given ADB command. |
| 55 func (adb *ADB) Cmd(log bool, args ...string) *exec.Cmd { |
| 56 cmd := []string{} |
| 57 if adb.serial != "" { |
| 58 cmd = append(cmd, "-s", adb.serial) |
| 59 } |
| 60 cmd = append(cmd, args...) |
| 61 if log { |
| 62 glog.Infof("Exec `%s %s`", adb.path, strings.Join(cmd, " ")) |
| 63 } |
| 64 return exec.Command(adb.path, cmd...) |
| 65 } |
| 66 |
| 67 // KillSkia kills any running process. |
| 68 func (adb *ADB) Kill(proc string) error { |
| 69 return adb.Cmd(false, "shell", "am", "force-stop", proc).Run() |
| 70 } |
| 71 |
| 72 // PollSkia checks to see if the given process is running. If so, return the pid
, otherwise return -1. |
| 73 func (adb *ADB) Poll(proc string) (int64, error) { |
| 74 output, err := adb.Cmd(false, "shell", "ps").Output() |
| 75 if err != nil { |
| 76 return -1, fmt.Errorf("Failed to check the running processes on
the device: %v", err) |
| 77 } |
| 78 for _, line := range strings.Split(string(output), "\n") { |
| 79 if strings.Contains(line, proc) { |
| 80 fields := strings.Fields(line) |
| 81 pid, err := strconv.ParseInt(fields[1], 10, 32) |
| 82 if err != nil { |
| 83 return -1, fmt.Errorf("Failed to parse \"%s\" to
an integer: %v", fields[1], err) |
| 84 } |
| 85 return pid, nil |
| 86 } |
| 87 } |
| 88 return -1, nil |
| 89 } |
| 90 |
| 91 // ReadLinesFromPipe reads from the given pipe and logs its output. |
| 92 func ReadLinesFromPipe(pipe io.Reader, lines chan string) { |
| 93 buf := []byte{} |
| 94 |
| 95 // writeLine recovers from a panic when writing to the channel. |
| 96 writeLine := func(s string) { |
| 97 defer func() { |
| 98 if r := recover(); r != nil { |
| 99 glog.Warningf("Panic writing to channel... are w
e exiting?") |
| 100 } |
| 101 }() |
| 102 lines <- s |
| 103 } |
| 104 |
| 105 // readLines reads output lines from the buffer and pushes them into the
channel. |
| 106 readlines := func() { |
| 107 readIdx := 0 |
| 108 // Read lines from buf. |
| 109 for i, b := range buf { |
| 110 if b == '\n' { |
| 111 s := string(buf[readIdx:i]) |
| 112 writeLine(s) |
| 113 readIdx = i + 1 |
| 114 } |
| 115 } |
| 116 // Remove the lines we read. |
| 117 buf = buf[readIdx:] |
| 118 } |
| 119 |
| 120 // Loop forever, reading bytes from the pipe. |
| 121 readbuf := make([]byte, 4096) |
| 122 for { |
| 123 read, err := pipe.Read(readbuf) |
| 124 if read > 0 { |
| 125 buf = append(buf, readbuf[:read]...) |
| 126 |
| 127 // Read lines from the buffers. |
| 128 readlines() |
| 129 } |
| 130 if err != nil { |
| 131 if err == io.EOF { |
| 132 break |
| 133 } else { |
| 134 glog.Error(err) |
| 135 } |
| 136 } else if read == 0 { |
| 137 time.Sleep(20 * time.Millisecond) |
| 138 } |
| 139 } |
| 140 // Read any remaining lines from the buffers. |
| 141 readlines() |
| 142 // "Flush" any incomplete lines from the buffers. |
| 143 writeLine(string(buf)) |
| 144 } |
| 145 |
| 146 // RunApp launches the given app on the device, pipes its log output to stdout, |
| 147 // and returns when the app finishes running, with an error if the app did not |
| 148 // complete successfully. |
| 149 func RunApp(adb *ADB, appName string, args []string) error { |
| 150 // Kill any running instances of the app. |
| 151 if err := adb.Kill(COM_SKIA); err != nil { |
| 152 return fmt.Errorf("Failed to kill app: %v", err) |
| 153 } |
| 154 |
| 155 // Clear the ADB log. |
| 156 if err := adb.Cmd(false, "logcat", "-c").Run(); err != nil { |
| 157 return fmt.Errorf("Failed to clear ADB log: %v", err) |
| 158 } |
| 159 |
| 160 // Prepare to read the subprocess output. |
| 161 logProc := adb.Cmd(false, "logcat") |
| 162 defer func() { |
| 163 // Cleanup. |
| 164 if err := logProc.Process.Kill(); err != nil { |
| 165 glog.Errorf("Failed to kill logcat process: %v", err) |
| 166 } |
| 167 }() |
| 168 |
| 169 stdout, err := logProc.StdoutPipe() |
| 170 if err != nil { |
| 171 return fmt.Errorf("Failed to obtain stdout pipe: %v", err) |
| 172 } |
| 173 stdoutLines := make(chan string) |
| 174 stderr, err := logProc.StderrPipe() |
| 175 if err != nil { |
| 176 return fmt.Errorf("Failed to obtain stderr pipe: %v", err) |
| 177 } |
| 178 stderrLines := make(chan string) |
| 179 |
| 180 go ReadLinesFromPipe(stdout, stdoutLines) |
| 181 go ReadLinesFromPipe(stderr, stderrLines) |
| 182 if err := logProc.Start(); err != nil { |
| 183 return fmt.Errorf("Failed to start logcat process.") |
| 184 } |
| 185 |
| 186 // Launch the app. |
| 187 stop := make(chan bool, 1) |
| 188 activity := fmt.Sprintf("%s.%s/%s.%sActivity", COM_SKIA, strings.ToLower
(appName), COM_SKIA, appName) |
| 189 flags := strings.Join(args, " ") |
| 190 cmd := []string{"shell", "am", "start", "-S", "-n", activity, "--es", "c
mdLineFlags", flags} |
| 191 output, err := adb.Cmd(true, cmd...).Output() |
| 192 if err != nil { |
| 193 return fmt.Errorf("Failed to launch app: %v", err) |
| 194 } |
| 195 glog.Info(string(output)) |
| 196 // Make a handler for SIGINT so that we can kill the app if this script
is interrupted. |
| 197 go func() { |
| 198 interrupt := make(chan os.Signal, 1) |
| 199 signal.Notify(interrupt, os.Interrupt) |
| 200 for _ = range interrupt { |
| 201 glog.Infof("Received SIGINT; killing app.") |
| 202 stop <- true |
| 203 close(stdoutLines) |
| 204 close(stderrLines) |
| 205 if err := adb.Kill(COM_SKIA); err != nil { |
| 206 glog.Errorf("Failed to kill app: %v", err) |
| 207 } |
| 208 if err := logProc.Process.Kill(); err != nil { |
| 209 glog.Errorf("Failed to kill logcat process: %v",
err) |
| 210 } |
| 211 } |
| 212 }() |
| 213 |
| 214 // Loop until the app finishes or we're interrupted, writing output as a
ppropriate. |
| 215 // TODO(borenet): VisualBench should print its exit code. This script sh
ould exit |
| 216 // with that code, or a non-zero code if the process ended without print
ing any code. |
| 217 second := time.Tick(time.Second) |
| 218 PollLoop: |
| 219 for { |
| 220 select { |
| 221 case <-second: |
| 222 // Poll the Skia process every second to make sure it's
still running. |
| 223 pid, err := adb.Poll(COM_SKIA) |
| 224 if err != nil { |
| 225 glog.Errorf("Failed to poll Skia process: %v", e
rr) |
| 226 } else if pid < 0 { |
| 227 glog.Infof("Skia process is no longer running!") |
| 228 break PollLoop |
| 229 } |
| 230 case <-stop: |
| 231 break PollLoop |
| 232 case line := <-stdoutLines: |
| 233 glog.Info(line) |
| 234 case line := <-stderrLines: |
| 235 glog.Error(line) |
| 236 } |
| 237 } |
| 238 |
| 239 return nil |
| 240 } |
| 241 |
| 242 func main() { |
| 243 common.Init() |
| 244 args := flag.Args() |
| 245 if err := RunApp(ADBFromFlags(), *app, args); err != nil { |
| 246 glog.Fatal(err) |
| 247 } |
| 248 } |
OLD | NEW |