summaryrefslogtreecommitdiffstats
path: root/testing
diff options
context:
space:
mode:
authorjbudorick <jbudorick@chromium.org>2015-04-27 14:13:31 -0700
committerCommit bot <commit-bot@chromium.org>2015-04-27 21:14:11 +0000
commit4d64d862917d9cd9e045e95c68a3dd984d4eabcd (patch)
treea1768b18c27994ef8c60e8e79ec0d60937fb446e /testing
parentb9bfea7758f5fbda076d5873f5cf10a5f44598cf (diff)
downloadchromium_src-4d64d862917d9cd9e045e95c68a3dd984d4eabcd.zip
chromium_src-4d64d862917d9cd9e045e95c68a3dd984d4eabcd.tar.gz
chromium_src-4d64d862917d9cd9e045e95c68a3dd984d4eabcd.tar.bz2
[Android] Add an out-of-app instrumentation driver APK. (RELAND)
BUG=444049 Review URL: https://codereview.chromium.org/1060933003 Cr-Commit-Position: refs/heads/master@{#327118}
Diffstat (limited to 'testing')
-rw-r--r--testing/android/appurify_support.gyp22
-rw-r--r--testing/android/appurify_support/BUILD.gn15
-rw-r--r--testing/android/appurify_support/java/src/org/chromium/test/support/ResultsBundleGenerator.java30
-rw-r--r--testing/android/appurify_support/java/src/org/chromium/test/support/RobotiumBundleGenerator.java56
-rw-r--r--testing/android/broker/BUILD.gn13
-rw-r--r--testing/android/broker/java/src/org/chromium/test/broker/OnDeviceInstrumentationBroker.java64
-rw-r--r--testing/android/driver/BUILD.gn21
-rw-r--r--testing/android/driver/java/AndroidManifest.xml23
-rw-r--r--testing/android/driver/java/src/org/chromium/test/driver/OnDeviceInstrumentationDriver.java271
-rw-r--r--testing/android/native_test/java/src/org/chromium/native_test/ChromeNativeTestInstrumentationTestRunner.java67
-rw-r--r--testing/android/on_device_instrumentation.gyp79
-rw-r--r--testing/android/reporter/BUILD.gn19
-rw-r--r--testing/android/reporter/java/src/org/chromium/test/reporter/TestStatusListener.java78
-rw-r--r--testing/android/reporter/java/src/org/chromium/test/reporter/TestStatusReceiver.java128
-rw-r--r--testing/android/reporter/java/src/org/chromium/test/reporter/TestStatusReporter.java83
15 files changed, 913 insertions, 56 deletions
diff --git a/testing/android/appurify_support.gyp b/testing/android/appurify_support.gyp
new file mode 100644
index 0000000..2904368
--- /dev/null
+++ b/testing/android/appurify_support.gyp
@@ -0,0 +1,22 @@
+# 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.
+
+{
+ 'conditions': [
+ ['OS=="android"', {
+ 'targets': [
+ {
+ 'target_name': 'appurify_support_java',
+ 'type': 'none',
+ 'variables': {
+ 'java_in_dir': '../../testing/android/appurify_support/java',
+ },
+ 'includes': [
+ '../../build/java.gypi',
+ ],
+ },
+ ],
+ }],
+ ],
+}
diff --git a/testing/android/appurify_support/BUILD.gn b/testing/android/appurify_support/BUILD.gn
new file mode 100644
index 0000000..871f9d0
--- /dev/null
+++ b/testing/android/appurify_support/BUILD.gn
@@ -0,0 +1,15 @@
+# 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.
+
+import("//build/config/android/rules.gni")
+
+# GYP: //testing/android/appurify_support.gyp:appurify_support_java
+android_library("appurify_support_java") {
+ chromium_code = true
+
+ java_files = [
+ "java/src/org/chromium/test/support/ResultsBundleGenerator.java",
+ "java/src/org/chromium/test/support/RobotiumBundleGenerator.java",
+ ]
+}
diff --git a/testing/android/appurify_support/java/src/org/chromium/test/support/ResultsBundleGenerator.java b/testing/android/appurify_support/java/src/org/chromium/test/support/ResultsBundleGenerator.java
new file mode 100644
index 0000000..c9588d9
--- /dev/null
+++ b/testing/android/appurify_support/java/src/org/chromium/test/support/ResultsBundleGenerator.java
@@ -0,0 +1,30 @@
+// 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.support;
+
+import android.os.Bundle;
+
+import java.util.Map;
+
+/**
+ * Creates a results Bundle.
+ */
+public interface ResultsBundleGenerator {
+
+ /** Indicates the state of a test.
+ */
+ static enum TestResult {
+ PASSED, FAILED, ERROR, UNKNOWN
+ }
+
+ /** Creates a bundle of test results from the provided raw results.
+
+ Note: actual bundle content and format may vary.
+
+ @param rawResults A map between test names and test results.
+ */
+ Bundle generate(Map<String, TestResult> rawResults);
+}
+
diff --git a/testing/android/appurify_support/java/src/org/chromium/test/support/RobotiumBundleGenerator.java b/testing/android/appurify_support/java/src/org/chromium/test/support/RobotiumBundleGenerator.java
new file mode 100644
index 0000000..167e7b9
--- /dev/null
+++ b/testing/android/appurify_support/java/src/org/chromium/test/support/RobotiumBundleGenerator.java
@@ -0,0 +1,56 @@
+// 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.support;
+
+import android.app.Instrumentation;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.util.Map;
+
+/**
+ * Creates a results bundle that emulates the one created by Robotium.
+ */
+public class RobotiumBundleGenerator implements ResultsBundleGenerator {
+
+ private static final String TAG = "RobotiumBundleGenerator";
+
+ public Bundle generate(Map<String, ResultsBundleGenerator.TestResult> rawResults) {
+ int testsPassed = 0;
+ int testsFailed = 0;
+
+ for (Map.Entry<String, ResultsBundleGenerator.TestResult> entry : rawResults.entrySet()) {
+ switch (entry.getValue()) {
+ case PASSED:
+ ++testsPassed;
+ break;
+ case FAILED:
+ // TODO(jbudorick): Remove this log message once AMP execution and
+ // results handling has been stabilized.
+ Log.d(TAG, "FAILED: " + entry.getKey());
+ ++testsFailed;
+ break;
+ default:
+ Log.w(TAG, "Unhandled: " + entry.getKey() + ", "
+ + entry.getValue().toString());
+ break;
+ }
+ }
+
+ StringBuilder resultBuilder = new StringBuilder();
+ if (testsFailed > 0) {
+ resultBuilder.append(
+ "\nFAILURES!!! Tests run: " + Integer.toString(rawResults.size())
+ + ", Failures: " + Integer.toString(testsFailed) + ", Errors: 0");
+ } else {
+ resultBuilder.append("\nOK (" + Integer.toString(testsPassed) + " tests)");
+ }
+
+ Bundle resultsBundle = new Bundle();
+ resultsBundle.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
+ resultBuilder.toString());
+ return resultsBundle;
+ }
+}
diff --git a/testing/android/broker/BUILD.gn b/testing/android/broker/BUILD.gn
new file mode 100644
index 0000000..8daa040
--- /dev/null
+++ b/testing/android/broker/BUILD.gn
@@ -0,0 +1,13 @@
+# 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.
+
+import("//build/config/android/rules.gni")
+
+# GYP: //testing/android/on_device_instrumentation.gyp:broker_java
+android_library("broker_java") {
+ chromium_code = true
+
+ java_files =
+ [ "java/src/org/chromium/test/broker/OnDeviceInstrumentationBroker.java" ]
+}
diff --git a/testing/android/broker/java/src/org/chromium/test/broker/OnDeviceInstrumentationBroker.java b/testing/android/broker/java/src/org/chromium/test/broker/OnDeviceInstrumentationBroker.java
new file mode 100644
index 0000000..cd755d0
--- /dev/null
+++ b/testing/android/broker/java/src/org/chromium/test/broker/OnDeviceInstrumentationBroker.java
@@ -0,0 +1,64 @@
+// 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.broker;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * An Activity target for OnDeviceInstrumentationDriver that starts the specified
+ * Instrumentation test.
+ */
+public class OnDeviceInstrumentationBroker extends Activity {
+
+ public static final String EXTRA_INSTRUMENTATION_PACKAGE =
+ "org.chromium.test.broker.OnDeviceInstrumentationBroker."
+ + "InstrumentationPackage";
+ public static final String EXTRA_INSTRUMENTATION_CLASS =
+ "org.chromium.test.broker.OnDeviceInstrumentationBroker."
+ + "InstrumentationClass";
+ public static final String EXTRA_TARGET_ARGS =
+ "org.chromium.test.broker.OnDeviceInstrumentationBroker.TargetArgs";
+ public static final String EXTRA_TEST =
+ "org.chromium.test.broker.OnDeviceInstrumentationBroker.Test";
+
+ private static final String TAG = "OnDeviceInstrumentationBroker";
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Log.d(TAG, "onCreate()");
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ Intent i = getIntent();
+ String instrumentationPackage = i.getStringExtra(EXTRA_INSTRUMENTATION_PACKAGE);
+ String instrumentationClass = i.getStringExtra(EXTRA_INSTRUMENTATION_CLASS);
+ Bundle targetArgs = i.getBundleExtra(EXTRA_TARGET_ARGS);
+ String test = i.getStringExtra(EXTRA_TEST);
+
+ if (instrumentationPackage == null || instrumentationClass == null) {
+ finish();
+ return;
+ }
+
+ ComponentName instrumentationComponent =
+ new ComponentName(instrumentationPackage, instrumentationClass);
+
+ if (test != null) {
+ targetArgs.putString("class", test);
+ }
+
+ startInstrumentation(instrumentationComponent, null, targetArgs);
+ finish();
+ }
+}
+
diff --git a/testing/android/driver/BUILD.gn b/testing/android/driver/BUILD.gn
new file mode 100644
index 0000000..436ac63
--- /dev/null
+++ b/testing/android/driver/BUILD.gn
@@ -0,0 +1,21 @@
+# 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.
+
+import("//build/config/android/rules.gni")
+
+# GYP: //testing/android/on_device_instrumentation.gyp:driver_apk
+android_apk("driver_apk") {
+ android_manifest = "java/AndroidManifest.xml"
+ apk_name = "OnDeviceInstrumentationDriver"
+ testonly = true
+
+ deps = [
+ "//testing/android/appurify_support:appurify_support_java",
+ "//testing/android/broker:broker_java",
+ "//testing/android/reporter:reporter_java",
+ ]
+
+ java_files =
+ [ "java/src/org/chromium/test/driver/OnDeviceInstrumentationDriver.java" ]
+}
diff --git a/testing/android/driver/java/AndroidManifest.xml b/testing/android/driver/java/AndroidManifest.xml
new file mode 100644
index 0000000..c7e99ef
--- /dev/null
+++ b/testing/android/driver/java/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="org.chromium.test.driver"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="21" />
+
+ <application android:label="OnDeviceInstrumentationDriver" />
+
+ <instrumentation android:name="org.chromium.test.driver.OnDeviceInstrumentationDriver"
+ android:targetPackage="org.chromium.test.driver"
+ android:label="OnDeviceInstrumentationDriver"/>
+
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+
+</manifest>
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 0000000..78c571a
--- /dev/null
+++ b/testing/android/driver/java/src/org/chromium/test/driver/OnDeviceInstrumentationDriver.java
@@ -0,0 +1,271 @@
+// 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.os.Bundle;
+import android.os.Environment;
+import android.test.InstrumentationTestRunner;
+import android.util.Log;
+
+import org.chromium.test.broker.OnDeviceInstrumentationBroker;
+import org.chromium.test.reporter.TestStatusReceiver;
+import org.chromium.test.reporter.TestStatusReporter;
+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.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 TEST_WAIT_TIMEOUT = 5 * TestStatusReporter.HEARTBEAT_INTERVAL_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();
+
+ // 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() {
+ 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;
+
+ public Driver(String targetPackage, String targetClass, Bundle targetArgs,
+ List<String> testClasses) {
+ mTargetPackage = targetPackage;
+ mTargetClass = targetClass;
+ mTargetArgs = targetArgs;
+ mTestClasses = testClasses;
+ }
+
+ 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);
+ }
+
+ /** Run the tests. */
+ @Override
+ public void run() {
+ final HashMap<String, ResultsBundleGenerator.TestResult> finished =
+ new HashMap<String, ResultsBundleGenerator.TestResult>();
+ final Object statusLock = new Object();
+
+ try {
+ TestStatusReceiver r = new TestStatusReceiver();
+ r.registerCallback(new TestStatusReceiver.StartCallback() {
+ @Override
+ public void testStarted(String testClass, String testMethod) {
+ sendTestStatus(InstrumentationTestRunner.REPORT_VALUE_RESULT_START,
+ testClass, testMethod);
+ synchronized (statusLock) {
+ statusLock.notify();
+ }
+ }
+ });
+ r.registerCallback(new TestStatusReceiver.PassCallback() {
+ @Override
+ public void testPassed(String testClass, String testMethod) {
+ sendTestStatus(InstrumentationTestRunner.REPORT_VALUE_RESULT_OK, testClass,
+ testMethod);
+ synchronized (statusLock) {
+ finished.put(testClass + "#" + testMethod,
+ ResultsBundleGenerator.TestResult.PASSED);
+ statusLock.notify();
+ }
+ }
+ });
+ r.registerCallback(new TestStatusReceiver.FailCallback() {
+ @Override
+ public void testFailed(String testClass, String testMethod) {
+ sendTestStatus(InstrumentationTestRunner.REPORT_VALUE_RESULT_ERROR,
+ testClass, testMethod);
+ synchronized (statusLock) {
+ finished.put(testClass + "#" + testMethod,
+ ResultsBundleGenerator.TestResult.FAILED);
+ statusLock.notify();
+ }
+ }
+ });
+ r.registerCallback(new TestStatusReceiver.HeartbeatCallback() {
+ @Override
+ public void heartbeat() {
+ Log.i(TAG, "Heartbeat received.");
+ synchronized (statusLock) {
+ statusLock.notify();
+ }
+ }
+ });
+ r.register(getContext());
+
+ 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);
+
+ synchronized (statusLock) {
+ while (!finished.containsKey(t)) {
+ long waitStart = System.currentTimeMillis();
+ statusLock.wait(TEST_WAIT_TIMEOUT);
+ if (System.currentTimeMillis() - waitStart > TEST_WAIT_TIMEOUT) {
+ Log.e(TAG, t + " has gone missing and is assumed to be dead.");
+ finished.put(t, ResultsBundleGenerator.TestResult.FAILED);
+ break;
+ }
+ }
+ }
+ }
+ getContext().unregisterReceiver(r);
+
+ } catch (InterruptedException e) {
+ fail("Interrupted while running tests.", e);
+ return;
+ }
+ pass(new RobotiumBundleGenerator().generate(finished));
+ }
+
+ }
+
+ 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);
+ }
+}
diff --git a/testing/android/native_test/java/src/org/chromium/native_test/ChromeNativeTestInstrumentationTestRunner.java b/testing/android/native_test/java/src/org/chromium/native_test/ChromeNativeTestInstrumentationTestRunner.java
index 4e1a067..25b6570 100644
--- a/testing/android/native_test/java/src/org/chromium/native_test/ChromeNativeTestInstrumentationTestRunner.java
+++ b/testing/android/native_test/java/src/org/chromium/native_test/ChromeNativeTestInstrumentationTestRunner.java
@@ -12,6 +12,9 @@ import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
+import org.chromium.test.support.ResultsBundleGenerator;
+import org.chromium.test.support.RobotiumBundleGenerator;
+
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
@@ -38,10 +41,6 @@ public class ChromeNativeTestInstrumentationTestRunner extends Instrumentation {
private static final int ACCEPT_TIMEOUT_MS = 5000;
private static final Pattern RE_TEST_OUTPUT = Pattern.compile("\\[ *([^ ]*) *\\] ?([^ ]+) .*");
- private static interface ResultsBundleGenerator {
- public Bundle generate(Map<String, TestResult> rawResults);
- }
-
private String mCommandLineFile;
private String mCommandLineFlags;
private File mStdoutFile;
@@ -91,7 +90,7 @@ public class ChromeNativeTestInstrumentationTestRunner extends Instrumentation {
}
Log.i(TAG, "Getting results.");
- Map<String, TestResult> results = parseResults(activityUnderTest);
+ Map<String, ResultsBundleGenerator.TestResult> results = parseResults(activityUnderTest);
Log.i(TAG, "Parsing results and generating output.");
return mBundleGenerator.generate(results);
@@ -117,16 +116,14 @@ public class ChromeNativeTestInstrumentationTestRunner extends Instrumentation {
return startActivitySync(i);
}
- private static enum TestResult {
- PASSED, FAILED, ERROR, UNKNOWN
- }
-
/**
* Generates a map between test names and test results from the instrumented Activity's
* output.
*/
- private Map<String, TestResult> parseResults(Activity activityUnderTest) {
- Map<String, TestResult> results = new HashMap<String, TestResult>();
+ private Map<String, ResultsBundleGenerator.TestResult> parseResults(
+ Activity activityUnderTest) {
+ Map<String, ResultsBundleGenerator.TestResult> results =
+ new HashMap<String, ResultsBundleGenerator.TestResult>();
BufferedReader r = null;
@@ -145,14 +142,14 @@ public class ChromeNativeTestInstrumentationTestRunner extends Instrumentation {
boolean isFailure = false;
if (m.matches()) {
if (m.group(1).equals("RUN")) {
- results.put(m.group(2), TestResult.UNKNOWN);
+ results.put(m.group(2), ResultsBundleGenerator.TestResult.UNKNOWN);
} else if (m.group(1).equals("FAILED")) {
- results.put(m.group(2), TestResult.FAILED);
+ results.put(m.group(2), ResultsBundleGenerator.TestResult.FAILED);
isFailure = true;
mLogBundle.putString(Instrumentation.REPORT_KEY_STREAMRESULT, l + "\n");
sendStatus(0, mLogBundle);
} else if (m.group(1).equals("OK")) {
- results.put(m.group(2), TestResult.PASSED);
+ results.put(m.group(2), ResultsBundleGenerator.TestResult.PASSED);
}
}
@@ -185,46 +182,4 @@ public class ChromeNativeTestInstrumentationTestRunner extends Instrumentation {
return results;
}
- /**
- * Creates a results bundle that emulates the one created by Robotium.
- */
- private static class RobotiumBundleGenerator implements ResultsBundleGenerator {
- public Bundle generate(Map<String, TestResult> rawResults) {
- Bundle resultsBundle = new Bundle();
-
- int testsPassed = 0;
- int testsFailed = 0;
-
- for (Map.Entry<String, TestResult> entry : rawResults.entrySet()) {
- switch (entry.getValue()) {
- case PASSED:
- ++testsPassed;
- break;
- case FAILED:
- // TODO(jbudorick): Remove this log message once AMP execution and
- // results handling has been stabilized.
- Log.d(TAG, "FAILED: " + entry.getKey());
- ++testsFailed;
- break;
- default:
- Log.w(TAG, "Unhandled: " + entry.getKey() + ", "
- + entry.getValue().toString());
- break;
- }
- }
-
- StringBuilder resultBuilder = new StringBuilder();
- if (testsFailed > 0) {
- resultBuilder.append(
- "\nFAILURES!!! Tests run: " + Integer.toString(rawResults.size())
- + ", Failures: " + Integer.toString(testsFailed) + ", Errors: 0");
- } else {
- resultBuilder.append("\nOK (" + Integer.toString(testsPassed) + " tests)");
- }
- resultsBundle.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
- resultBuilder.toString());
- return resultsBundle;
- }
- }
-
}
diff --git a/testing/android/on_device_instrumentation.gyp b/testing/android/on_device_instrumentation.gyp
new file mode 100644
index 0000000..12e0f35
--- /dev/null
+++ b/testing/android/on_device_instrumentation.gyp
@@ -0,0 +1,79 @@
+# 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.
+
+{
+ 'conditions': [
+ ['OS=="android"', {
+ 'variables' : {
+ 'driver_apk_name': 'OnDeviceInstrumentationDriver',
+ 'driver_apk_path': '<(PRODUCT_DIR)/apks/<(driver_apk_name).apk'
+ },
+ 'targets': [
+ {
+ 'target_name': 'reporter_java',
+ 'type': 'none',
+ 'dependencies': ['../../base/base.gyp:base_java'],
+ 'variables': {
+ 'java_in_dir': '../../testing/android/reporter/java',
+ },
+ 'includes': [
+ '../../build/java.gypi',
+ ],
+ },
+ {
+ 'target_name': 'broker_java',
+ 'type': 'none',
+ 'variables': {
+ 'java_in_dir': '../../testing/android/broker/java',
+ },
+ 'includes': [
+ '../../build/java.gypi',
+ ],
+ },
+ {
+ 'target_name': 'driver_apk',
+ 'type': 'none',
+ 'dependencies': [
+ 'broker_java',
+ 'reporter_java',
+ 'appurify_support.gyp:appurify_support_java',
+ ],
+ 'variables': {
+ 'apk_name': '<(driver_apk_name)',
+ 'final_apk_path': '<(driver_apk_path)',
+ 'java_in_dir': '../../testing/android/driver/java',
+ },
+ 'includes': [
+ '../../build/java_apk.gypi',
+ ],
+ },
+ {
+ # This emulates gn's datadeps fields, allowing other APKs to declare
+ # that they require that this APK be built without including the
+ # driver's code.
+ 'target_name': 'require_driver_apk',
+ 'type': 'none',
+ 'actions': [
+ {
+ 'action_name': 'require_<(driver_apk_name)',
+ 'message': 'Making sure <(driver_apk_path) has been built.',
+ 'variables': {
+ 'required_file': '<(PRODUCT_DIR)/driver_apk/<(driver_apk_name).apk.required',
+ },
+ 'inputs': [
+ '<(driver_apk_path)',
+ ],
+ 'outputs': [
+ '<(required_file)',
+ ],
+ 'action': [
+ 'python', '../../build/android/gyp/touch.py', '<(required_file)',
+ ],
+ },
+ ],
+ },
+ ],
+ }],
+ ],
+}
diff --git a/testing/android/reporter/BUILD.gn b/testing/android/reporter/BUILD.gn
new file mode 100644
index 0000000..7086a79
--- /dev/null
+++ b/testing/android/reporter/BUILD.gn
@@ -0,0 +1,19 @@
+# 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.
+
+import("//build/config/android/rules.gni")
+
+# GYP: //testing/android/on_device_instrumentation.gyp:reporter_java
+android_library("reporter_java") {
+ chromium_code = true
+
+ deps = [
+ "//base:base_java",
+ ]
+ java_files = [
+ "java/src/org/chromium/test/reporter/TestStatusListener.java",
+ "java/src/org/chromium/test/reporter/TestStatusReceiver.java",
+ "java/src/org/chromium/test/reporter/TestStatusReporter.java",
+ ]
+}
diff --git a/testing/android/reporter/java/src/org/chromium/test/reporter/TestStatusListener.java b/testing/android/reporter/java/src/org/chromium/test/reporter/TestStatusListener.java
new file mode 100644
index 0000000..6275418
--- /dev/null
+++ b/testing/android/reporter/java/src/org/chromium/test/reporter/TestStatusListener.java
@@ -0,0 +1,78 @@
+// 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.reporter;
+
+import android.content.Context;
+import android.util.Log;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestListener;
+
+/**
+ * A TestListener that reports when tests start, pass, or fail.
+ */
+public class TestStatusListener implements TestListener {
+
+ private static final String TAG = "TestStatusListener";
+
+ private boolean mFailed;
+ private final TestStatusReporter mReporter;
+
+ public TestStatusListener(Context context) {
+ mReporter = new TestStatusReporter(context);
+ }
+
+ /** Called when an error has occurred while running a test.
+
+ Note that an error usually means a problem with the test or test harness, not with
+ the code under test.
+
+ @param test The test in which the error occurred.
+ @param t The exception that was raised.
+ */
+ @Override
+ public void addError(Test test, Throwable t) {
+ Log.e(TAG, "Error while running " + test.toString(), t);
+ mFailed = true;
+ }
+
+ /** Called when a test has failed.
+
+ @param test The test in which the failure occurred.
+ @param t The exception that was raised.
+ */
+ public void addFailure(Test test, AssertionFailedError e) {
+ Log.e(TAG, "Failure while running " + test.toString(), e);
+ mFailed = true;
+ }
+
+ /** Called when a test has started.
+ @param test The test that started.
+ */
+ @Override
+ public void startTest(Test test) {
+ mFailed = false;
+ TestCase testCase = (TestCase) test;
+ mReporter.startHeartbeat();
+ mReporter.testStarted(testCase.getClass().getName(), testCase.getName());
+ }
+
+ /** Called when a test has ended.
+ @param test The test that ended.
+ */
+ @Override
+ public void endTest(Test test) {
+ TestCase testCase = (TestCase) test;
+ if (mFailed) {
+ mReporter.testFailed(testCase.getClass().getName(), testCase.getName());
+ } else {
+ mReporter.testPassed(testCase.getClass().getName(), testCase.getName());
+ }
+ mReporter.stopHeartbeat();
+ }
+
+}
diff --git a/testing/android/reporter/java/src/org/chromium/test/reporter/TestStatusReceiver.java b/testing/android/reporter/java/src/org/chromium/test/reporter/TestStatusReceiver.java
new file mode 100644
index 0000000..e4af9b6
--- /dev/null
+++ b/testing/android/reporter/java/src/org/chromium/test/reporter/TestStatusReceiver.java
@@ -0,0 +1,128 @@
+// 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.reporter;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Receives test status broadcasts send from
+ {@link org.chromium.test.reporter.TestStatusReporter}.
+ */
+public class TestStatusReceiver extends BroadcastReceiver {
+
+ private static final String TAG = "ResultReceiver";
+
+ private final List<FailCallback> mFailCallbacks = new ArrayList<FailCallback>();
+ private final List<HeartbeatCallback> mHeartbeatCallbacks = new ArrayList<HeartbeatCallback>();
+ private final List<PassCallback> mPassCallbacks = new ArrayList<PassCallback>();
+ private final List<StartCallback> mStartCallbacks = new ArrayList<StartCallback>();
+
+ /** An IntentFilter that matches the intents that this class can receive. */
+ private static final IntentFilter INTENT_FILTER;
+ static {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(TestStatusReporter.ACTION_HEARTBEAT);
+ filter.addAction(TestStatusReporter.ACTION_TEST_FAILED);
+ filter.addAction(TestStatusReporter.ACTION_TEST_PASSED);
+ filter.addAction(TestStatusReporter.ACTION_TEST_STARTED);
+ try {
+ filter.addDataType(TestStatusReporter.DATA_TYPE_HEARTBEAT);
+ filter.addDataType(TestStatusReporter.DATA_TYPE_RESULT);
+ } catch (IntentFilter.MalformedMimeTypeException e) {
+ Log.wtf(TAG, "Invalid MIME type", e);
+ }
+ INTENT_FILTER = filter;
+ }
+
+ /** A callback used when a test has failed. */
+ public interface FailCallback {
+ void testFailed(String testClass, String testMethod);
+ }
+
+ /** A callback used when a heartbeat is received. */
+ public interface HeartbeatCallback {
+ void heartbeat();
+ }
+
+ /** A callback used when a test has passed. */
+ public interface PassCallback {
+ void testPassed(String testClass, String testMethod);
+ }
+
+ /** A callback used when a test has started. */
+ public interface StartCallback {
+ void testStarted(String testClass, String testMethod);
+ }
+
+ /** Register a callback for when a test has failed. */
+ public void registerCallback(FailCallback c) {
+ mFailCallbacks.add(c);
+ }
+
+ /** Register a callback for when a heartbeat is received. */
+ public void registerCallback(HeartbeatCallback c) {
+ mHeartbeatCallbacks.add(c);
+ }
+
+ /** Register a callback for when a test has passed. */
+ public void registerCallback(PassCallback c) {
+ mPassCallbacks.add(c);
+ }
+
+ /** Register a callback for when a test has started. */
+ public void registerCallback(StartCallback c) {
+ mStartCallbacks.add(c);
+ }
+
+ /** Register this receiver using the provided context. */
+ public void register(Context c) {
+ c.registerReceiver(this, INTENT_FILTER);
+ }
+
+ /** Receive a broadcast intent.
+ *
+ * @param context The Context in which the receiver is running.
+ * @param intent The intent received.
+ */
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String testClass = intent.getStringExtra(TestStatusReporter.EXTRA_TEST_CLASS);
+ String testMethod = intent.getStringExtra(TestStatusReporter.EXTRA_TEST_METHOD);
+
+ switch (intent.getAction()) {
+ case TestStatusReporter.ACTION_TEST_STARTED:
+ for (StartCallback c : mStartCallbacks) {
+ c.testStarted(testClass, testMethod);
+ }
+ break;
+ case TestStatusReporter.ACTION_TEST_PASSED:
+ for (PassCallback c : mPassCallbacks) {
+ c.testPassed(testClass, testMethod);
+ }
+ break;
+ case TestStatusReporter.ACTION_TEST_FAILED:
+ for (FailCallback c : mFailCallbacks) {
+ c.testFailed(testClass, testMethod);
+ }
+ break;
+ case TestStatusReporter.ACTION_HEARTBEAT:
+ for (HeartbeatCallback c : mHeartbeatCallbacks) {
+ c.heartbeat();
+ }
+ break;
+ default:
+ Log.e(TAG, "Unrecognized intent received: " + intent.toString());
+ break;
+ }
+ }
+
+}
+
diff --git a/testing/android/reporter/java/src/org/chromium/test/reporter/TestStatusReporter.java b/testing/android/reporter/java/src/org/chromium/test/reporter/TestStatusReporter.java
new file mode 100644
index 0000000..6ac7312
--- /dev/null
+++ b/testing/android/reporter/java/src/org/chromium/test/reporter/TestStatusReporter.java
@@ -0,0 +1,83 @@
+// 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.reporter;
+
+import android.content.Context;
+import android.content.Intent;
+
+import org.chromium.base.ThreadUtils;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Broadcasts test status to any listening {@link org.chromium.test.reporter.TestStatusReceiver}.
+ */
+public class TestStatusReporter {
+
+ public static final String ACTION_HEARTBEAT =
+ "org.chromium.test.reporter.TestStatusReporter.HEARTBEAT";
+ public static final String ACTION_TEST_STARTED =
+ "org.chromium.test.reporter.TestStatusReporter.TEST_STARTED";
+ public static final String ACTION_TEST_PASSED =
+ "org.chromium.test.reporter.TestStatusReporter.TEST_PASSED";
+ public static final String ACTION_TEST_FAILED =
+ "org.chromium.test.reporter.TestStatusReporter.TEST_FAILED";
+ public static final String DATA_TYPE_HEARTBEAT = "org.chromium.test.reporter/heartbeat";
+ public static final String DATA_TYPE_RESULT = "org.chromium.test.reporter/result";
+ public static final String EXTRA_TEST_CLASS =
+ "org.chromium.test.reporter.TestStatusReporter.TEST_CLASS";
+ public static final String EXTRA_TEST_METHOD =
+ "org.chromium.test.reporter.TestStatusReporter.TEST_METHOD";
+
+ public static final int HEARTBEAT_INTERVAL_MS = 5000;
+
+ private final Context mContext;
+ private final AtomicBoolean mKeepBeating = new AtomicBoolean(false);
+
+ public TestStatusReporter(Context c) {
+ mContext = c;
+ }
+
+ public void startHeartbeat() {
+ mKeepBeating.set(true);
+ Runnable heartbeat = new Runnable() {
+ @Override
+ public void run() {
+ Intent i = new Intent(ACTION_HEARTBEAT);
+ i.setType(DATA_TYPE_HEARTBEAT);
+ mContext.sendBroadcast(i);
+ if (mKeepBeating.get()) {
+ ThreadUtils.postOnUiThreadDelayed(this, HEARTBEAT_INTERVAL_MS);
+ }
+ }
+ };
+ ThreadUtils.postOnUiThreadDelayed(heartbeat, HEARTBEAT_INTERVAL_MS);
+ }
+
+ public void testStarted(String testClass, String testMethod) {
+ sendBroadcast(testClass, testMethod, ACTION_TEST_STARTED);
+ }
+
+ public void testPassed(String testClass, String testMethod) {
+ sendBroadcast(testClass, testMethod, ACTION_TEST_PASSED);
+ }
+
+ public void testFailed(String testClass, String testMethod) {
+ sendBroadcast(testClass, testMethod, ACTION_TEST_FAILED);
+ }
+
+ public void stopHeartbeat() {
+ mKeepBeating.set(false);
+ }
+
+ private void sendBroadcast(String testClass, String testMethod, String action) {
+ Intent i = new Intent(action);
+ i.setType(DATA_TYPE_RESULT);
+ i.putExtra(EXTRA_TEST_CLASS, testClass);
+ i.putExtra(EXTRA_TEST_METHOD, testMethod);
+ mContext.sendBroadcast(i);
+ }
+
+}