OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 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 package org.chromium.test.outstrumentation.master; | |
6 | |
7 import android.app.Activity; | |
8 import android.app.Instrumentation; | |
9 import android.content.ComponentName; | |
10 import android.content.Intent; | |
11 import android.content.ServiceConnection; | |
12 import android.os.Bundle; | |
13 import android.os.Environment; | |
14 import android.os.Handler; | |
15 import android.os.IBinder; | |
16 import android.os.Looper; | |
17 import android.os.Message; | |
18 import android.os.Messenger; | |
19 import android.os.RemoteException; | |
20 import android.util.Log; | |
21 | |
22 import org.chromium.test.outstrumentation.reporter.ReportingService; | |
23 import org.chromium.test.outstrumentation.slave.OutstrumentationSlaveActivity; | |
24 import org.chromium.test.support.ResultsBundleGenerator; | |
25 import org.chromium.test.support.RobotiumBundleGenerator; | |
26 | |
27 import java.io.BufferedReader; | |
28 import java.io.File; | |
29 import java.io.FileReader; | |
30 import java.io.IOException; | |
31 import java.util.ArrayList; | |
32 import java.util.Arrays; | |
33 import java.util.HashMap; | |
34 import java.util.List; | |
35 import java.util.Map; | |
36 import java.util.regex.Pattern; | |
37 | |
38 /** | |
39 * An Instrumentation that drives instrumentation tests from outside the app. (H ence the name.) | |
40 */ | |
41 public class Outstrumentation extends Instrumentation { | |
42 | |
43 private static final String TAG = "Outstrumentation"; | |
44 | |
45 private static final String EXTRA_TEST_LIST = | |
46 "org.chromium.test.outstrumentation.master.Outstrumentation.TestList "; | |
47 private static final String EXTRA_TEST_LIST_FILE = | |
48 "org.chromium.test.outstrumentation.master.Outstrumentation.TestList File"; | |
49 private static final String EXTRA_TARGET_PACKAGE = | |
50 "org.chromium.test.outstrumentation.master.Outstrumentation.TargetPa ckage"; | |
51 private static final String EXTRA_TARGET_CLASS = | |
52 "org.chromium.test.outstrumentation.master.Outstrumentation.TargetCl ass"; | |
53 | |
54 private static final Pattern COMMA = Pattern.compile(","); | |
55 | |
56 private Bundle mTargetArgs; | |
57 private String mTargetClass; | |
58 private String mTargetPackage; | |
59 private List<String> mTestClasses; | |
60 | |
61 @Override | |
62 public void onCreate(Bundle arguments) { | |
63 mTargetArgs = new Bundle(arguments); | |
64 mTargetPackage = arguments.getString(EXTRA_TARGET_PACKAGE); | |
65 if (mTargetPackage == null) { | |
66 fail("No target package."); | |
67 return; | |
68 } | |
69 mTargetArgs.remove(EXTRA_TARGET_PACKAGE); | |
Ted C
2015/04/06 17:43:54
why remove things?
jbudorick
2015/04/07 00:54:38
I want Outstrumentation (% name change) to forward
| |
70 | |
71 mTargetClass = arguments.getString(EXTRA_TARGET_CLASS); | |
72 if (mTargetClass == null) { | |
73 fail("No target class."); | |
74 return; | |
75 } | |
76 mTargetArgs.remove(EXTRA_TARGET_CLASS); | |
77 | |
78 mTestClasses = new ArrayList<String>(); | |
79 String testList = arguments.getString(EXTRA_TEST_LIST); | |
80 if (testList != null) { | |
81 mTestClasses.addAll(Arrays.asList(COMMA.split(testList))); | |
82 mTargetArgs.remove(EXTRA_TEST_LIST); | |
83 } | |
84 | |
85 String testListFilePath = arguments.getString(EXTRA_TEST_LIST_FILE); | |
86 if (testListFilePath != null) { | |
87 File testListFile = new File(Environment.getExternalStorageDirectory (), | |
88 testListFilePath); | |
Ted C
2015/04/06 17:43:54
java indenting is 8 from the previous line
jbudorick
2015/04/07 00:54:39
Done.
| |
89 try { | |
90 BufferedReader testListFileReader = | |
91 new BufferedReader(new FileReader(testListFile)); | |
92 while (true) { | |
Ted C
2015/04/06 17:43:54
nit: I normally see this as:
String test;
while (
jbudorick
2015/04/07 00:54:39
Done.
| |
93 String test = testListFileReader.readLine(); | |
94 if (test == null) break; | |
95 mTestClasses.add(test); | |
96 } | |
97 testListFileReader.close(); | |
98 } catch (IOException e) { | |
99 Log.e(TAG, "Error reading " + testListFile.getAbsolutePath(), e) ; | |
100 } | |
101 mTargetArgs.remove(EXTRA_TEST_LIST_FILE); | |
102 } | |
103 | |
104 if (mTestClasses.isEmpty()) { | |
105 fail("No tests."); | |
106 return; | |
107 } | |
108 | |
109 start(); | |
110 } | |
111 | |
112 @Override | |
113 public void onStart() { | |
114 super.onStart(); | |
115 | |
116 getContext().startService(new Intent(getContext(), ReportingService.clas s)); | |
117 | |
118 // Start the driver on its own thread s.t. it can block while the main t hread's | |
119 // Looper receives and handles messages. | |
120 Thread driverThread = new Thread( | |
121 new Driver(mTargetPackage, mTargetClass, mTargetArgs, mTestClass es)); | |
122 driverThread.start(); | |
123 } | |
124 | |
125 @Override | |
126 public void onDestroy() { | |
Ted C
2015/04/06 17:43:54
onStop is the corollary to onStart. So the thread
jbudorick
2015/04/07 00:54:38
Moved thread creation to onCreate and added a bool
| |
127 getContext().stopService(new Intent(getContext(), ReportingService.class )); | |
128 super.onDestroy(); | |
129 } | |
130 | |
131 private class Driver implements Runnable { | |
132 | |
133 private static final String TAG = Outstrumentation.TAG + ".Driver"; | |
134 | |
135 private Messenger mReportingService; | |
Ted C
2015/04/06 17:43:54
why not declared inside of ReportingServiceConnect
jbudorick
2015/04/07 00:54:39
I'm not actually sure why I did that; I imagine th
| |
136 private Object mServiceLock = new Object(); | |
137 private Bundle mTargetArgs; | |
138 private String mTargetClass; | |
139 private String mTargetPackage; | |
140 private List<String> mTestClasses; | |
141 private ReportHandler mHandler; | |
142 | |
143 public Driver(String targetPackage, String targetClass, Bundle targetArg s, | |
144 List<String> testClasses) { | |
145 mTargetPackage = targetPackage; | |
146 mTargetClass = targetClass; | |
147 mTargetArgs = targetArgs; | |
148 mTestClasses = testClasses; | |
149 mHandler = new ReportHandler(Looper.getMainLooper()); | |
150 } | |
151 | |
152 private class ReportingServiceConnection implements ServiceConnection { | |
153 | |
154 private static final String TAG = Driver.TAG + ".ReportingServiceCon nection"; | |
155 | |
156 @Override | |
157 public void onServiceConnected(ComponentName name, IBinder service) { | |
158 // Register receiver. | |
159 Log.i(TAG, "service connected"); | |
160 synchronized (mServiceLock) { | |
161 mReportingService = new Messenger(service); | |
162 mServiceLock.notify(); | |
163 } | |
164 } | |
165 | |
166 public void registerWithReportingService(Messenger receiver) | |
Ted C
2015/04/06 17:43:54
javadoc all the public methods (and in ReportHandl
jbudorick
2015/04/07 00:54:39
Done.
| |
167 throws InterruptedException, RemoteException { | |
168 synchronized (mServiceLock) { | |
169 Log.i(TAG, "acquired service lock"); | |
170 while (mReportingService == null) { | |
171 Log.i(TAG, "waiting for service connection."); | |
172 mServiceLock.wait(); | |
173 } | |
174 | |
175 Log.i(TAG, "reporting service non-null"); | |
176 | |
177 Message registration = Message.obtain(); | |
178 registration.what = ReportingService.MSG_REGISTER_RECEIVER; | |
179 registration.replyTo = receiver; | |
180 mReportingService.send(registration); | |
181 | |
182 Log.i(TAG, "sent registration message"); | |
183 } | |
184 } | |
185 | |
186 public void unregisterWithReportingService() | |
187 throws InterruptedException, RemoteException { | |
188 synchronized (mServiceLock) { | |
189 if (mReportingService == null) return; | |
190 | |
191 Message registration = Message.obtain(); | |
192 registration.what = ReportingService.MSG_UNREGISTER_RECEIVER ; | |
193 mReportingService.send(registration); | |
194 } | |
195 } | |
196 | |
197 @Override | |
198 public void onServiceDisconnected(ComponentName name) { | |
199 synchronized (mServiceLock) { | |
200 mReportingService = null; | |
201 mServiceLock.notify(); | |
202 } | |
203 } | |
204 } | |
205 | |
206 private class ReportHandler extends Handler { | |
207 private static final String TAG = Driver.TAG + ".ReportHandler"; | |
208 | |
209 private final Object mLock = new Object(); | |
210 private final Map<String, ResultsBundleGenerator.TestResult> mFinish ed = | |
211 new HashMap<String, ResultsBundleGenerator.TestResult>(); | |
212 | |
213 public ReportHandler(Looper looper) { | |
214 super(looper); | |
215 } | |
216 | |
217 @Override | |
218 public void handleMessage(Message msg) { | |
219 switch (msg.what) { | |
220 case ReportingService.MSG_REPORT_TEST_STARTED: | |
221 case ReportingService.MSG_REPORT_TEST_PASSED: | |
222 case ReportingService.MSG_REPORT_TEST_FAILED: | |
223 handleTestStatusMessage(msg); | |
224 break; | |
225 default: | |
226 super.handleMessage(msg); | |
227 break; | |
228 } | |
229 } | |
230 | |
231 private void handleTestStatusMessage(Message msg) { | |
232 String testClass = msg.getData().getString(ReportingService.MSG_ DATA_TEST_CLASS); | |
233 String testMethod = msg.getData().getString(ReportingService.MSG _DATA_TEST_METHOD); | |
234 String testName = testClass + "#" + testMethod; | |
235 synchronized (mLock) { | |
236 switch (msg.what) { | |
237 case ReportingService.MSG_REPORT_TEST_STARTED: | |
238 testStarted(testClass, testMethod); | |
239 break; | |
240 case ReportingService.MSG_REPORT_TEST_PASSED: | |
241 mFinished.put(testName, ResultsBundleGenerator.TestR esult.PASSED); | |
242 testPassed(testClass, testMethod); | |
243 mLock.notify(); | |
244 break; | |
245 case ReportingService.MSG_REPORT_TEST_FAILED: | |
246 mFinished.put(testName, ResultsBundleGenerator.TestR esult.FAILED); | |
247 testFailed(testClass, testMethod); | |
248 mLock.notify(); | |
249 break; | |
250 default: | |
Ted C
2015/04/06 17:43:54
probably should fail in this case as it would indi
jbudorick
2015/04/07 00:54:39
Done.
| |
251 break; | |
252 } | |
253 } | |
254 } | |
255 | |
256 public void waitForTestFinished(String testName) throws InterruptedE xception { | |
257 synchronized (mLock) { | |
258 while (!mFinished.containsKey(testName)) { | |
259 mLock.wait(); | |
260 } | |
261 } | |
262 } | |
263 | |
264 public Bundle getResults() { | |
265 return new RobotiumBundleGenerator().generate(mFinished); | |
266 } | |
267 } | |
268 | |
269 @Override | |
270 public void run() { | |
271 try { | |
272 ReportingServiceConnection c = new ReportingServiceConnection(); | |
273 if (!getContext().bindService( | |
274 new Intent(getContext(), ReportingService.class), c, 0)) { | |
275 fail("Reporting service not bound."); | |
276 return; | |
277 } | |
278 | |
279 c.registerWithReportingService(new Messenger(mHandler)); | |
280 | |
281 for (String t : mTestClasses) { | |
282 Intent slaveIntent = new Intent(); | |
283 slaveIntent.setComponent( | |
284 new ComponentName(mTargetPackage, | |
285 OutstrumentationSlaveActivity.clas s.getName())); | |
Ted C
2015/04/06 17:43:54
same indent comment...8 from the previous line
jbudorick
2015/04/07 00:54:39
the troubles with jumping between languages
done.
| |
286 slaveIntent.putExtra( | |
287 OutstrumentationSlaveActivity.EXTRA_INSTRUMENTATION_ PACKAGE, | |
288 mTargetPackage); | |
289 slaveIntent.putExtra( | |
290 OutstrumentationSlaveActivity.EXTRA_INSTRUMENTATION_ CLASS, | |
291 mTargetClass); | |
292 slaveIntent.putExtra(OutstrumentationSlaveActivity.EXTRA_TES T, t); | |
293 slaveIntent.putExtra(OutstrumentationSlaveActivity.EXTRA_TAR GET_ARGS, | |
294 mTargetArgs); | |
295 slaveIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | |
296 | |
297 getContext().startActivity(slaveIntent); | |
298 | |
299 mHandler.waitForTestFinished(t); | |
300 } | |
301 | |
302 c.unregisterWithReportingService(); | |
303 getContext().unbindService(c); | |
304 } catch (RemoteException e) { | |
305 fail("Error connecting to reporting service.", e); | |
306 return; | |
307 } catch (InterruptedException e) { | |
308 fail("Interrupted while running tests.", e); | |
309 return; | |
310 } | |
311 pass(mHandler.getResults()); | |
312 } | |
313 | |
314 } | |
315 | |
316 private void fail(String reason) { | |
317 Log.e(TAG, reason); | |
318 failImpl(reason); | |
319 } | |
320 | |
321 private void fail(String reason, Exception e) { | |
322 Log.e(TAG, reason, e); | |
323 failImpl(reason); | |
324 } | |
325 | |
326 private void failImpl(String reason) { | |
327 Bundle b = new Bundle(); | |
328 b.putString("reason", reason); | |
329 finish(Activity.RESULT_CANCELED, b); | |
330 } | |
331 | |
332 private void pass(Bundle results) { | |
333 finish(Activity.RESULT_OK, results); | |
334 } | |
335 | |
336 private void testStarted(String testClass, String testMethod) { | |
337 Bundle status = new Bundle(); | |
338 status.putString("class", testClass); | |
339 status.putString("test", testMethod); | |
340 sendStatus(1, status); | |
Ted C
2015/04/06 17:43:54
where are these status values defined (1, 0, -1)?
jbudorick
2015/04/07 00:54:39
I had thought they were private constants in Instr
| |
341 } | |
342 | |
343 private void testPassed(String testClass, String testMethod) { | |
344 Bundle status = new Bundle(); | |
345 status.putString("class", testClass); | |
346 status.putString("test", testMethod); | |
347 sendStatus(0, status); | |
348 } | |
349 | |
350 private void testFailed(String testClass, String testMethod) { | |
351 Bundle status = new Bundle(); | |
352 status.putString("class", testClass); | |
353 status.putString("test", testMethod); | |
354 sendStatus(-1, status); | |
355 } | |
356 } | |
OLD | NEW |