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

Unified Diff: testing/android/java/src/org/chromium/test/outstrumentation/master/Outstrumentation.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: Created 5 years, 9 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/outstrumentation/master/Outstrumentation.java
diff --git a/testing/android/java/src/org/chromium/test/outstrumentation/master/Outstrumentation.java b/testing/android/java/src/org/chromium/test/outstrumentation/master/Outstrumentation.java
new file mode 100644
index 0000000000000000000000000000000000000000..db3c1b1e639e36c168390057174b2a96acd381ab
--- /dev/null
+++ b/testing/android/java/src/org/chromium/test/outstrumentation/master/Outstrumentation.java
@@ -0,0 +1,356 @@
+// 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.outstrumentation.master;
+
+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.util.Log;
+
+import org.chromium.test.outstrumentation.reporter.ReportingService;
+import org.chromium.test.outstrumentation.slave.OutstrumentationSlaveActivity;
+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.)
+ */
+public class Outstrumentation extends Instrumentation {
+
+ private static final String TAG = "Outstrumentation";
+
+ private static final String EXTRA_TEST_LIST =
+ "org.chromium.test.outstrumentation.master.Outstrumentation.TestList";
+ private static final String EXTRA_TEST_LIST_FILE =
+ "org.chromium.test.outstrumentation.master.Outstrumentation.TestListFile";
+ private static final String EXTRA_TARGET_PACKAGE =
+ "org.chromium.test.outstrumentation.master.Outstrumentation.TargetPackage";
+ private static final String EXTRA_TARGET_CLASS =
+ "org.chromium.test.outstrumentation.master.Outstrumentation.TargetClass";
+
+ private static final Pattern COMMA = Pattern.compile(",");
+
+ private Bundle mTargetArgs;
+ private String mTargetClass;
+ private String mTargetPackage;
+ private List<String> mTestClasses;
+
+ @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);
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
+
+ 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);
Ted C 2015/04/06 17:43:54 java indenting is 8 from the previous line
jbudorick 2015/04/07 00:54:39 Done.
+ try {
+ BufferedReader testListFileReader =
+ new BufferedReader(new FileReader(testListFile));
+ 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.
+ String test = testListFileReader.readLine();
+ if (test == null) break;
+ 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;
+ }
+
+ start();
+ }
+
+ @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.
+ Thread driverThread = new Thread(
+ new Driver(mTargetPackage, mTargetClass, mTargetArgs, mTestClasses));
+ driverThread.start();
+ }
+
+ @Override
+ 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
+ getContext().stopService(new Intent(getContext(), ReportingService.class));
+ super.onDestroy();
+ }
+
+ private class Driver implements Runnable {
+
+ private static final String TAG = Outstrumentation.TAG + ".Driver";
+
+ 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
+ private Object mServiceLock = new Object();
+ 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";
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ // Register receiver.
+ Log.i(TAG, "service connected");
+ synchronized (mServiceLock) {
+ mReportingService = new Messenger(service);
+ mServiceLock.notify();
+ }
+ }
+
+ 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.
+ throws InterruptedException, RemoteException {
+ synchronized (mServiceLock) {
+ Log.i(TAG, "acquired service lock");
+ while (mReportingService == null) {
+ Log.i(TAG, "waiting for service connection.");
+ mServiceLock.wait();
+ }
+
+ 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");
+ }
+ }
+
+ public void unregisterWithReportingService()
+ throws InterruptedException, RemoteException {
+ synchronized (mServiceLock) {
+ if (mReportingService == null) return;
+
+ Message registration = Message.obtain();
+ registration.what = ReportingService.MSG_UNREGISTER_RECEIVER;
+ mReportingService.send(registration);
+ }
+ }
+
+ @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);
+ }
+
+ @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:
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.
+ break;
+ }
+ }
+ }
+
+ public void waitForTestFinished(String testName) throws InterruptedException {
+ synchronized (mLock) {
+ while (!mFinished.containsKey(testName)) {
+ mLock.wait();
+ }
+ }
+ }
+
+ public Bundle getResults() {
+ return new RobotiumBundleGenerator().generate(mFinished);
+ }
+ }
+
+ @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;
+ }
+
+ c.registerWithReportingService(new Messenger(mHandler));
+
+ for (String t : mTestClasses) {
+ Intent slaveIntent = new Intent();
+ slaveIntent.setComponent(
+ new ComponentName(mTargetPackage,
+ OutstrumentationSlaveActivity.class.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.
+ slaveIntent.putExtra(
+ OutstrumentationSlaveActivity.EXTRA_INSTRUMENTATION_PACKAGE,
+ mTargetPackage);
+ slaveIntent.putExtra(
+ OutstrumentationSlaveActivity.EXTRA_INSTRUMENTATION_CLASS,
+ mTargetClass);
+ slaveIntent.putExtra(OutstrumentationSlaveActivity.EXTRA_TEST, t);
+ slaveIntent.putExtra(OutstrumentationSlaveActivity.EXTRA_TARGET_ARGS,
+ mTargetArgs);
+ slaveIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ getContext().startActivity(slaveIntent);
+
+ mHandler.waitForTestFinished(t);
+ }
+
+ c.unregisterWithReportingService();
+ 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) {
+ Bundle status = new Bundle();
+ status.putString("class", testClass);
+ status.putString("test", testMethod);
+ 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
+ }
+
+ private void testPassed(String testClass, String testMethod) {
+ Bundle status = new Bundle();
+ status.putString("class", testClass);
+ status.putString("test", testMethod);
+ sendStatus(0, status);
+ }
+
+ private void testFailed(String testClass, String testMethod) {
+ Bundle status = new Bundle();
+ status.putString("class", testClass);
+ status.putString("test", testMethod);
+ sendStatus(-1, status);
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698