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 |