| Index: testing/android/driver/java/src/org/chromium/test/driver/OnDeviceInstrumentationDriver.java
|
| diff --git a/testing/android/driver/java/src/org/chromium/test/driver/OnDeviceInstrumentationDriver.java b/testing/android/driver/java/src/org/chromium/test/driver/OnDeviceInstrumentationDriver.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..878c40cc0ae7f5c23d9da60d98bc555264c919dd
|
| --- /dev/null
|
| +++ b/testing/android/driver/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.broker.OnDeviceInstrumentationBroker;
|
| +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.
|
| + */
|
| +public class OnDeviceInstrumentationDriver extends Instrumentation {
|
| +
|
| + 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, OnDeviceInstrumentationBroker.class.getName()));
|
| + slaveIntent.putExtra(
|
| + OnDeviceInstrumentationBroker.EXTRA_INSTRUMENTATION_PACKAGE,
|
| + mTargetPackage);
|
| + slaveIntent.putExtra(
|
| + OnDeviceInstrumentationBroker.EXTRA_INSTRUMENTATION_CLASS,
|
| + mTargetClass);
|
| + slaveIntent.putExtra(OnDeviceInstrumentationBroker.EXTRA_TEST, t);
|
| + slaveIntent.putExtra(OnDeviceInstrumentationBroker.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);
|
| + }
|
| +}
|
|
|