Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(300)

Unified Diff: testing/android/java/src/org/chromium/test/driver/OnDeviceInstrumentationDriver.java

Issue 1034053002: [Android] Add an out-of-app instrumentation driver APK. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: the rename Created 5 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: testing/android/java/src/org/chromium/test/driver/OnDeviceInstrumentationDriver.java
diff --git a/testing/android/java/src/org/chromium/test/driver/OnDeviceInstrumentationDriver.java b/testing/android/java/src/org/chromium/test/driver/OnDeviceInstrumentationDriver.java
new file mode 100644
index 0000000000000000000000000000000000000000..1daa6fd4198cef68118bbe868923ec5b7dd96136
--- /dev/null
+++ b/testing/android/java/src/org/chromium/test/driver/OnDeviceInstrumentationDriver.java
@@ -0,0 +1,412 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.test.driver;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.test.InstrumentationTestRunner;
+import android.util.Log;
+
+import org.chromium.test.passenger.OnDeviceInstrumentationPassenger;
+import org.chromium.test.reporter.ReportingService;
+import org.chromium.test.support.ResultsBundleGenerator;
+import org.chromium.test.support.RobotiumBundleGenerator;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+/**
+ * An Instrumentation that drives instrumentation tests from outside the app. (Hence 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.
+ */
+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
+
+ private static final String TAG = "OnDeviceInstrumentationDriver";
+
+ private static final String EXTRA_TEST_LIST =
+ "org.chromium.test.driver.OnDeviceInstrumentationDriver.TestList";
+ private static final String EXTRA_TEST_LIST_FILE =
+ "org.chromium.test.driver.OnDeviceInstrumentationDriver.TestListFile";
+ private static final String EXTRA_TARGET_PACKAGE =
+ "org.chromium.test.driver.OnDeviceInstrumentationDriver.TargetPackage";
+ private static final String EXTRA_TARGET_CLASS =
+ "org.chromium.test.driver.OnDeviceInstrumentationDriver.TargetClass";
+
+ private static final Pattern COMMA = Pattern.compile(",");
+ private static final int SERVICE_WAIT_TIMEOUT = 5000; // ms
+ private static final int TEST_WAIT_TIMEOUT = 10000; // ms
+
+ private boolean mDriverStarted;
+ private Thread mDriverThread;
+ private Bundle mTargetArgs;
+ private String mTargetClass;
+ private String mTargetPackage;
+ private List<String> mTestClasses;
+
+ /** Parse any arguments and prepare to run tests.
+
+ @param arguments The arguments to parse.
+ */
+ @Override
+ public void onCreate(Bundle arguments) {
+ mTargetArgs = new Bundle(arguments);
+ mTargetPackage = arguments.getString(EXTRA_TARGET_PACKAGE);
+ if (mTargetPackage == null) {
+ fail("No target package.");
+ return;
+ }
+ mTargetArgs.remove(EXTRA_TARGET_PACKAGE);
+
+ mTargetClass = arguments.getString(EXTRA_TARGET_CLASS);
+ if (mTargetClass == null) {
+ fail("No target class.");
+ return;
+ }
+ mTargetArgs.remove(EXTRA_TARGET_CLASS);
+
+ mTestClasses = new ArrayList<String>();
+ String testList = arguments.getString(EXTRA_TEST_LIST);
+ if (testList != null) {
+ mTestClasses.addAll(Arrays.asList(COMMA.split(testList)));
+ mTargetArgs.remove(EXTRA_TEST_LIST);
+ }
+
+ String testListFilePath = arguments.getString(EXTRA_TEST_LIST_FILE);
+ if (testListFilePath != null) {
+ File testListFile = new File(Environment.getExternalStorageDirectory(),
+ testListFilePath);
+ try {
+ BufferedReader testListFileReader =
+ new BufferedReader(new FileReader(testListFile));
+ String test;
+ while ((test = testListFileReader.readLine()) != null) {
+ mTestClasses.add(test);
+ }
+ testListFileReader.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Error reading " + testListFile.getAbsolutePath(), e);
+ }
+ mTargetArgs.remove(EXTRA_TEST_LIST_FILE);
+ }
+
+ if (mTestClasses.isEmpty()) {
+ fail("No tests.");
+ return;
+ }
+
+ mDriverThread = new Thread(
+ new Driver(mTargetPackage, mTargetClass, mTargetArgs, mTestClasses));
+
+ start();
+ }
+
+ /** Start running tests. */
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ getContext().startService(new Intent(getContext(), ReportingService.class));
+
+ // Start the driver on its own thread s.t. it can block while the main thread's
+ // Looper receives and handles messages.
+ if (!mDriverStarted) {
+ mDriverThread.start();
+ mDriverStarted = true;
+ }
+ }
+
+ /** Clean up the reporting service. */
+ @Override
+ public void onDestroy() {
+ getContext().stopService(new Intent(getContext(), ReportingService.class));
+ super.onDestroy();
+ }
+
+ private class Driver implements Runnable {
+
+ private static final String TAG = OnDeviceInstrumentationDriver.TAG + ".Driver";
+
+ private Bundle mTargetArgs;
+ private String mTargetClass;
+ private String mTargetPackage;
+ private List<String> mTestClasses;
+ private ReportHandler mHandler;
+
+ public Driver(String targetPackage, String targetClass, Bundle targetArgs,
+ List<String> testClasses) {
+ mTargetPackage = targetPackage;
+ mTargetClass = targetClass;
+ mTargetArgs = targetArgs;
+ mTestClasses = testClasses;
+ mHandler = new ReportHandler(Looper.getMainLooper());
+ }
+
+ private class ReportingServiceConnection implements ServiceConnection {
+
+ private static final String TAG = Driver.TAG + ".ReportingServiceConnection";
+
+ private Messenger mReportingService;
+ private Object mServiceLock = new Object();
+
+ /** Called when a service has been connected.
+
+ @param name The name of the service that has been connected.
+ @param service The IBinder for the service.
+ */
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ synchronized (mServiceLock) {
+ mReportingService = new Messenger(service);
+ mServiceLock.notify();
+ }
+ }
+
+ /** Registers a {@link android.os.Messenger} with the
+ {@link org.chromium.test.driver.reporter.ReportingService}.
+
+ This lets the reporting service know that it should relay messages it receives
+ to the provided receiver.
+
+ @param receiver The object to register with the reporting service.
+ */
+ public void registerWithReportingService(Messenger receiver)
+ throws InterruptedException, RemoteException {
+ synchronized (mServiceLock) {
+ Log.i(TAG, "acquired service lock");
+ while (mReportingService == null) {
+ Log.i(TAG, "waiting for service connection.");
+ mServiceLock.wait(SERVICE_WAIT_TIMEOUT);
+ }
+
+ Log.i(TAG, "reporting service non-null");
+
+ Message registration = Message.obtain();
+ registration.what = ReportingService.MSG_REGISTER_RECEIVER;
+ registration.replyTo = receiver;
+ mReportingService.send(registration);
+
+ Log.i(TAG, "sent registration message");
+ }
+ }
+
+ /** Unregisters a {@link android.os.Messenger} with the
+ {@link org.chromium.test.driver.reporter.ReportingService}.
+
+ This lets the reporting service know that it should no longer relay messages it
+ receives to this object.
+
+ @param receiver The object to unregister from the reporting service.
+ */
+ public void unregisterWithReportingService(Messenger receiver)
+ throws InterruptedException, RemoteException {
+ synchronized (mServiceLock) {
+ if (mReportingService == null) return;
+
+ Message registration = Message.obtain();
+ registration.what = ReportingService.MSG_UNREGISTER_RECEIVER;
+ registration.replyTo = receiver;
+ mReportingService.send(registration);
+ }
+ }
+
+ /** Called when a service has disconnected.
+
+ @param name The name of the service that has disconnected.
+ */
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ synchronized (mServiceLock) {
+ mReportingService = null;
+ mServiceLock.notify();
+ }
+ }
+ }
+
+ private class ReportHandler extends Handler {
+ private static final String TAG = Driver.TAG + ".ReportHandler";
+
+ private final Object mLock = new Object();
+ private final Map<String, ResultsBundleGenerator.TestResult> mFinished =
+ new HashMap<String, ResultsBundleGenerator.TestResult>();
+
+ public ReportHandler(Looper looper) {
+ super(looper);
+ }
+
+ /** Receive a message.
+
+ This does nothing unless the message is a test report from the
+ {@link org.chromium.test.driver.reporter.ReportingService}.
+
+ @param msg The message to handle.
+ */
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case ReportingService.MSG_REPORT_TEST_STARTED:
+ case ReportingService.MSG_REPORT_TEST_PASSED:
+ case ReportingService.MSG_REPORT_TEST_FAILED:
+ handleTestStatusMessage(msg);
+ break;
+ default:
+ super.handleMessage(msg);
+ break;
+ }
+ }
+
+ private void handleTestStatusMessage(Message msg) {
+ String testClass = msg.getData().getString(ReportingService.MSG_DATA_TEST_CLASS);
+ String testMethod = msg.getData().getString(ReportingService.MSG_DATA_TEST_METHOD);
+ String testName = testClass + "#" + testMethod;
+ synchronized (mLock) {
+ switch (msg.what) {
+ case ReportingService.MSG_REPORT_TEST_STARTED:
+ testStarted(testClass, testMethod);
+ break;
+ case ReportingService.MSG_REPORT_TEST_PASSED:
+ mFinished.put(testName, ResultsBundleGenerator.TestResult.PASSED);
+ testPassed(testClass, testMethod);
+ mLock.notify();
+ break;
+ case ReportingService.MSG_REPORT_TEST_FAILED:
+ mFinished.put(testName, ResultsBundleGenerator.TestResult.FAILED);
+ testFailed(testClass, testMethod);
+ mLock.notify();
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Received unexpected message from reporting service: "
+ + msg.what);
+ }
+ }
+ }
+
+ /** Wait until the test with the given name has been reported as finished.
+
+ @param testName The name of the test to wait for.
+ */
+ public void waitForTestFinished(String testName) throws InterruptedException {
+ synchronized (mLock) {
+ while (!mFinished.containsKey(testName)) {
+ Log.i(TAG, "Waiting for " + testName + " to finish.");
+ mLock.wait(TEST_WAIT_TIMEOUT);
+ }
+ }
+ }
+
+ /** Get the test results.
+
+ @return A {@link android.os.Bundle} containing the results of all reported tests.
+ */
+ public Bundle getResults() {
+ return new RobotiumBundleGenerator().generate(mFinished);
+ }
+ }
+
+ /** Run the tests. */
+ @Override
+ public void run() {
+ try {
+ ReportingServiceConnection c = new ReportingServiceConnection();
+ if (!getContext().bindService(
+ new Intent(getContext(), ReportingService.class), c, 0)) {
+ fail("Reporting service not bound.");
+ return;
+ }
+
+ Messenger receiver = new Messenger(mHandler);
+ c.registerWithReportingService(receiver);
+
+ for (String t : mTestClasses) {
+ Intent slaveIntent = new Intent();
+ slaveIntent.setComponent(new ComponentName(
+ mTargetPackage, OnDeviceInstrumentationPassenger.class.getName()));
+ slaveIntent.putExtra(
+ OnDeviceInstrumentationPassenger.EXTRA_INSTRUMENTATION_PACKAGE,
+ mTargetPackage);
+ slaveIntent.putExtra(
+ OnDeviceInstrumentationPassenger.EXTRA_INSTRUMENTATION_CLASS,
+ mTargetClass);
+ slaveIntent.putExtra(OnDeviceInstrumentationPassenger.EXTRA_TEST, t);
+ slaveIntent.putExtra(OnDeviceInstrumentationPassenger.EXTRA_TARGET_ARGS,
+ mTargetArgs);
+ slaveIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ getContext().startActivity(slaveIntent);
+
+ mHandler.waitForTestFinished(t);
+ }
+
+ c.unregisterWithReportingService(receiver);
+ getContext().unbindService(c);
+ } catch (RemoteException e) {
+ fail("Error connecting to reporting service.", e);
+ return;
+ } catch (InterruptedException e) {
+ fail("Interrupted while running tests.", e);
+ return;
+ }
+ pass(mHandler.getResults());
+ }
+
+ }
+
+ private void fail(String reason) {
+ Log.e(TAG, reason);
+ failImpl(reason);
+ }
+
+ private void fail(String reason, Exception e) {
+ Log.e(TAG, reason, e);
+ failImpl(reason);
+ }
+
+ private void failImpl(String reason) {
+ Bundle b = new Bundle();
+ b.putString("reason", reason);
+ finish(Activity.RESULT_CANCELED, b);
+ }
+
+ private void pass(Bundle results) {
+ finish(Activity.RESULT_OK, results);
+ }
+
+ private void testStarted(String testClass, String testMethod) {
+ sendTestStatus(InstrumentationTestRunner.REPORT_VALUE_RESULT_START, testClass, testMethod);
+ }
+
+ private void testPassed(String testClass, String testMethod) {
+ sendTestStatus(InstrumentationTestRunner.REPORT_VALUE_RESULT_OK, testClass, testMethod);
+ }
+
+ private void testFailed(String testClass, String testMethod) {
+ sendTestStatus(InstrumentationTestRunner.REPORT_VALUE_RESULT_ERROR, testClass, testMethod);
+ }
+
+ private void sendTestStatus(int status, String testClass, String testMethod) {
+ Bundle statusBundle = new Bundle();
+ statusBundle.putString(InstrumentationTestRunner.REPORT_KEY_NAME_CLASS, testClass);
+ statusBundle.putString(InstrumentationTestRunner.REPORT_KEY_NAME_TEST, testMethod);
+ sendStatus(status, statusBundle);
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698