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