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.driver; | |
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.test.InstrumentationTestRunner; | |
21 import android.util.Log; | |
22 | |
23 import org.chromium.test.passenger.OnDeviceInstrumentationPassenger; | |
24 import org.chromium.test.reporter.ReportingService; | |
25 import org.chromium.test.support.ResultsBundleGenerator; | |
26 import org.chromium.test.support.RobotiumBundleGenerator; | |
27 | |
28 import java.io.BufferedReader; | |
29 import java.io.File; | |
30 import java.io.FileReader; | |
31 import java.io.IOException; | |
32 import java.util.ArrayList; | |
33 import java.util.Arrays; | |
34 import java.util.HashMap; | |
35 import java.util.List; | |
36 import java.util.Map; | |
37 import java.util.regex.Pattern; | |
38 | |
39 /** | |
40 * An Instrumentation that drives instrumentation tests from outside the app. (H ence the name.) | |
Yaron
2015/04/07 15:39:04
I think you can now drop "(Hence the name.)"
jbudorick
2015/04/07 19:24:45
oops, done.
| |
41 */ | |
42 public class OnDeviceInstrumentationDriver extends Instrumentation { | |
jbudorick
2015/04/07 15:09:22
Renamed:
Outstrumentation -> OnDeviceInstrumenta
Yaron
2015/04/07 15:39:04
I think it would be helpful to have a mini-dd or o
jbudorick
2015/04/07 19:24:45
Can do. I've got it on a whiteboard right now and
| |
43 | |
44 private static final String TAG = "OnDeviceInstrumentationDriver"; | |
45 | |
46 private static final String EXTRA_TEST_LIST = | |
47 "org.chromium.test.driver.OnDeviceInstrumentationDriver.TestList"; | |
48 private static final String EXTRA_TEST_LIST_FILE = | |
49 "org.chromium.test.driver.OnDeviceInstrumentationDriver.TestListFile "; | |
50 private static final String EXTRA_TARGET_PACKAGE = | |
51 "org.chromium.test.driver.OnDeviceInstrumentationDriver.TargetPackag e"; | |
52 private static final String EXTRA_TARGET_CLASS = | |
53 "org.chromium.test.driver.OnDeviceInstrumentationDriver.TargetClass" ; | |
54 | |
55 private static final Pattern COMMA = Pattern.compile(","); | |
56 private static final int SERVICE_WAIT_TIMEOUT = 5000; // ms | |
57 private static final int TEST_WAIT_TIMEOUT = 10000; // ms | |
58 | |
59 private boolean mDriverStarted; | |
60 private Thread mDriverThread; | |
61 private Bundle mTargetArgs; | |
62 private String mTargetClass; | |
63 private String mTargetPackage; | |
64 private List<String> mTestClasses; | |
65 | |
66 /** Parse any arguments and prepare to run tests. | |
67 | |
68 @param arguments The arguments to parse. | |
69 */ | |
70 @Override | |
71 public void onCreate(Bundle arguments) { | |
72 mTargetArgs = new Bundle(arguments); | |
73 mTargetPackage = arguments.getString(EXTRA_TARGET_PACKAGE); | |
74 if (mTargetPackage == null) { | |
75 fail("No target package."); | |
76 return; | |
77 } | |
78 mTargetArgs.remove(EXTRA_TARGET_PACKAGE); | |
79 | |
80 mTargetClass = arguments.getString(EXTRA_TARGET_CLASS); | |
81 if (mTargetClass == null) { | |
82 fail("No target class."); | |
83 return; | |
84 } | |
85 mTargetArgs.remove(EXTRA_TARGET_CLASS); | |
86 | |
87 mTestClasses = new ArrayList<String>(); | |
88 String testList = arguments.getString(EXTRA_TEST_LIST); | |
89 if (testList != null) { | |
90 mTestClasses.addAll(Arrays.asList(COMMA.split(testList))); | |
91 mTargetArgs.remove(EXTRA_TEST_LIST); | |
92 } | |
93 | |
94 String testListFilePath = arguments.getString(EXTRA_TEST_LIST_FILE); | |
95 if (testListFilePath != null) { | |
96 File testListFile = new File(Environment.getExternalStorageDirectory (), | |
97 testListFilePath); | |
98 try { | |
99 BufferedReader testListFileReader = | |
100 new BufferedReader(new FileReader(testListFile)); | |
101 String test; | |
102 while ((test = testListFileReader.readLine()) != null) { | |
103 mTestClasses.add(test); | |
104 } | |
105 testListFileReader.close(); | |
106 } catch (IOException e) { | |
107 Log.e(TAG, "Error reading " + testListFile.getAbsolutePath(), e) ; | |
108 } | |
109 mTargetArgs.remove(EXTRA_TEST_LIST_FILE); | |
110 } | |
111 | |
112 if (mTestClasses.isEmpty()) { | |
113 fail("No tests."); | |
114 return; | |
115 } | |
116 | |
117 mDriverThread = new Thread( | |
118 new Driver(mTargetPackage, mTargetClass, mTargetArgs, mTestClass es)); | |
119 | |
120 start(); | |
121 } | |
122 | |
123 /** Start running tests. */ | |
124 @Override | |
125 public void onStart() { | |
126 super.onStart(); | |
127 | |
128 getContext().startService(new Intent(getContext(), ReportingService.clas s)); | |
129 | |
130 // Start the driver on its own thread s.t. it can block while the main t hread's | |
131 // Looper receives and handles messages. | |
132 if (!mDriverStarted) { | |
133 mDriverThread.start(); | |
134 mDriverStarted = true; | |
135 } | |
136 } | |
137 | |
138 /** Clean up the reporting service. */ | |
139 @Override | |
140 public void onDestroy() { | |
141 getContext().stopService(new Intent(getContext(), ReportingService.class )); | |
142 super.onDestroy(); | |
143 } | |
144 | |
145 private class Driver implements Runnable { | |
146 | |
147 private static final String TAG = OnDeviceInstrumentationDriver.TAG + ". Driver"; | |
148 | |
149 private Bundle mTargetArgs; | |
150 private String mTargetClass; | |
151 private String mTargetPackage; | |
152 private List<String> mTestClasses; | |
153 private ReportHandler mHandler; | |
154 | |
155 public Driver(String targetPackage, String targetClass, Bundle targetArg s, | |
156 List<String> testClasses) { | |
157 mTargetPackage = targetPackage; | |
158 mTargetClass = targetClass; | |
159 mTargetArgs = targetArgs; | |
160 mTestClasses = testClasses; | |
161 mHandler = new ReportHandler(Looper.getMainLooper()); | |
162 } | |
163 | |
164 private class ReportingServiceConnection implements ServiceConnection { | |
165 | |
166 private static final String TAG = Driver.TAG + ".ReportingServiceCon nection"; | |
167 | |
168 private Messenger mReportingService; | |
169 private Object mServiceLock = new Object(); | |
170 | |
171 /** Called when a service has been connected. | |
172 | |
173 @param name The name of the service that has been connected. | |
174 @param service The IBinder for the service. | |
175 */ | |
176 @Override | |
177 public void onServiceConnected(ComponentName name, IBinder service) { | |
178 synchronized (mServiceLock) { | |
179 mReportingService = new Messenger(service); | |
180 mServiceLock.notify(); | |
181 } | |
182 } | |
183 | |
184 /** Registers a {@link android.os.Messenger} with the | |
185 {@link org.chromium.test.driver.reporter.ReportingService}. | |
186 | |
187 This lets the reporting service know that it should relay messag es it receives | |
188 to the provided receiver. | |
189 | |
190 @param receiver The object to register with the reporting servic e. | |
191 */ | |
192 public void registerWithReportingService(Messenger receiver) | |
193 throws InterruptedException, RemoteException { | |
194 synchronized (mServiceLock) { | |
195 Log.i(TAG, "acquired service lock"); | |
196 while (mReportingService == null) { | |
197 Log.i(TAG, "waiting for service connection."); | |
198 mServiceLock.wait(SERVICE_WAIT_TIMEOUT); | |
199 } | |
200 | |
201 Log.i(TAG, "reporting service non-null"); | |
202 | |
203 Message registration = Message.obtain(); | |
204 registration.what = ReportingService.MSG_REGISTER_RECEIVER; | |
205 registration.replyTo = receiver; | |
206 mReportingService.send(registration); | |
207 | |
208 Log.i(TAG, "sent registration message"); | |
209 } | |
210 } | |
211 | |
212 /** Unregisters a {@link android.os.Messenger} with the | |
213 {@link org.chromium.test.driver.reporter.ReportingService}. | |
214 | |
215 This lets the reporting service know that it should no longer re lay messages it | |
216 receives to this object. | |
217 | |
218 @param receiver The object to unregister from the reporting serv ice. | |
219 */ | |
220 public void unregisterWithReportingService(Messenger receiver) | |
221 throws InterruptedException, RemoteException { | |
222 synchronized (mServiceLock) { | |
223 if (mReportingService == null) return; | |
224 | |
225 Message registration = Message.obtain(); | |
226 registration.what = ReportingService.MSG_UNREGISTER_RECEIVER ; | |
227 registration.replyTo = receiver; | |
228 mReportingService.send(registration); | |
229 } | |
230 } | |
231 | |
232 /** Called when a service has disconnected. | |
233 | |
234 @param name The name of the service that has disconnected. | |
235 */ | |
236 @Override | |
237 public void onServiceDisconnected(ComponentName name) { | |
238 synchronized (mServiceLock) { | |
239 mReportingService = null; | |
240 mServiceLock.notify(); | |
241 } | |
242 } | |
243 } | |
244 | |
245 private class ReportHandler extends Handler { | |
246 private static final String TAG = Driver.TAG + ".ReportHandler"; | |
247 | |
248 private final Object mLock = new Object(); | |
249 private final Map<String, ResultsBundleGenerator.TestResult> mFinish ed = | |
250 new HashMap<String, ResultsBundleGenerator.TestResult>(); | |
251 | |
252 public ReportHandler(Looper looper) { | |
253 super(looper); | |
254 } | |
255 | |
256 /** Receive a message. | |
257 | |
258 This does nothing unless the message is a test report from the | |
259 {@link org.chromium.test.driver.reporter.ReportingService}. | |
260 | |
261 @param msg The message to handle. | |
262 */ | |
263 @Override | |
264 public void handleMessage(Message msg) { | |
265 switch (msg.what) { | |
266 case ReportingService.MSG_REPORT_TEST_STARTED: | |
267 case ReportingService.MSG_REPORT_TEST_PASSED: | |
268 case ReportingService.MSG_REPORT_TEST_FAILED: | |
269 handleTestStatusMessage(msg); | |
270 break; | |
271 default: | |
272 super.handleMessage(msg); | |
273 break; | |
274 } | |
275 } | |
276 | |
277 private void handleTestStatusMessage(Message msg) { | |
278 String testClass = msg.getData().getString(ReportingService.MSG_ DATA_TEST_CLASS); | |
279 String testMethod = msg.getData().getString(ReportingService.MSG _DATA_TEST_METHOD); | |
280 String testName = testClass + "#" + testMethod; | |
281 synchronized (mLock) { | |
282 switch (msg.what) { | |
283 case ReportingService.MSG_REPORT_TEST_STARTED: | |
284 testStarted(testClass, testMethod); | |
285 break; | |
286 case ReportingService.MSG_REPORT_TEST_PASSED: | |
287 mFinished.put(testName, ResultsBundleGenerator.TestR esult.PASSED); | |
288 testPassed(testClass, testMethod); | |
289 mLock.notify(); | |
290 break; | |
291 case ReportingService.MSG_REPORT_TEST_FAILED: | |
292 mFinished.put(testName, ResultsBundleGenerator.TestR esult.FAILED); | |
293 testFailed(testClass, testMethod); | |
294 mLock.notify(); | |
295 break; | |
296 default: | |
297 throw new IllegalArgumentException( | |
298 "Received unexpected message from reporting service: " | |
299 + msg.what); | |
300 } | |
301 } | |
302 } | |
303 | |
304 /** Wait until the test with the given name has been reported as fin ished. | |
305 | |
306 @param testName The name of the test to wait for. | |
307 */ | |
308 public void waitForTestFinished(String testName) throws InterruptedE xception { | |
309 synchronized (mLock) { | |
310 while (!mFinished.containsKey(testName)) { | |
311 Log.i(TAG, "Waiting for " + testName + " to finish."); | |
312 mLock.wait(TEST_WAIT_TIMEOUT); | |
313 } | |
314 } | |
315 } | |
316 | |
317 /** Get the test results. | |
318 | |
319 @return A {@link android.os.Bundle} containing the results of al l reported tests. | |
320 */ | |
321 public Bundle getResults() { | |
322 return new RobotiumBundleGenerator().generate(mFinished); | |
323 } | |
324 } | |
325 | |
326 /** Run the tests. */ | |
327 @Override | |
328 public void run() { | |
329 try { | |
330 ReportingServiceConnection c = new ReportingServiceConnection(); | |
331 if (!getContext().bindService( | |
332 new Intent(getContext(), ReportingService.class), c, 0)) { | |
333 fail("Reporting service not bound."); | |
334 return; | |
335 } | |
336 | |
337 Messenger receiver = new Messenger(mHandler); | |
338 c.registerWithReportingService(receiver); | |
339 | |
340 for (String t : mTestClasses) { | |
341 Intent slaveIntent = new Intent(); | |
342 slaveIntent.setComponent(new ComponentName( | |
343 mTargetPackage, OnDeviceInstrumentationPassenger.cla ss.getName())); | |
344 slaveIntent.putExtra( | |
345 OnDeviceInstrumentationPassenger.EXTRA_INSTRUMENTATI ON_PACKAGE, | |
346 mTargetPackage); | |
347 slaveIntent.putExtra( | |
348 OnDeviceInstrumentationPassenger.EXTRA_INSTRUMENTATI ON_CLASS, | |
349 mTargetClass); | |
350 slaveIntent.putExtra(OnDeviceInstrumentationPassenger.EXTRA_ TEST, t); | |
351 slaveIntent.putExtra(OnDeviceInstrumentationPassenger.EXTRA_ TARGET_ARGS, | |
352 mTargetArgs); | |
353 slaveIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | |
354 | |
355 getContext().startActivity(slaveIntent); | |
356 | |
357 mHandler.waitForTestFinished(t); | |
358 } | |
359 | |
360 c.unregisterWithReportingService(receiver); | |
361 getContext().unbindService(c); | |
362 } catch (RemoteException e) { | |
363 fail("Error connecting to reporting service.", e); | |
364 return; | |
365 } catch (InterruptedException e) { | |
366 fail("Interrupted while running tests.", e); | |
367 return; | |
368 } | |
369 pass(mHandler.getResults()); | |
370 } | |
371 | |
372 } | |
373 | |
374 private void fail(String reason) { | |
375 Log.e(TAG, reason); | |
376 failImpl(reason); | |
377 } | |
378 | |
379 private void fail(String reason, Exception e) { | |
380 Log.e(TAG, reason, e); | |
381 failImpl(reason); | |
382 } | |
383 | |
384 private void failImpl(String reason) { | |
385 Bundle b = new Bundle(); | |
386 b.putString("reason", reason); | |
387 finish(Activity.RESULT_CANCELED, b); | |
388 } | |
389 | |
390 private void pass(Bundle results) { | |
391 finish(Activity.RESULT_OK, results); | |
392 } | |
393 | |
394 private void testStarted(String testClass, String testMethod) { | |
395 sendTestStatus(InstrumentationTestRunner.REPORT_VALUE_RESULT_START, test Class, testMethod); | |
396 } | |
397 | |
398 private void testPassed(String testClass, String testMethod) { | |
399 sendTestStatus(InstrumentationTestRunner.REPORT_VALUE_RESULT_OK, testCla ss, testMethod); | |
400 } | |
401 | |
402 private void testFailed(String testClass, String testMethod) { | |
403 sendTestStatus(InstrumentationTestRunner.REPORT_VALUE_RESULT_ERROR, test Class, testMethod); | |
404 } | |
405 | |
406 private void sendTestStatus(int status, String testClass, String testMethod) { | |
407 Bundle statusBundle = new Bundle(); | |
408 statusBundle.putString(InstrumentationTestRunner.REPORT_KEY_NAME_CLASS, testClass); | |
409 statusBundle.putString(InstrumentationTestRunner.REPORT_KEY_NAME_TEST, t estMethod); | |
410 sendStatus(status, statusBundle); | |
411 } | |
412 } | |
OLD | NEW |