Commit 4d64d862 authored by jbudorick's avatar jbudorick Committed by Commit bot

[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}
parent b9bfea77
...@@ -94,6 +94,8 @@ ...@@ -94,6 +94,8 @@
'../base/base.gyp:base_java_test_support', '../base/base.gyp:base_java_test_support',
'../content/content_shell_and_tests.gyp:content_java_test_support', '../content/content_shell_and_tests.gyp:content_java_test_support',
'../net/net.gyp:net_java_test_support', '../net/net.gyp:net_java_test_support',
'../testing/android/on_device_instrumentation.gyp:broker_java',
'../testing/android/on_device_instrumentation.gyp:require_driver_apk',
'android_webview_apk_java', 'android_webview_apk_java',
], ],
'variables': { 'variables': {
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
<!-- TODO(boliu): Change minSdkVersion to 19 when bots no longer try <!-- TODO(boliu): Change minSdkVersion to 19 when bots no longer try
to install webview apks on < K devices. crbug.com/474374 --> to install webview apks on < K devices. crbug.com/474374 -->
<uses-sdk android:minSdkVersion="16" android:targetSdkVersion="21" /> <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="21" />
<instrumentation android:name="android.test.InstrumentationTestRunner" <instrumentation android:name="org.chromium.base.test.BaseInstrumentationTestRunner"
android:targetPackage="org.chromium.android_webview.shell" android:targetPackage="org.chromium.android_webview.shell"
android:label="Tests for org.chromium.android_webview"/> android:label="Tests for org.chromium.android_webview"/>
<uses-permission android:name="android.permission.RUN_INSTRUMENTATION" /> <uses-permission android:name="android.permission.RUN_INSTRUMENTATION" />
...@@ -19,5 +19,7 @@ ...@@ -19,5 +19,7 @@
needed when building test cases. --> needed when building test cases. -->
<application android:hardwareAccelerated="false"> <application android:hardwareAccelerated="false">
<uses-library android:name="android.test.runner" /> <uses-library android:name="android.test.runner" />
<activity android:name="org.chromium.test.broker.OnDeviceInstrumentationBroker"
android:exported="true"/>
</application> </application>
</manifest> </manifest>
...@@ -1448,6 +1448,7 @@ if (is_android) { ...@@ -1448,6 +1448,7 @@ if (is_android) {
android_library("base_java_test_support") { android_library("base_java_test_support") {
deps = [ deps = [
":base_java", ":base_java",
"//testing/android/reporter:reporter_java",
] ]
DEPRECATED_java_in_dir = "test/android/javatests/src" DEPRECATED_java_in_dir = "test/android/javatests/src"
} }
......
...@@ -176,6 +176,7 @@ public class ThreadUtils { ...@@ -176,6 +176,7 @@ public class ThreadUtils {
* @param task The Runnable to run * @param task The Runnable to run
* @param delayMillis The delay in milliseconds until the Runnable will be run * @param delayMillis The delay in milliseconds until the Runnable will be run
*/ */
@VisibleForTesting
public static void postOnUiThreadDelayed(Runnable task, long delayMillis) { public static void postOnUiThreadDelayed(Runnable task, long delayMillis) {
getUiThreadHandler().postDelayed(task, delayMillis); getUiThreadHandler().postDelayed(task, delayMillis);
} }
......
...@@ -1476,6 +1476,7 @@ ...@@ -1476,6 +1476,7 @@
'type': 'none', 'type': 'none',
'dependencies': [ 'dependencies': [
'base_java', 'base_java',
'../testing/android/on_device_instrumentation.gyp:reporter_java',
], ],
'variables': { 'variables': {
'java_in_dir': '../base/test/android/javatests', 'java_in_dir': '../base/test/android/javatests',
......
...@@ -14,10 +14,12 @@ import junit.framework.TestCase; ...@@ -14,10 +14,12 @@ import junit.framework.TestCase;
import junit.framework.TestResult; import junit.framework.TestResult;
import org.chromium.base.test.util.MinAndroidSdkLevel; import org.chromium.base.test.util.MinAndroidSdkLevel;
import org.chromium.test.reporter.TestStatusListener;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
// TODO(jbudorick): Add support for on-device handling of timeouts.
/** /**
* An Instrumentation test runner that checks SDK level for tests with specific requirements. * An Instrumentation test runner that checks SDK level for tests with specific requirements.
*/ */
...@@ -88,7 +90,7 @@ public class BaseInstrumentationTestRunner extends InstrumentationTestRunner { ...@@ -88,7 +90,7 @@ public class BaseInstrumentationTestRunner extends InstrumentationTestRunner {
@Override @Override
protected AndroidTestRunner getAndroidTestRunner() { protected AndroidTestRunner getAndroidTestRunner() {
return new AndroidTestRunner() { AndroidTestRunner runner = new AndroidTestRunner() {
@Override @Override
protected TestResult createTestResult() { protected TestResult createTestResult() {
SkippingTestResult r = new SkippingTestResult(); SkippingTestResult r = new SkippingTestResult();
...@@ -96,6 +98,8 @@ public class BaseInstrumentationTestRunner extends InstrumentationTestRunner { ...@@ -96,6 +98,8 @@ public class BaseInstrumentationTestRunner extends InstrumentationTestRunner {
return r; return r;
} }
}; };
runner.addTestListener(new TestStatusListener(getContext()));
return runner;
} }
/** /**
......
...@@ -30,6 +30,17 @@ _ACTIVITY_RESULT_OK = -1 ...@@ -30,6 +30,17 @@ _ACTIVITY_RESULT_OK = -1
_DEFAULT_ANNOTATIONS = [ _DEFAULT_ANNOTATIONS = [
'Smoke', 'SmallTest', 'MediumTest', 'LargeTest', 'Smoke', 'SmallTest', 'MediumTest', 'LargeTest',
'EnormousTest', 'IntegrationTest'] 'EnormousTest', 'IntegrationTest']
_EXTRA_ENABLE_HTTP_SERVER = (
'org.chromium.chrome.test.ChromeInstrumentationTestRunner.'
+ 'EnableTestHttpServer')
_EXTRA_DRIVER_TEST_LIST = (
'org.chromium.test.driver.OnDeviceInstrumentationDriver.TestList')
_EXTRA_DRIVER_TEST_LIST_FILE = (
'org.chromium.test.driver.OnDeviceInstrumentationDriver.TestListFile')
_EXTRA_DRIVER_TARGET_PACKAGE = (
'org.chromium.test.driver.OnDeviceInstrumentationDriver.TargetPackage')
_EXTRA_DRIVER_TARGET_CLASS = (
'org.chromium.test.driver.OnDeviceInstrumentationDriver.TargetClass')
_NATIVE_CRASH_RE = re.compile('native crash', re.IGNORECASE) _NATIVE_CRASH_RE = re.compile('native crash', re.IGNORECASE)
_PICKLE_FORMAT_VERSION = 10 _PICKLE_FORMAT_VERSION = 10
...@@ -130,29 +141,35 @@ class InstrumentationTestInstance(test_instance.TestInstance): ...@@ -130,29 +141,35 @@ class InstrumentationTestInstance(test_instance.TestInstance):
self._apk_under_test = None self._apk_under_test = None
self._package_info = None self._package_info = None
self._suite = None
self._test_apk = None self._test_apk = None
self._test_jar = None self._test_jar = None
self._test_package = None self._test_package = None
self._test_runner = None self._test_runner = None
self._test_support_apk = None self._test_support_apk = None
self.__initializeApkAttributes(args, error_func) self._initializeApkAttributes(args, error_func)
self._data_deps = None self._data_deps = None
self._isolate_abs_path = None self._isolate_abs_path = None
self._isolate_delegate = None self._isolate_delegate = None
self._isolated_abs_path = None self._isolated_abs_path = None
self._test_data = None self._test_data = None
self.__initializeDataDependencyAttributes(args, isolate_delegate) self._initializeDataDependencyAttributes(args, isolate_delegate)
self._annotations = None self._annotations = None
self._excluded_annotations = None self._excluded_annotations = None
self._test_filter = None self._test_filter = None
self.__initializeTestFilterAttributes(args) self._initializeTestFilterAttributes(args)
self._flags = None self._flags = None
self.__initializeFlagAttributes(args) self._initializeFlagAttributes(args)
def __initializeApkAttributes(self, args, error_func): self._driver_apk = None
self._driver_package = None
self._driver_name = None
self._initializeDriverAttributes()
def _initializeApkAttributes(self, args, error_func):
if args.apk_under_test.endswith('.apk'): if args.apk_under_test.endswith('.apk'):
self._apk_under_test = args.apk_under_test self._apk_under_test = args.apk_under_test
else: else:
...@@ -164,20 +181,20 @@ class InstrumentationTestInstance(test_instance.TestInstance): ...@@ -164,20 +181,20 @@ class InstrumentationTestInstance(test_instance.TestInstance):
error_func('Unable to find APK under test: %s' % self._apk_under_test) error_func('Unable to find APK under test: %s' % self._apk_under_test)
if args.test_apk.endswith('.apk'): if args.test_apk.endswith('.apk'):
test_apk_root = os.path.splitext(os.path.basename(args.test_apk))[0] self._suite = os.path.splitext(os.path.basename(args.test_apk))[0]
self._test_apk = args.test_apk self._test_apk = args.test_apk
else: else:
test_apk_root = args.test_apk self._suite = args.test_apk
self._test_apk = os.path.join( self._test_apk = os.path.join(
constants.GetOutDirectory(), constants.SDK_BUILD_APKS_DIR, constants.GetOutDirectory(), constants.SDK_BUILD_APKS_DIR,
'%s.apk' % args.test_apk) '%s.apk' % args.test_apk)
self._test_jar = os.path.join( self._test_jar = os.path.join(
constants.GetOutDirectory(), constants.SDK_BUILD_TEST_JAVALIB_DIR, constants.GetOutDirectory(), constants.SDK_BUILD_TEST_JAVALIB_DIR,
'%s.jar' % test_apk_root) '%s.jar' % self._suite)
self._test_support_apk = os.path.join( self._test_support_apk = os.path.join(
constants.GetOutDirectory(), constants.SDK_BUILD_TEST_JAVALIB_DIR, constants.GetOutDirectory(), constants.SDK_BUILD_TEST_JAVALIB_DIR,
'%sSupport.apk' % test_apk_root) '%sSupport.apk' % self._suite)
if not os.path.exists(self._test_apk): if not os.path.exists(self._test_apk):
error_func('Unable to find test APK: %s' % self._test_apk) error_func('Unable to find test APK: %s' % self._test_apk)
...@@ -194,7 +211,7 @@ class InstrumentationTestInstance(test_instance.TestInstance): ...@@ -194,7 +211,7 @@ class InstrumentationTestInstance(test_instance.TestInstance):
if not self._package_info: if not self._package_info:
logging.warning('Unable to find package info for %s', self._test_package) logging.warning('Unable to find package info for %s', self._test_package)
def __initializeDataDependencyAttributes(self, args, isolate_delegate): def _initializeDataDependencyAttributes(self, args, isolate_delegate):
self._data_deps = [] self._data_deps = []
if args.isolate_file_path: if args.isolate_file_path:
self._isolate_abs_path = os.path.abspath(args.isolate_file_path) self._isolate_abs_path = os.path.abspath(args.isolate_file_path)
...@@ -215,7 +232,7 @@ class InstrumentationTestInstance(test_instance.TestInstance): ...@@ -215,7 +232,7 @@ class InstrumentationTestInstance(test_instance.TestInstance):
if not self._isolate_delegate and not self._test_data: if not self._isolate_delegate and not self._test_data:
logging.warning('No data dependencies will be pushed.') logging.warning('No data dependencies will be pushed.')
def __initializeTestFilterAttributes(self, args): def _initializeTestFilterAttributes(self, args):
self._test_filter = args.test_filter self._test_filter = args.test_filter
def annotation_dict_element(a): def annotation_dict_element(a):
...@@ -240,7 +257,7 @@ class InstrumentationTestInstance(test_instance.TestInstance): ...@@ -240,7 +257,7 @@ class InstrumentationTestInstance(test_instance.TestInstance):
else: else:
self._excluded_annotations = {} self._excluded_annotations = {}
def __initializeFlagAttributes(self, args): def _initializeFlagAttributes(self, args):
self._flags = ['--disable-fre', '--enable-test-intents'] self._flags = ['--disable-fre', '--enable-test-intents']
# TODO(jbudorick): Transition "--device-flags" to "--device-flags-file" # TODO(jbudorick): Transition "--device-flags" to "--device-flags-file"
if hasattr(args, 'device_flags') and args.device_flags: if hasattr(args, 'device_flags') and args.device_flags:
...@@ -252,9 +269,17 @@ class InstrumentationTestInstance(test_instance.TestInstance): ...@@ -252,9 +269,17 @@ class InstrumentationTestInstance(test_instance.TestInstance):
stripped_lines = (l.strip() for l in device_flags_file) stripped_lines = (l.strip() for l in device_flags_file)
self._flags.extend([flag for flag in stripped_lines if flag]) self._flags.extend([flag for flag in stripped_lines if flag])
@property def _initializeDriverAttributes(self):
def suite(self): self._driver_apk = os.path.join(
return 'instrumentation' constants.GetOutDirectory(), constants.SDK_BUILD_APKS_DIR,
'OnDeviceInstrumentationDriver.apk')
if os.path.exists(self._driver_apk):
self._driver_package = apk_helper.GetPackageName(
self._driver_apk)
self._driver_name = apk_helper.GetInstrumentationName(
self._driver_apk)
else:
self._driver_apk = None
@property @property
def apk_under_test(self): def apk_under_test(self):
...@@ -264,10 +289,26 @@ class InstrumentationTestInstance(test_instance.TestInstance): ...@@ -264,10 +289,26 @@ class InstrumentationTestInstance(test_instance.TestInstance):
def flags(self): def flags(self):
return self._flags return self._flags
@property
def driver_apk(self):
return self._driver_apk
@property
def driver_package(self):
return self._driver_package
@property
def driver_name(self):
return self._driver_name
@property @property
def package_info(self): def package_info(self):
return self._package_info return self._package_info
@property
def suite(self):
return self._suite
@property @property
def test_apk(self): def test_apk(self):
return self._test_apk return self._test_apk
...@@ -445,6 +486,28 @@ class InstrumentationTestInstance(test_instance.TestInstance): ...@@ -445,6 +486,28 @@ class InstrumentationTestInstance(test_instance.TestInstance):
}) })
return inflated_tests return inflated_tests
@staticmethod
def GetHttpServerEnvironmentVars():
return {
_EXTRA_ENABLE_HTTP_SERVER: None,
}
def GetDriverEnvironmentVars(
self, test_list=None, test_list_file_path=None):
env = {
_EXTRA_DRIVER_TARGET_PACKAGE: self.test_package,
_EXTRA_DRIVER_TARGET_CLASS: self.test_runner,
}
if test_list:
env[_EXTRA_DRIVER_TEST_LIST] = ','.join(test_list)
if test_list_file_path:
env[_EXTRA_DRIVER_TEST_LIST_FILE] = (
os.path.basename(test_list_file_path))
return env
@staticmethod @staticmethod
def ParseAmInstrumentRawOutput(raw_output): def ParseAmInstrumentRawOutput(raw_output):
return ParseAmInstrumentRawOutput(raw_output) return ParseAmInstrumentRawOutput(raw_output)
......
...@@ -130,22 +130,41 @@ class LocalDeviceInstrumentationTestRun( ...@@ -130,22 +130,41 @@ class LocalDeviceInstrumentationTestRun(
#override #override
def _RunTest(self, device, test): def _RunTest(self, device, test):
test_name = self._GetTestName(test) extras = self._test_instance.GetHttpServerEnvironmentVars()
logging.info('preparing to run %s: %s' % (test_name, test))
if isinstance(test, list):
if not self._test_instance.driver_apk:
raise Exception('driver_apk does not exist. '
'Please build it and try again.')
def name_and_timeout(t):
n = self._GetTestName(t)
i = self._GetTimeoutFromAnnotations(t['annotations'], n)
return (n, i)
test_names, timeouts = zip(*(name_and_timeout(t) for t in test))
test_name = ','.join(test_names)
target = '%s/%s' % (
self._test_instance.driver_package,
self._test_instance.driver_name)
extras.update(
self._test_instance.GetDriverEnvironmentVars(
test_list=test_names))
timeout = sum(timeouts)
else:
test_name = self._GetTestName(test)
target = '%s/%s' % (
self._test_instance.test_package, self._test_instance.test_runner)
extras['class'] = test_name
timeout = self._GetTimeoutFromAnnotations(test['annotations'], test_name)
extras = { logging.info('preparing to run %s: %s' % (test_name, test))
'class': test_name,
'org.chromium.chrome.test.ChromeInstrumentationTestRunner'
'.EnableTestHttpServer': '',
}
timeout = self._GetTimeoutFromAnnotations(test['annotations'], test_name)
time_ms = lambda: int(time.time() * 1e3) time_ms = lambda: int(time.time() * 1e3)
start_ms = time_ms() start_ms = time_ms()
output = device.StartInstrumentation( output = device.StartInstrumentation(
'%s/%s' % (self._test_instance.test_package, target, raw=True, extras=extras, timeout=timeout, retries=0)
self._test_instance.test_runner),
raw=True, extras=extras, timeout=timeout, retries=0)
duration_ms = time_ms() - start_ms duration_ms = time_ms() - start_ms
# TODO(jbudorick): Make instrumentation tests output a JSON so this # TODO(jbudorick): Make instrumentation tests output a JSON so this
......
...@@ -20,4 +20,5 @@ def TestHttpResponse(response, error_msg): ...@@ -20,4 +20,5 @@ def TestHttpResponse(response, error_msg):
error_msg: Error message to display if bad response is seen. error_msg: Error message to display if bad response is seen.
""" """
if response.status_code != 200: if response.status_code != 200:
raise RemoteDeviceError(error_msg) raise RemoteDeviceError(
'%s (%d: %s)' % (error_msg, response.status_code, response.reason))
...@@ -8,6 +8,7 @@ import logging ...@@ -8,6 +8,7 @@ import logging
import os import os
import tempfile import tempfile
from pylib import constants
from pylib.base import base_test_result from pylib.base import base_test_result
from pylib.remote.device import remote_device_test_run from pylib.remote.device import remote_device_test_run
from pylib.utils import apk_helper from pylib.utils import apk_helper
...@@ -25,9 +26,33 @@ class RemoteDeviceInstrumentationTestRun( ...@@ -25,9 +26,33 @@ class RemoteDeviceInstrumentationTestRun(
def _TriggerSetUp(self): def _TriggerSetUp(self):
"""Set up the triggering of a test run.""" """Set up the triggering of a test run."""
logging.info('Triggering test run.') logging.info('Triggering test run.')
self._AmInstrumentTestSetup(
self._test_instance._apk_under_test, self._test_instance.test_apk, with tempfile.NamedTemporaryFile(suffix='.txt') as test_list_file:
self._test_instance.test_runner, environment_variables={}) tests = self._test_instance.GetTests()
logging.debug('preparing to run %d instrumentation tests remotely:',
len(tests))
for t in tests:
test_name = '%s#%s' % (t['class'], t['method'])
logging.debug(' %s', test_name)
test_list_file.write('%s\n' % test_name)
test_list_file.flush()
self._test_instance._data_deps.append(
(os.path.abspath(test_list_file.name), None))
env_vars = self._test_instance.GetDriverEnvironmentVars(
test_list_file_path=test_list_file.name)
env_vars.update(self._test_instance.GetHttpServerEnvironmentVars())
logging.debug('extras:')
for k, v in env_vars.iteritems():
logging.debug(' %s: %s', k, v)
self._AmInstrumentTestSetup(
self._test_instance.apk_under_test,
self._test_instance.driver_apk,
self._test_instance.driver_name,
environment_variables=env_vars,
extra_apks=[self._test_instance.test_apk])
#override #override
def _ParseTestResults(self): def _ParseTestResults(self):
......
...@@ -201,7 +201,7 @@ class RemoteDeviceTestRun(test_run.TestRun): ...@@ -201,7 +201,7 @@ class RemoteDeviceTestRun(test_run.TestRun):
return self._results['status'] return self._results['status']
def _AmInstrumentTestSetup(self, app_path, test_path, runner_package, def _AmInstrumentTestSetup(self, app_path, test_path, runner_package,
environment_variables): environment_variables, extra_apks=None):
config = {'runner': runner_package} config = {'runner': runner_package}
if environment_variables: if environment_variables:
config['environment_vars'] = ','.join( config['environment_vars'] = ','.join(
...@@ -213,6 +213,7 @@ class RemoteDeviceTestRun(test_run.TestRun): ...@@ -213,6 +213,7 @@ class RemoteDeviceTestRun(test_run.TestRun):
if data_deps: if data_deps:
with tempfile.NamedTemporaryFile(suffix='.zip') as test_with_deps: with tempfile.NamedTemporaryFile(suffix='.zip') as test_with_deps:
sdcard_files = [] sdcard_files = []
additional_apks = []
host_test = os.path.basename(test_path) host_test = os.path.basename(test_path)
with zipfile.ZipFile(test_with_deps.name, 'w') as zip_file: with zipfile.ZipFile(test_with_deps.name, 'w') as zip_file:
zip_file.write(test_path, host_test, zipfile.ZIP_DEFLATED) zip_file.write(test_path, host_test, zipfile.ZIP_DEFLATED)
...@@ -223,8 +224,14 @@ class RemoteDeviceTestRun(test_run.TestRun): ...@@ -223,8 +224,14 @@ class RemoteDeviceTestRun(test_run.TestRun):
else: else:
zip_utils.WriteToZipFile(zip_file, h, os.path.basename(h)) zip_utils.WriteToZipFile(zip_file, h, os.path.basename(h))
sdcard_files.append(os.path.basename(h)) sdcard_files.append(os.path.basename(h))
for a in extra_apks or ():
zip_utils.WriteToZipFile(zip_file, a, os.path.basename(a));
additional_apks.append(os.path.basename(a))
config['sdcard_files'] = ','.join(sdcard_files) config['sdcard_files'] = ','.join(sdcard_files)
config['host_test'] = host_test config['host_test'] = host_test
if additional_apks:
config['additional_apks'] = ','.join(additional_apks)
self._test_id = self._UploadTestToDevice( self._test_id = self._UploadTestToDevice(
'robotium', test_with_deps.name, app_id=self._app_id) 'robotium', test_with_deps.name, app_id=self._app_id)
else: else:
...@@ -238,7 +245,8 @@ class RemoteDeviceTestRun(test_run.TestRun): ...@@ -238,7 +245,8 @@ class RemoteDeviceTestRun(test_run.TestRun):
def _UploadAppToDevice(self, app_path): def _UploadAppToDevice(self, app_path):
"""Upload app to device.""" """Upload app to device."""
logging.info('Uploading %s to remote service.', app_path) logging.info('Uploading %s to remote service as %s.', app_path,
self._test_instance.suite)
with open(app_path, 'rb') as apk_src: with open(app_path, 'rb') as apk_src:
with appurify_sanitized.SanitizeLogging(self._env.verbose_count, with appurify_sanitized.SanitizeLogging(self._env.verbose_count,
logging.WARNING): logging.WARNING):
...@@ -297,4 +305,4 @@ class RemoteDeviceTestRun(test_run.TestRun): ...@@ -297,4 +305,4 @@ class RemoteDeviceTestRun(test_run.TestRun):
config_response = appurify_sanitized.api.config_upload( config_response = appurify_sanitized.api.config_upload(
self._env.token, config, self._test_id) self._env.token, config, self._test_id)
remote_device_helper.TestHttpResponse( remote_device_helper.TestHttpResponse(
config_response, 'Unable to upload test config.') config_response, 'Unable to upload test config.')
\ No newline at end of file
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
'<(DEPTH)/base/base.gyp:base_java', '<(DEPTH)/base/base.gyp:base_java',
'<(DEPTH)/build/android/pylib/device/commands/commands.gyp:chromium_commands', '<(DEPTH)/build/android/pylib/device/commands/commands.gyp:chromium_commands',
'<(DEPTH)/build/android/pylib/remote/device/dummy/dummy.gyp:remote_device_dummy_apk', '<(DEPTH)/build/android/pylib/remote/device/dummy/dummy.gyp:remote_device_dummy_apk',
'<(DEPTH)/testing/android/appurify_support.gyp:appurify_support_java',
'<(DEPTH)/tools/android/android_tools.gyp:android_tools', '<(DEPTH)/tools/android/android_tools.gyp:android_tools',
], ],
'conditions': [ 'conditions': [
......
...@@ -1566,6 +1566,7 @@ template("unittest_apk") { ...@@ -1566,6 +1566,7 @@ template("unittest_apk") {
deps = [ deps = [
"//base:base_java", "//base:base_java",
"//build/android/pylib/remote/device/dummy:remote_device_dummy_apk", "//build/android/pylib/remote/device/dummy:remote_device_dummy_apk",
"//testing/android/appurify_support:appurify_support_java",
] ]
if (defined(invoker.deps)) { if (defined(invoker.deps)) {
deps += invoker.deps deps += invoker.deps
......
...@@ -449,8 +449,12 @@ android_apk("chrome_shell_test_apk") { ...@@ -449,8 +449,12 @@ android_apk("chrome_shell_test_apk") {
":chrome_javatests", ":chrome_javatests",
":chrome_shell_test_java", ":chrome_shell_test_java",
"//sync/android:sync_javatests", "//sync/android:sync_javatests",
"//testing/android/broker:broker_java",
"//ui/android:ui_javatests", "//ui/android:ui_javatests",
] ]
datadeps = [
"//testing/android/driver:driver_apk",
]
apk_name = "ChromeShellTest" apk_name = "ChromeShellTest"
android_manifest = "shell/javatests/AndroidManifest.xml" android_manifest = "shell/javatests/AndroidManifest.xml"
} }
......
...@@ -17,6 +17,9 @@ ...@@ -17,6 +17,9 @@
<provider android:name="org.chromium.chrome.test.partnercustomizations.TestPartnerBrowserCustomizationsDelayedProvider" <provider android:name="org.chromium.chrome.test.partnercustomizations.TestPartnerBrowserCustomizationsDelayedProvider"
android:authorities="org.chromium.chrome.test.partnercustomizations.TestPartnerBrowserCustomizationsDelayedProvider" /> android:authorities="org.chromium.chrome.test.partnercustomizations.TestPartnerBrowserCustomizationsDelayedProvider" />
<activity android:name="org.chromium.test.broker.OnDeviceInstrumentationBroker"
android:exported="true"/>
<activity android:name="org.chromium.sync.test.util.MockGrantCredentialsPermissionActivity" <activity android:name="org.chromium.sync.test.util.MockGrantCredentialsPermissionActivity"
android:exported="false"> android:exported="false">
<intent-filter> <intent-filter>
......
...@@ -2938,6 +2938,8 @@ ...@@ -2938,6 +2938,8 @@
'../components/components.gyp:precache_javatests', '../components/components.gyp:precache_javatests',
'../content/content_shell_and_tests.gyp:content_java_test_support', '../content/content_shell_and_tests.gyp:content_java_test_support',
'../sync/sync.gyp:sync_javatests', '../sync/sync.gyp:sync_javatests',
'../testing/android/on_device_instrumentation.gyp:broker_java',
'../testing/android/on_device_instrumentation.gyp:require_driver_apk',
'../ui/android/ui_android.gyp:ui_javatests', '../ui/android/ui_android.gyp:ui_javatests',
], ],
'variables': { 'variables': {
......
...@@ -1966,6 +1966,8 @@ ...@@ -1966,6 +1966,8 @@
'../net/net.gyp:net_java', '../net/net.gyp:net_java',
'../net/net.gyp:net_javatests', '../net/net.gyp:net_javatests',
'../net/net.gyp:net_java_test_support', '../net/net.gyp:net_java_test_support',
'../testing/android/on_device_instrumentation.gyp:broker_java',
'../testing/android/on_device_instrumentation.gyp:require_driver_apk',
'../third_party/mojo/mojo_public.gyp:mojo_public_test_interfaces', '../third_party/mojo/mojo_public.gyp:mojo_public_test_interfaces',
], ],
'variables': { 'variables': {
......
...@@ -153,9 +153,11 @@ android_apk("content_shell_test_apk") { ...@@ -153,9 +153,11 @@ android_apk("content_shell_test_apk") {
"//content/public/android:content_javatests", "//content/public/android:content_javatests",
"//base:base_javatests", "//base:base_javatests",
"//net/android:net_javatests", "//net/android:net_javatests",
"//testing/android/broker:broker_java",
] ]
datadeps = [ datadeps = [
":content_shell_apk", ":content_shell_apk",
"//testing/android/driver:driver_apk",
] ]
apk_under_test = ":content_shell_apk" apk_under_test = ":content_shell_apk"
apk_name = "ContentShellTest" apk_name = "ContentShellTest"
......
...@@ -11,9 +11,11 @@ ...@@ -11,9 +11,11 @@
needed when building test cases. --> needed when building test cases. -->
<application> <application>
<uses-library android:name="android.test.runner" /> <uses-library android:name="android.test.runner" />
<activity android:name="org.chromium.test.broker.OnDeviceInstrumentationBroker"
android:exported="true"/>
</application> </application>
<uses-sdk android:minSdkVersion="16" android:targetSdkVersion="22" /> <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="22" />
<instrumentation android:name="android.test.InstrumentationTestRunner" <instrumentation android:name="org.chromium.base.test.BaseInstrumentationTestRunner"
android:targetPackage="org.chromium.content_shell_apk" android:targetPackage="org.chromium.content_shell_apk"
android:label="Tests for org.chromium.content_shell_apk"/> android:label="Tests for org.chromium.content_shell_apk"/>
<uses-permission android:name="android.permission.RUN_INSTRUMENTATION" /> <uses-permission android:name="android.permission.RUN_INSTRUMENTATION" />
......
# 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',
],
},
],
}],
],
}
# 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",
]
}
// 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);
}
// 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;
}
}
# 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" ]
}
// 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();
}
}
# 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" ]
}
<?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>
...@@ -12,6 +12,9 @@ import android.os.Bundle; ...@@ -12,6 +12,9 @@ import android.os.Bundle;
import android.os.Environment; import android.os.Environment;
import android.util.Log; import android.util.Log;
import org.chromium.test.support.ResultsBundleGenerator;
import org.chromium.test.support.RobotiumBundleGenerator;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
...@@ -38,10 +41,6 @@ public class ChromeNativeTestInstrumentationTestRunner extends Instrumentation { ...@@ -38,10 +41,6 @@ public class ChromeNativeTestInstrumentationTestRunner extends Instrumentation {
private static final int ACCEPT_TIMEOUT_MS = 5000; private static final int ACCEPT_TIMEOUT_MS = 5000;
private static final Pattern RE_TEST_OUTPUT = Pattern.compile("\\[ *([^ ]*) *\\] ?([^ ]+) .*"); 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 mCommandLineFile;
private String mCommandLineFlags; private String mCommandLineFlags;
private File mStdoutFile; private File mStdoutFile;
...@@ -91,7 +90,7 @@ public class ChromeNativeTestInstrumentationTestRunner extends Instrumentation { ...@@ -91,7 +90,7 @@ public class ChromeNativeTestInstrumentationTestRunner extends Instrumentation {
} }
Log.i(TAG, "Getting results."); 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."); Log.i(TAG, "Parsing results and generating output.");
return mBundleGenerator.generate(results); return mBundleGenerator.generate(results);
...@@ -117,16 +116,14 @@ public class ChromeNativeTestInstrumentationTestRunner extends Instrumentation { ...@@ -117,16 +116,14 @@ public class ChromeNativeTestInstrumentationTestRunner extends Instrumentation {
return startActivitySync(i); 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 * Generates a map between test names and test results from the instrumented Activity's
* output. * output.
*/ */
private Map<String, TestResult> parseResults(Activity activityUnderTest) { private Map<String, ResultsBundleGenerator.TestResult> parseResults(
Map<String, TestResult> results = new HashMap<String, TestResult>(); Activity activityUnderTest) {
Map<String, ResultsBundleGenerator.TestResult> results =
new HashMap<String, ResultsBundleGenerator.TestResult>();
BufferedReader r = null; BufferedReader r = null;
...@@ -145,14 +142,14 @@ public class ChromeNativeTestInstrumentationTestRunner extends Instrumentation { ...@@ -145,14 +142,14 @@ public class ChromeNativeTestInstrumentationTestRunner extends Instrumentation {
boolean isFailure = false; boolean isFailure = false;
if (m.matches()) { if (m.matches()) {
if (m.group(1).equals("RUN")) { 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")) { } else if (m.group(1).equals("FAILED")) {
results.put(m.group(2), TestResult.FAILED); results.put(m.group(2), ResultsBundleGenerator.TestResult.FAILED);
isFailure = true; isFailure = true;
mLogBundle.putString(Instrumentation.REPORT_KEY_STREAMRESULT, l + "\n"); mLogBundle.putString(Instrumentation.REPORT_KEY_STREAMRESULT, l + "\n");
sendStatus(0, mLogBundle); sendStatus(0, mLogBundle);
} else if (m.group(1).equals("OK")) { } 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 { ...@@ -185,46 +182,4 @@ public class ChromeNativeTestInstrumentationTestRunner extends Instrumentation {
return results; 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;
}
}
} }
# 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)',
],
},
],
},
],
}],
],
}
# 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",
]
}
// 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();
}
}
// 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;
}
}
}
// 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);
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment