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 |