Chromium Code Reviews| 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 |