summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjbudorick <jbudorick@chromium.org>2015-06-12 06:06:12 -0700
committerCommit bot <commit-bot@chromium.org>2015-06-12 13:06:54 +0000
commitb049103fa4848e304102a24e78c6ba994130a535 (patch)
tree3dc71fa65201527d337aedc11dba4c2fe51c1e07
parent23490d44633f4eaeb76e03384e08e9fd3ef2909a (diff)
downloadchromium_src-b049103fa4848e304102a24e78c6ba994130a535.zip
chromium_src-b049103fa4848e304102a24e78c6ba994130a535.tar.gz
chromium_src-b049103fa4848e304102a24e78c6ba994130a535.tar.bz2
[Android] Refactor browser test execution.
This change allows us to run multiple browser tests within a single invocation of an app. It does so by moving the test activity into its own process, running each test individually within that activity, and killing the process between each test. BUG=472360 Review URL: https://codereview.chromium.org/1165523002 Cr-Commit-Position: refs/heads/master@{#334149}
-rw-r--r--build/android/pylib/gtest/gtest_test_instance.py68
-rw-r--r--build/android/pylib/gtest/local_device_gtest_run.py73
-rw-r--r--build/android/pylib/gtest/setup.py23
-rwxr-xr-xbuild/android/test_runner.py4
-rw-r--r--build/apk_test.gypi1
-rw-r--r--build/config/android/internal_rules.gni8
-rw-r--r--build/config/android/rules.gni1
-rw-r--r--components/components_tests.gyp1
-rw-r--r--components/test/DEPS1
-rw-r--r--components/test/android/browsertests_apk/AndroidManifest.xml.jinja23
-rw-r--r--components/test/android/browsertests_apk/src/org/chromium/components_browsertests_apk/ComponentsBrowserTestsActivity.java58
-rw-r--r--components/test/android/browsertests_apk/src/org/chromium/components_browsertests_apk/ComponentsBrowserTestsApplication.java2
-rw-r--r--content/content_tests.gypi16
-rw-r--r--content/shell/android/browsertests/src/org/chromium/content_shell/browsertests/ContentShellBrowserTestActivity.java89
-rw-r--r--content/shell/android/browsertests_apk/AndroidManifest.xml.jinja23
-rw-r--r--content/shell/android/browsertests_apk/src/org/chromium/content_browsertests_apk/ContentBrowserTestsActivity.java54
-rw-r--r--content/shell/android/browsertests_apk/src/org/chromium/content_browsertests_apk/ContentBrowserTestsApplication.java2
-rw-r--r--testing/android/appurify_support/java/src/org/chromium/test/support/RobotiumBundleGenerator.java13
-rw-r--r--testing/android/native_test.gyp1
-rw-r--r--testing/android/native_test/java/src/org/chromium/native_test/NativeBrowserTestActivity.java41
-rw-r--r--testing/android/native_test/java/src/org/chromium/native_test/NativeTestActivity.java31
-rw-r--r--testing/android/native_test/java/src/org/chromium/native_test/NativeTestInstrumentationTestRunner.java225
-rw-r--r--testing/android/native_test/java/src/org/chromium/native_test/NativeUnitTestActivity.java2
-rw-r--r--testing/android/reporter/java/src/org/chromium/test/reporter/TestStatusReceiver.java30
-rw-r--r--testing/android/reporter/java/src/org/chromium/test/reporter/TestStatusReporter.java37
25 files changed, 594 insertions, 193 deletions
diff --git a/build/android/pylib/gtest/gtest_test_instance.py b/build/android/pylib/gtest/gtest_test_instance.py
index ed1190f..3285e0b 100644
--- a/build/android/pylib/gtest/gtest_test_instance.py
+++ b/build/android/pylib/gtest/gtest_test_instance.py
@@ -12,6 +12,7 @@ import tempfile
from pylib import constants
from pylib.base import base_test_result
from pylib.base import test_instance
+from pylib.utils import apk_helper
sys.path.append(os.path.join(
constants.DIR_SOURCE_ROOT, 'build', 'util', 'lib', 'common'))
@@ -24,6 +25,29 @@ BROWSER_TEST_SUITES = [
]
+_DEFAULT_ISOLATE_FILE_PATHS = {
+ 'base_unittests': 'base/base_unittests.isolate',
+ 'blink_heap_unittests':
+ 'third_party/WebKit/Source/platform/heap/BlinkHeapUnitTests.isolate',
+ 'breakpad_unittests': 'breakpad/breakpad_unittests.isolate',
+ 'cc_perftests': 'cc/cc_perftests.isolate',
+ 'components_browsertests': 'components/components_browsertests.isolate',
+ 'components_unittests': 'components/components_unittests.isolate',
+ 'content_browsertests': 'content/content_browsertests.isolate',
+ 'content_unittests': 'content/content_unittests.isolate',
+ 'media_perftests': 'media/media_perftests.isolate',
+ 'media_unittests': 'media/media_unittests.isolate',
+ 'midi_unittests': 'media/midi/midi_unittests.isolate',
+ 'net_unittests': 'net/net_unittests.isolate',
+ 'sql_unittests': 'sql/sql_unittests.isolate',
+ 'sync_unit_tests': 'sync/sync_unit_tests.isolate',
+ 'ui_base_unittests': 'ui/base/ui_base_tests.isolate',
+ 'unit_tests': 'chrome/unit_tests.isolate',
+ 'webkit_unit_tests':
+ 'third_party/WebKit/Source/web/WebKitUnitTests.isolate',
+}
+
+
# Used for filtering large data deps at a finer grain than what's allowed in
# isolate files since pushing deps to devices is expensive.
# Wildcards are allowed.
@@ -47,6 +71,13 @@ _DEPS_EXCLUSION_LIST = [
]
+_EXTRA_NATIVE_TEST_ACTIVITY = (
+ 'org.chromium.native_test.NativeTestInstrumentationTestRunner.'
+ 'NativeTestActivity')
+_EXTRA_SHARD_SIZE_LIMIT =(
+ 'org.chromium.native_test.NativeTestInstrumentationTestRunner.'
+ 'ShardSizeLimit')
+
# TODO(jbudorick): Remove these once we're no longer parsing stdout to generate
# results.
_RE_TEST_STATUS = re.compile(
@@ -106,6 +137,20 @@ class GtestTestInstance(test_instance.TestInstance):
self._suite)
if not os.path.exists(self._apk_path):
self._apk_path = None
+ self._activity = None
+ self._package = None
+ self._runner = None
+ else:
+ helper = apk_helper.ApkHelper(self._apk_path)
+ self._activity = helper.GetActivityName()
+ self._package = helper.GetPackageName()
+ self._runner = helper.GetInstrumentationName()
+ self._extras = {
+ _EXTRA_NATIVE_TEST_ACTIVITY: self._activity,
+ }
+ if self._suite in BROWSER_TEST_SUITES:
+ self._extras[_EXTRA_SHARD_SIZE_LIMIT] = 1
+
if not os.path.exists(self._exe_path):
self._exe_path = None
if not self._apk_path and not self._exe_path:
@@ -119,6 +164,13 @@ class GtestTestInstance(test_instance.TestInstance):
self._gtest_filter = ':'.join(l.strip() for l in f)
else:
self._gtest_filter = None
+
+ if not args.isolate_file_path:
+ default_isolate_file_path = _DEFAULT_ISOLATE_FILE_PATHS.get(self._suite)
+ if default_isolate_file_path:
+ args.isolate_file_path = os.path.join(
+ constants.DIR_SOURCE_ROOT, default_isolate_file_path)
+
if args.isolate_file_path:
self._isolate_abs_path = os.path.abspath(args.isolate_file_path)
self._isolate_delegate = isolate_delegate
@@ -240,6 +292,10 @@ class GtestTestInstance(test_instance.TestInstance):
self._isolate_delegate.Clear()
@property
+ def activity(self):
+ return self._activity
+
+ @property
def apk(self):
return self._apk_path
@@ -256,6 +312,18 @@ class GtestTestInstance(test_instance.TestInstance):
return self._exe_path
@property
+ def extras(self):
+ return self._extras
+
+ @property
+ def package(self):
+ return self._package
+
+ @property
+ def runner(self):
+ return self._runner
+
+ @property
def suite(self):
return self._suite
diff --git a/build/android/pylib/gtest/local_device_gtest_run.py b/build/android/pylib/gtest/local_device_gtest_run.py
index c375b97..f1cea4e 100644
--- a/build/android/pylib/gtest/local_device_gtest_run.py
+++ b/build/android/pylib/gtest/local_device_gtest_run.py
@@ -16,7 +16,6 @@ from pylib.gtest import gtest_test_instance
from pylib.local import local_test_server_spawner
from pylib.local.device import local_device_environment
from pylib.local.device import local_device_test_run
-from pylib.utils import apk_helper
from pylib.utils import device_temp_file
_COMMAND_LINE_FLAGS_SUPPORTED = True
@@ -25,9 +24,9 @@ _EXTRA_COMMAND_LINE_FILE = (
'org.chromium.native_test.NativeTestActivity.CommandLineFile')
_EXTRA_COMMAND_LINE_FLAGS = (
'org.chromium.native_test.NativeTestActivity.CommandLineFlags')
-_EXTRA_NATIVE_TEST_ACTIVITY = (
+_EXTRA_TEST_LIST = (
'org.chromium.native_test.NativeTestInstrumentationTestRunner'
- '.NativeTestActivity')
+ '.TestList')
_MAX_SHARD_SIZE = 256
@@ -53,30 +52,32 @@ def PullAppFilesImpl(device, package, files, directory):
device.PullFile(device_file, host_file)
class _ApkDelegate(object):
- def __init__(self, apk):
- self._apk = apk
+ def __init__(self, test_instance):
+ self._activity = test_instance.activity
+ self._apk = test_instance.apk
+ self._package = test_instance.package
+ self._runner = test_instance.runner
- helper = apk_helper.ApkHelper(self._apk)
- self._activity = helper.GetActivityName()
- self._package = helper.GetPackageName()
- self._runner = helper.GetInstrumentationName()
self._component = '%s/%s' % (self._package, self._runner)
- self._enable_test_server_spawner = False
+ self._extras = test_instance.extras
def Install(self, device):
device.Install(self._apk)
- def RunWithFlags(self, device, flags, **kwargs):
+ def Run(self, test, device, flags=None, **kwargs):
+ extras = dict(self._extras)
+
with device_temp_file.DeviceTempFile(device.adb) as command_line_file:
- device.WriteFile(command_line_file.name, '_ %s' % flags)
+ device.WriteFile(command_line_file.name, '_ %s' % flags if flags else '_')
+ extras[_EXTRA_COMMAND_LINE_FILE] = command_line_file.name
- extras = {
- _EXTRA_COMMAND_LINE_FILE: command_line_file.name,
- _EXTRA_NATIVE_TEST_ACTIVITY: self._activity,
- }
+ with device_temp_file.DeviceTempFile(device.adb) as test_list_file:
+ if test:
+ device.WriteFile(test_list_file.name, '\n'.join(test))
+ extras[_EXTRA_TEST_LIST] = test_list_file.name
- return device.StartInstrumentation(
- self._component, extras=extras, raw=False, **kwargs)
+ return device.StartInstrumentation(
+ self._component, extras=extras, raw=False, **kwargs)
def PullAppFiles(self, device, files, directory):
PullAppFilesImpl(device, self._package, files, directory)
@@ -86,7 +87,7 @@ class _ApkDelegate(object):
class _ExeDelegate(object):
- def __init__(self, exe, tr):
+ def __init__(self, tr, exe):
self._exe_host_path = exe
self._exe_file_name = os.path.split(exe)[-1]
self._exe_device_path = '%s/%s' % (
@@ -107,12 +108,15 @@ class _ExeDelegate(object):
host_device_tuples.append((self._deps_host_path, self._deps_device_path))
device.PushChangedFiles(host_device_tuples)
- def RunWithFlags(self, device, flags, **kwargs):
+ def Run(self, test, device, flags=None, **kwargs):
cmd = [
self._test_run.GetTool(device).GetTestWrapper(),
self._exe_device_path,
- flags,
]
+ if test:
+ cmd.append('--gtest_filter=%s' % ':'.join(test))
+ if flags:
+ cmd.append(flags)
cwd = constants.TEST_EXECUTABLE_DIR
env = {
@@ -152,7 +156,7 @@ class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun):
super(LocalDeviceGtestRun, self).__init__(env, test_instance)
if self._test_instance.apk:
- self._delegate = _ApkDelegate(self._test_instance.apk)
+ self._delegate = _ApkDelegate(self._test_instance)
elif self._test_instance.exe:
self._delegate = _ExeDelegate(self, self._test_instance.exe)
@@ -194,21 +198,18 @@ class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun):
#override
def _CreateShards(self, tests):
- if self._test_instance.suite in gtest_test_instance.BROWSER_TEST_SUITES:
- return tests
- else:
- device_count = len(self._env.devices)
- shards = []
- for i in xrange(0, device_count):
- unbounded_shard = tests[i::device_count]
- shards += [unbounded_shard[j:j+_MAX_SHARD_SIZE]
- for j in xrange(0, len(unbounded_shard), _MAX_SHARD_SIZE)]
- return [':'.join(s) for s in shards]
+ device_count = len(self._env.devices)
+ shards = []
+ for i in xrange(0, device_count):
+ unbounded_shard = tests[i::device_count]
+ shards += [unbounded_shard[j:j+_MAX_SHARD_SIZE]
+ for j in xrange(0, len(unbounded_shard), _MAX_SHARD_SIZE)]
+ return shards
#override
def _GetTests(self):
- tests = self._delegate.RunWithFlags(
- self._env.devices[0], '--gtest_list_tests')
+ tests = self._delegate.Run(
+ None, self._env.devices[0], flags='--gtest_list_tests')
tests = gtest_test_instance.ParseGTestListTests(tests)
tests = self._test_instance.FilterTests(tests)
return tests
@@ -216,8 +217,8 @@ class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun):
#override
def _RunTest(self, device, test):
# Run the test.
- output = self._delegate.RunWithFlags(
- device, '--gtest_filter=%s' % test, timeout=900, retries=0)
+ output = self._delegate.Run(
+ test, device, timeout=900, retries=0)
for s in self._servers[str(device)]:
s.Reset()
if self._test_instance.app_files:
diff --git a/build/android/pylib/gtest/setup.py b/build/android/pylib/gtest/setup.py
index 8ba9a8d..f563ccf 100644
--- a/build/android/pylib/gtest/setup.py
+++ b/build/android/pylib/gtest/setup.py
@@ -26,27 +26,8 @@ sys.path.insert(0,
import unittest_util # pylint: disable=F0401
-ISOLATE_FILE_PATHS = {
- 'base_unittests': 'base/base_unittests.isolate',
- 'blink_heap_unittests':
- 'third_party/WebKit/Source/platform/heap/BlinkHeapUnitTests.isolate',
- 'breakpad_unittests': 'breakpad/breakpad_unittests.isolate',
- 'cc_perftests': 'cc/cc_perftests.isolate',
- 'components_browsertests': 'components/components_browsertests.isolate',
- 'components_unittests': 'components/components_unittests.isolate',
- 'content_browsertests': 'content/content_browsertests.isolate',
- 'content_unittests': 'content/content_unittests.isolate',
- 'media_perftests': 'media/media_perftests.isolate',
- 'media_unittests': 'media/media_unittests.isolate',
- 'midi_unittests': 'media/midi/midi_unittests.isolate',
- 'net_unittests': 'net/net_unittests.isolate',
- 'sql_unittests': 'sql/sql_unittests.isolate',
- 'sync_unit_tests': 'sync/sync_unit_tests.isolate',
- 'ui_base_unittests': 'ui/base/ui_base_tests.isolate',
- 'unit_tests': 'chrome/unit_tests.isolate',
- 'webkit_unit_tests':
- 'third_party/WebKit/Source/web/WebKitUnitTests.isolate',
-}
+ISOLATE_FILE_PATHS = gtest_test_instance._DEFAULT_ISOLATE_FILE_PATHS
+
# Used for filtering large data deps at a finer grain than what's allowed in
# isolate files since pushing deps to devices is expensive.
diff --git a/build/android/test_runner.py b/build/android/test_runner.py
index 2cff5f2..c69b531 100755
--- a/build/android/test_runner.py
+++ b/build/android/test_runner.py
@@ -27,6 +27,8 @@ from pylib.base import test_run_factory
from pylib.device import device_errors
from pylib.device import device_utils
from pylib.gtest import gtest_config
+# TODO(jbudorick): Remove this once we stop selectively enabling platform mode.
+from pylib.gtest import gtest_test_instance
from pylib.gtest import setup as gtest_setup
from pylib.gtest import test_options as gtest_test_options
from pylib.linker import setup as linker_setup
@@ -919,6 +921,8 @@ def RunTestsCommand(args, parser):
raise Exception('Failed to reset test server port.')
if command == 'gtest':
+ if args.suite_name[0] in gtest_test_instance.BROWSER_TEST_SUITES:
+ return RunTestsInPlatformMode(args, parser)
return _RunGTests(args, devices)
elif command == 'linker':
return _RunLinkerTests(args, devices)
diff --git a/build/apk_test.gypi b/build/apk_test.gypi
index 2a6d312..e0d323f 100644
--- a/build/apk_test.gypi
+++ b/build/apk_test.gypi
@@ -23,6 +23,7 @@
'<(DEPTH)/build/android/pylib/device/commands/commands.gyp:chromium_commands',
'<(DEPTH)/build/android/pylib/remote/device/dummy/dummy.gyp:remote_device_dummy_apk',
'<(DEPTH)/testing/android/appurify_support.gyp:appurify_support_java',
+ '<(DEPTH)/testing/android/on_device_instrumentation.gyp:reporter_java',
'<(DEPTH)/tools/android/android_tools.gyp:android_tools',
],
'conditions': [
diff --git a/build/config/android/internal_rules.gni b/build/config/android/internal_rules.gni
index d3d54f1..f9893e4 100644
--- a/build/config/android/internal_rules.gni
+++ b/build/config/android/internal_rules.gni
@@ -86,6 +86,14 @@ template("findbugs") {
rebased_build_config = rebase_path(build_config, root_build_dir)
+ if (defined(invoker.deps)) {
+ deps = invoker.deps
+ }
+
+ if (defined(invoker.testonly)) {
+ testonly = invoker.testonly
+ }
+
inputs = [
"//build/android/pylib/utils/findbugs.py",
exclusions_file,
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index 0c135b3..d687112 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -1856,6 +1856,7 @@ template("unittest_apk") {
"//base:base_java",
"//build/android/pylib/remote/device/dummy:remote_device_dummy_apk",
"//testing/android/appurify_support:appurify_support_java",
+ "//testing/android/reporter:reporter_java",
]
if (defined(invoker.deps)) {
deps += invoker.deps
diff --git a/components/components_tests.gyp b/components/components_tests.gyp
index 87cfdfe..003bfc9 100644
--- a/components/components_tests.gyp
+++ b/components/components_tests.gyp
@@ -1236,6 +1236,7 @@
'../content/content.gyp:content_java',
'../content/content.gyp:content_v8_external_data',
'../content/content_shell_and_tests.gyp:content_java_test_support',
+ '../content/content_shell_and_tests.gyp:content_shell_browsertests_java',
'../content/content_shell_and_tests.gyp:content_shell_java',
'components_browsertests_paks_copy',
'components_browsertests',
diff --git a/components/test/DEPS b/components/test/DEPS
index ec6ab2f..88da157 100644
--- a/components/test/DEPS
+++ b/components/test/DEPS
@@ -7,6 +7,7 @@ include_rules = [
"+content/public/app/content_main.h",
"+content/public/common/content_switches.h",
"+content/public/test",
+ "+content/shell/android/browsertests",
"+content/shell/android/java/src/org/chromium/content_shell",
"+content/shell/android/shell_jni_registrar.h",
"+content/shell/app/shell_main_delegate.h",
diff --git a/components/test/android/browsertests_apk/AndroidManifest.xml.jinja2 b/components/test/android/browsertests_apk/AndroidManifest.xml.jinja2
index 97dae9e..e2b2f15 100644
--- a/components/test/android/browsertests_apk/AndroidManifest.xml.jinja2
+++ b/components/test/android/browsertests_apk/AndroidManifest.xml.jinja2
@@ -15,7 +15,8 @@
android:launchMode="singleTask"
android:theme="@android:style/Theme.Holo.Light.NoActionBar"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize"
- android:hardwareAccelerated="true">
+ android:hardwareAccelerated="true"
+ android:process=":test_process">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
diff --git a/components/test/android/browsertests_apk/src/org/chromium/components_browsertests_apk/ComponentsBrowserTestsActivity.java b/components/test/android/browsertests_apk/src/org/chromium/components_browsertests_apk/ComponentsBrowserTestsActivity.java
index ae94edc..77264b3 100644
--- a/components/test/android/browsertests_apk/src/org/chromium/components_browsertests_apk/ComponentsBrowserTestsActivity.java
+++ b/components/test/android/browsertests_apk/src/org/chromium/components_browsertests_apk/ComponentsBrowserTestsActivity.java
@@ -5,29 +5,16 @@
package org.chromium.components_browsertests_apk;
import android.os.Bundle;
-import android.view.Window;
-import android.view.WindowManager;
-
-import org.chromium.base.Log;
-import org.chromium.base.annotations.SuppressFBWarnings;
-import org.chromium.base.library_loader.LibraryLoader;
-import org.chromium.base.library_loader.LibraryProcessType;
-import org.chromium.base.library_loader.ProcessInitException;
-import org.chromium.content.browser.BrowserStartupController;
-import org.chromium.content_shell.ShellManager;
-import org.chromium.native_test.NativeBrowserTestActivity;
-import org.chromium.ui.base.ActivityWindowAndroid;
-import org.chromium.ui.base.WindowAndroid;
+
+import org.chromium.base.PathUtils;
+import org.chromium.content_shell.browsertests.ContentShellBrowserTestActivity;
+
+import java.io.File;
/**
* Android activity for running components browser tests
*/
-public class ComponentsBrowserTestsActivity extends NativeBrowserTestActivity {
- private static final String TAG = Log.makeTag("native_test");
-
- private ShellManager mShellManager;
- private WindowAndroid mWindowAndroid;
-
+public class ComponentsBrowserTestsActivity extends ContentShellBrowserTestActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -36,25 +23,18 @@ public class ComponentsBrowserTestsActivity extends NativeBrowserTestActivity {
}
@Override
- @SuppressFBWarnings("DM_EXIT")
- protected void initializeBrowserProcess() {
- try {
- LibraryLoader.get(LibraryProcessType.PROCESS_BROWSER).ensureInitialized();
- } catch (ProcessInitException e) {
- Log.e(TAG, "Cannot load components_browsertests.", e);
- System.exit(-1);
- }
- BrowserStartupController.get(getApplicationContext(), LibraryProcessType.PROCESS_BROWSER)
- .initChromiumBrowserProcessForTests();
-
- setContentView(R.layout.test_activity);
- mShellManager = (ShellManager) findViewById(R.id.shell_container);
- mWindowAndroid = new ActivityWindowAndroid(this);
- mShellManager.setWindow(mWindowAndroid, false);
-
- Window wind = this.getWindow();
- wind.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
- wind.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
- wind.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
+ protected File getPrivateDataDirectory() {
+ return new File(PathUtils.getExternalStorageDirectory(),
+ ComponentsBrowserTestsApplication.PRIVATE_DATA_DIRECTORY_SUFFIX);
+ }
+
+ @Override
+ protected int getTestActivityViewId() {
+ return R.layout.test_activity;
+ }
+
+ @Override
+ protected int getShellManagerViewId() {
+ return R.id.shell_container;
}
}
diff --git a/components/test/android/browsertests_apk/src/org/chromium/components_browsertests_apk/ComponentsBrowserTestsApplication.java b/components/test/android/browsertests_apk/src/org/chromium/components_browsertests_apk/ComponentsBrowserTestsApplication.java
index 91ee597..ffcea44 100644
--- a/components/test/android/browsertests_apk/src/org/chromium/components_browsertests_apk/ComponentsBrowserTestsApplication.java
+++ b/components/test/android/browsertests_apk/src/org/chromium/components_browsertests_apk/ComponentsBrowserTestsApplication.java
@@ -17,7 +17,7 @@ public class ComponentsBrowserTestsApplication extends BaseChromiumApplication {
private static final String[] MANDATORY_PAK_FILES =
new String[] {"components_tests_resources.pak", "content_shell.pak", "icudtl.dat",
"natives_blob.bin", "snapshot_blob.bin"};
- private static final String PRIVATE_DATA_DIRECTORY_SUFFIX = "components_shell";
+ static final String PRIVATE_DATA_DIRECTORY_SUFFIX = "components_shell";
@Override
public void onCreate() {
diff --git a/content/content_tests.gypi b/content/content_tests.gypi
index b97cff0..bdd665a 100644
--- a/content/content_tests.gypi
+++ b/content/content_tests.gypi
@@ -1814,6 +1814,21 @@
'includes': [ '../build/apk_test.gypi' ],
},
{
+ 'target_name': 'content_shell_browsertests_java',
+ 'type': 'none',
+ 'dependencies': [
+ 'content.gyp:content_java',
+ 'content_shell_java',
+ '../base/base.gyp:base_java',
+ '../testing/android/native_test.gyp:native_test_java',
+ '../ui/android/ui_android.gyp:ui_java',
+ ],
+ 'variables': {
+ 'java_in_dir': 'shell/android/browsertests',
+ },
+ 'includes': [ '../build/java.gypi' ],
+ },
+ {
# TODO(GN)
'target_name': 'content_browsertests_manifest',
'type': 'none',
@@ -1832,6 +1847,7 @@
'content.gyp:content_java',
'content.gyp:content_v8_external_data',
'content_browsertests',
+ 'content_shell_browsertests_java',
'content_java_test_support',
'content_shell_java',
],
diff --git a/content/shell/android/browsertests/src/org/chromium/content_shell/browsertests/ContentShellBrowserTestActivity.java b/content/shell/android/browsertests/src/org/chromium/content_shell/browsertests/ContentShellBrowserTestActivity.java
new file mode 100644
index 0000000..663d4a4
--- /dev/null
+++ b/content/shell/android/browsertests/src/org/chromium/content_shell/browsertests/ContentShellBrowserTestActivity.java
@@ -0,0 +1,89 @@
+// 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.content_shell.browsertests;
+
+import android.view.Window;
+import android.view.WindowManager;
+
+import org.chromium.base.Log;
+import org.chromium.base.annotations.SuppressFBWarnings;
+import org.chromium.base.library_loader.LibraryLoader;
+import org.chromium.base.library_loader.LibraryProcessType;
+import org.chromium.base.library_loader.ProcessInitException;
+import org.chromium.content.browser.BrowserStartupController;
+import org.chromium.content_shell.ShellManager;
+import org.chromium.native_test.NativeBrowserTestActivity;
+import org.chromium.ui.base.ActivityWindowAndroid;
+import org.chromium.ui.base.WindowAndroid;
+
+import java.io.File;
+
+/** An Activity base class for running browser tests against ContentShell. */
+public abstract class ContentShellBrowserTestActivity extends NativeBrowserTestActivity {
+
+ private static final String TAG = "cr.native_test";
+
+ private ShellManager mShellManager;
+ private WindowAndroid mWindowAndroid;
+
+ /** Deletes a file or directory along with any of its children.
+ *
+ * Note that, like File.delete(), this returns false if the file or directory couldn't be
+ * fully deleted. This means that, in the directory case, some files may be deleted even if
+ * the entire directory couldn't be.
+ *
+ * @param file The file or directory to delete.
+ * @return Whether or not the file or directory was deleted.
+ */
+ private static boolean deleteRecursive(File file) {
+ if (file == null) return true;
+
+ File[] children = file.listFiles();
+ if (children != null) {
+ for (File child : children) {
+ if (!deleteRecursive(child)) {
+ return false;
+ }
+ }
+ }
+ return file.delete();
+ }
+
+ /** Initializes the browser process.
+ *
+ * This generally includes loading native libraries and switching to the native command line,
+ * among other things.
+ *
+ * @param privateDataDirectory The private data directory to clear before starting the
+ * browser process. Can be null.
+ * @throws ProcessInitException if the native libraries cannot be loaded.
+ */
+ @Override
+ @SuppressFBWarnings("DM_EXIT")
+ protected void initializeBrowserProcess() {
+ try {
+ LibraryLoader.get(LibraryProcessType.PROCESS_BROWSER).ensureInitialized();
+ } catch (ProcessInitException e) {
+ Log.e(TAG, "Cannot load content_browsertests.", e);
+ System.exit(-1);
+ }
+ BrowserStartupController.get(getApplicationContext(), LibraryProcessType.PROCESS_BROWSER)
+ .initChromiumBrowserProcessForTests();
+
+ setContentView(getTestActivityViewId());
+ mShellManager = (ShellManager) findViewById(getShellManagerViewId());
+ mWindowAndroid = new ActivityWindowAndroid(this);
+ mShellManager.setWindow(mWindowAndroid, false);
+
+ Window wind = this.getWindow();
+ wind.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
+ wind.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+ wind.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
+ }
+
+ protected abstract int getTestActivityViewId();
+
+ protected abstract int getShellManagerViewId();
+}
diff --git a/content/shell/android/browsertests_apk/AndroidManifest.xml.jinja2 b/content/shell/android/browsertests_apk/AndroidManifest.xml.jinja2
index 25359c0..d93cced 100644
--- a/content/shell/android/browsertests_apk/AndroidManifest.xml.jinja2
+++ b/content/shell/android/browsertests_apk/AndroidManifest.xml.jinja2
@@ -15,7 +15,8 @@
android:launchMode="singleTask"
android:theme="@android:style/Theme.Holo.Light.NoActionBar"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize"
- android:hardwareAccelerated="true">
+ android:hardwareAccelerated="true"
+ android:process=":test_process">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
diff --git a/content/shell/android/browsertests_apk/src/org/chromium/content_browsertests_apk/ContentBrowserTestsActivity.java b/content/shell/android/browsertests_apk/src/org/chromium/content_browsertests_apk/ContentBrowserTestsActivity.java
index d10b719..aaadab1 100644
--- a/content/shell/android/browsertests_apk/src/org/chromium/content_browsertests_apk/ContentBrowserTestsActivity.java
+++ b/content/shell/android/browsertests_apk/src/org/chromium/content_browsertests_apk/ContentBrowserTestsActivity.java
@@ -5,29 +5,19 @@
package org.chromium.content_browsertests_apk;
import android.os.Bundle;
-import android.view.Window;
-import android.view.WindowManager;
import org.chromium.base.Log;
-import org.chromium.base.annotations.SuppressFBWarnings;
-import org.chromium.base.library_loader.LibraryLoader;
-import org.chromium.base.library_loader.LibraryProcessType;
-import org.chromium.base.library_loader.ProcessInitException;
-import org.chromium.content.browser.BrowserStartupController;
-import org.chromium.content_shell.ShellManager;
-import org.chromium.native_test.NativeBrowserTestActivity;
-import org.chromium.ui.base.ActivityWindowAndroid;
-import org.chromium.ui.base.WindowAndroid;
+import org.chromium.base.PathUtils;
+import org.chromium.content_shell.browsertests.ContentShellBrowserTestActivity;
+
+import java.io.File;
/**
* Android activity for running content browser tests
*/
-public class ContentBrowserTestsActivity extends NativeBrowserTestActivity {
+public class ContentBrowserTestsActivity extends ContentShellBrowserTestActivity {
private static final String TAG = Log.makeTag("native_test");
- private ShellManager mShellManager;
- private WindowAndroid mWindowAndroid;
-
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -36,25 +26,19 @@ public class ContentBrowserTestsActivity extends NativeBrowserTestActivity {
}
@Override
- @SuppressFBWarnings("DM_EXIT")
- protected void initializeBrowserProcess() {
- try {
- LibraryLoader.get(LibraryProcessType.PROCESS_BROWSER).ensureInitialized();
- } catch (ProcessInitException e) {
- Log.e(TAG, "Cannot load content_browsertests.", e);
- System.exit(-1);
- }
- BrowserStartupController.get(getApplicationContext(), LibraryProcessType.PROCESS_BROWSER)
- .initChromiumBrowserProcessForTests();
-
- setContentView(R.layout.test_activity);
- mShellManager = (ShellManager) findViewById(R.id.shell_container);
- mWindowAndroid = new ActivityWindowAndroid(this);
- mShellManager.setWindow(mWindowAndroid, false);
-
- Window wind = this.getWindow();
- wind.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
- wind.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
- wind.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
+ protected File getPrivateDataDirectory() {
+ return new File(PathUtils.getExternalStorageDirectory(),
+ ContentBrowserTestsApplication.PRIVATE_DATA_DIRECTORY_SUFFIX);
+ }
+
+ @Override
+ protected int getTestActivityViewId() {
+ return R.layout.test_activity;
}
+
+ @Override
+ protected int getShellManagerViewId() {
+ return R.id.shell_container;
+ }
+
}
diff --git a/content/shell/android/browsertests_apk/src/org/chromium/content_browsertests_apk/ContentBrowserTestsApplication.java b/content/shell/android/browsertests_apk/src/org/chromium/content_browsertests_apk/ContentBrowserTestsApplication.java
index 5b683e0..25e313b 100644
--- a/content/shell/android/browsertests_apk/src/org/chromium/content_browsertests_apk/ContentBrowserTestsApplication.java
+++ b/content/shell/android/browsertests_apk/src/org/chromium/content_browsertests_apk/ContentBrowserTestsApplication.java
@@ -21,7 +21,7 @@ public class ContentBrowserTestsApplication extends BaseChromiumApplication {
"natives_blob.bin",
"snapshot_blob.bin"
};
- private static final String PRIVATE_DATA_DIRECTORY_SUFFIX = "content_shell";
+ static final String PRIVATE_DATA_DIRECTORY_SUFFIX = "content_shell";
@Override
public void onCreate() {
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
index 167e7b9..e006954 100644
--- 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
@@ -20,6 +20,7 @@ public class RobotiumBundleGenerator implements ResultsBundleGenerator {
public Bundle generate(Map<String, ResultsBundleGenerator.TestResult> rawResults) {
int testsPassed = 0;
int testsFailed = 0;
+ int testsErrored = 0;
for (Map.Entry<String, ResultsBundleGenerator.TestResult> entry : rawResults.entrySet()) {
switch (entry.getValue()) {
@@ -32,6 +33,9 @@ public class RobotiumBundleGenerator implements ResultsBundleGenerator {
Log.d(TAG, "FAILED: " + entry.getKey());
++testsFailed;
break;
+ case UNKNOWN:
+ ++testsErrored;
+ break;
default:
Log.w(TAG, "Unhandled: " + entry.getKey() + ", "
+ entry.getValue().toString());
@@ -40,10 +44,11 @@ public class RobotiumBundleGenerator implements ResultsBundleGenerator {
}
StringBuilder resultBuilder = new StringBuilder();
- if (testsFailed > 0) {
- resultBuilder.append(
- "\nFAILURES!!! Tests run: " + Integer.toString(rawResults.size())
- + ", Failures: " + Integer.toString(testsFailed) + ", Errors: 0");
+ if (testsFailed > 0 || testsErrored > 0) {
+ resultBuilder.append("\nFAILURES!!! ")
+ .append("Tests run: ").append(Integer.toString(rawResults.size()))
+ .append(", Failures: ").append(Integer.toString(testsFailed))
+ .append(", Errors: ").append(Integer.toString(testsErrored));
} else {
resultBuilder.append("\nOK (" + Integer.toString(testsPassed) + " tests)");
}
diff --git a/testing/android/native_test.gyp b/testing/android/native_test.gyp
index fcfd7d7..734a00f 100644
--- a/testing/android/native_test.gyp
+++ b/testing/android/native_test.gyp
@@ -55,6 +55,7 @@
'type': 'none',
'dependencies': [
'appurify_support.gyp:appurify_support_java',
+ 'on_device_instrumentation.gyp:reporter_java',
'../../base/base.gyp:base_native_libraries_gen',
'../../base/base.gyp:base_java',
],
diff --git a/testing/android/native_test/java/src/org/chromium/native_test/NativeBrowserTestActivity.java b/testing/android/native_test/java/src/org/chromium/native_test/NativeBrowserTestActivity.java
index ac2ddee..0f6628a 100644
--- a/testing/android/native_test/java/src/org/chromium/native_test/NativeBrowserTestActivity.java
+++ b/testing/android/native_test/java/src/org/chromium/native_test/NativeBrowserTestActivity.java
@@ -6,11 +6,17 @@ package org.chromium.native_test;
import android.os.Bundle;
+import org.chromium.base.Log;
+
+import java.io.File;
+
/**
* An {@link android.app.Activity} for running native browser tests.
*/
public abstract class NativeBrowserTestActivity extends NativeTestActivity {
+ private static final String TAG = "cr.native_test";
+
private static final String BROWSER_TESTS_FLAGS[] = {
// content::kSingleProcessTestsFlag
"--single_process",
@@ -32,15 +38,48 @@ public abstract class NativeBrowserTestActivity extends NativeTestActivity {
@Override
public void onStart() {
+ deletePrivateDataDirectory();
initializeBrowserProcess();
super.onStart();
}
+ /** Deletes a file or directory along with any of its children.
+ *
+ * Note that, like File.delete(), this returns false if the file or directory couldn't be
+ * fully deleted. This means that, in the directory case, some files may be deleted even if
+ * the entire directory couldn't be.
+ *
+ * @param file The file or directory to delete.
+ * @return Whether or not the file or directory was deleted.
+ */
+ private static boolean deleteRecursive(File file) {
+ if (file == null) return true;
+
+ File[] children = file.listFiles();
+ if (children != null) {
+ for (File child : children) {
+ if (!deleteRecursive(child)) {
+ return false;
+ }
+ }
+ }
+ return file.delete();
+ }
+
+ private void deletePrivateDataDirectory() {
+ File privateDataDirectory = getPrivateDataDirectory();
+ if (!deleteRecursive(privateDataDirectory)) {
+ Log.e(TAG, "Failed to remove %s", privateDataDirectory.getAbsolutePath());
+ }
+ }
+
+ /** Returns the test suite's private data directory. */
+ protected abstract File getPrivateDataDirectory();
+
/** Initializes the browser process.
*
* This generally includes loading native libraries and switching to the native command line,
* among other things.
*/
protected abstract void initializeBrowserProcess();
-
}
diff --git a/testing/android/native_test/java/src/org/chromium/native_test/NativeTestActivity.java b/testing/android/native_test/java/src/org/chromium/native_test/NativeTestActivity.java
index 46a490b..8e147201 100644
--- a/testing/android/native_test/java/src/org/chromium/native_test/NativeTestActivity.java
+++ b/testing/android/native_test/java/src/org/chromium/native_test/NativeTestActivity.java
@@ -10,12 +10,16 @@ import android.content.Intent;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
+import android.os.Process;
import org.chromium.base.CommandLine;
import org.chromium.base.JNINamespace;
import org.chromium.base.Log;
+import org.chromium.test.reporter.TestStatusReporter;
import java.io.File;
+import java.util.ArrayList;
+import java.util.Iterator;
/**
* Android's NativeActivity is mostly useful for pure-native code.
@@ -28,16 +32,17 @@ public class NativeTestActivity extends Activity {
"org.chromium.native_test.NativeTestActivity.CommandLineFile";
public static final String EXTRA_COMMAND_LINE_FLAGS =
"org.chromium.native_test.NativeTestActivity.CommandLineFlags";
+ public static final String EXTRA_SHARD =
+ "org.chromium.native_test.NativeTestActivity.Shard";
public static final String EXTRA_STDOUT_FILE =
"org.chromium.native_test.NativeTestActivity.StdoutFile";
- private static final String TAG = Log.makeTag("native_test");
+ private static final String TAG = "cr.native_test";
private static final String EXTRA_RUN_IN_SUB_THREAD = "RunInSubThread";
- // We post a delayed task to run tests so that we do not block onCreate().
- private static final long RUN_TESTS_DELAY_IN_MS = 300;
private String mCommandLineFilePath;
private StringBuilder mCommandLineFlags = new StringBuilder();
+ private TestStatusReporter mReporter;
private boolean mRunInSubThread = false;
private boolean mStdoutFifo = false;
private String mStdoutFilePath;
@@ -48,6 +53,7 @@ public class NativeTestActivity extends Activity {
CommandLine.init(new String[]{});
parseArgumentsFromIntent(getIntent());
+ mReporter = new TestStatusReporter(this);
}
private void parseArgumentsFromIntent(Intent intent) {
@@ -68,6 +74,19 @@ public class NativeTestActivity extends Activity {
mRunInSubThread = intent.hasExtra(EXTRA_RUN_IN_SUB_THREAD);
+ ArrayList<String> shard = intent.getStringArrayListExtra(EXTRA_SHARD);
+ if (shard != null) {
+ StringBuilder filterFlag = new StringBuilder();
+ filterFlag.append("--gtest_filter=");
+ for (Iterator<String> test_iter = shard.iterator(); test_iter.hasNext();) {
+ filterFlag.append(test_iter.next());
+ if (test_iter.hasNext()) {
+ filterFlag.append(":");
+ }
+ }
+ appendCommandLineFlags(filterFlag.toString());
+ }
+
mStdoutFilePath = intent.getStringExtra(EXTRA_STDOUT_FILE);
if (mStdoutFilePath == null) {
mStdoutFilePath = new File(getFilesDir(), "test.fifo").getAbsolutePath();
@@ -94,19 +113,21 @@ public class NativeTestActivity extends Activity {
} else {
// Post a task to run the tests. This allows us to not block
// onCreate and still run tests on the main thread.
- new Handler().postDelayed(new Runnable() {
+ new Handler().post(new Runnable() {
@Override
public void run() {
runTests();
}
- }, RUN_TESTS_DELAY_IN_MS);
+ });
}
}
private void runTests() {
+ mReporter.testRunStarted(Process.myPid());
nativeRunTests(mCommandLineFlags.toString(), mCommandLineFilePath, mStdoutFilePath,
mStdoutFifo, getApplicationContext());
finish();
+ mReporter.testRunFinished(Process.myPid());
}
// Signal a failure of the native test loader to python scripts
diff --git a/testing/android/native_test/java/src/org/chromium/native_test/NativeTestInstrumentationTestRunner.java b/testing/android/native_test/java/src/org/chromium/native_test/NativeTestInstrumentationTestRunner.java
index 4db6286..6e39401 100644
--- a/testing/android/native_test/java/src/org/chromium/native_test/NativeTestInstrumentationTestRunner.java
+++ b/testing/android/native_test/java/src/org/chromium/native_test/NativeTestInstrumentationTestRunner.java
@@ -5,13 +5,19 @@
package org.chromium.native_test;
import android.app.Activity;
+import android.app.ActivityManager;
import android.app.Instrumentation;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Environment;
+import android.os.Handler;
+import android.os.Process;
+import android.util.SparseArray;
import org.chromium.base.Log;
+import org.chromium.test.reporter.TestStatusReceiver;
import org.chromium.test.support.ResultsBundleGenerator;
import org.chromium.test.support.RobotiumBundleGenerator;
@@ -20,10 +26,15 @@ import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
+import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -33,22 +44,38 @@ import java.util.regex.Pattern;
public class NativeTestInstrumentationTestRunner extends Instrumentation {
public static final String EXTRA_NATIVE_TEST_ACTIVITY =
- "org.chromium.native_test.NativeTestInstrumentationTestRunner."
- + "NativeTestActivity";
+ "org.chromium.native_test.NativeTestInstrumentationTestRunner.NativeTestActivity";
+ public static final String EXTRA_SHARD_NANO_TIMEOUT =
+ "org.chromium.native_test.NativeTestInstrumentationTestRunner.ShardNanoTimeout";
+ public static final String EXTRA_SHARD_SIZE_LIMIT =
+ "org.chromium.native_test.NativeTestInstrumentationTestRunner.ShardSizeLimit";
+ public static final String EXTRA_TEST_LIST_FILE =
+ "org.chromium.native_test.NativeTestInstrumentationTestRunner.TestList";
- private static final String TAG = Log.makeTag("native_test");
+ private static final String TAG = "cr.native_test";
- private static final int ACCEPT_TIMEOUT_MS = 5000;
+ private static final long DEFAULT_SHARD_NANO_TIMEOUT = 60 * 1000000000L;
+ // Default to no size limit.
+ private static final int DEFAULT_SHARD_SIZE_LIMIT = 0;
private static final String DEFAULT_NATIVE_TEST_ACTIVITY =
"org.chromium.native_test.NativeUnitTestActivity";
- private static final Pattern RE_TEST_OUTPUT = Pattern.compile("\\[ *([^ ]*) *\\] ?([^ ]+) .*");
+ private static final Pattern RE_TEST_OUTPUT =
+ Pattern.compile("\\[ *([^ ]*) *\\] ?([^ ]+)( .*)?$");
private ResultsBundleGenerator mBundleGenerator = new RobotiumBundleGenerator();
private String mCommandLineFile;
private String mCommandLineFlags;
+ private Handler mHandler = new Handler();
private String mNativeTestActivity;
private Bundle mLogBundle = new Bundle();
+ private TestStatusReceiver mReceiver;
+ private Map<String, ResultsBundleGenerator.TestResult> mResults =
+ new HashMap<String, ResultsBundleGenerator.TestResult>();
+ private Queue<ArrayList<String>> mShards = new ArrayDeque<ArrayList<String>>();
+ private long mShardNanoTimeout = DEFAULT_SHARD_NANO_TIMEOUT;
+ private int mShardSizeLimit = DEFAULT_SHARD_SIZE_LIMIT;
private File mStdoutFile;
+ private SparseArray<ShardMonitor> mMonitors = new SparseArray<ShardMonitor>();
@Override
public void onCreate(Bundle arguments) {
@@ -57,6 +84,39 @@ public class NativeTestInstrumentationTestRunner extends Instrumentation {
mNativeTestActivity = arguments.getString(EXTRA_NATIVE_TEST_ACTIVITY);
if (mNativeTestActivity == null) mNativeTestActivity = DEFAULT_NATIVE_TEST_ACTIVITY;
+ String shardNanoTimeout = arguments.getString(EXTRA_SHARD_NANO_TIMEOUT);
+ if (shardNanoTimeout != null) mShardNanoTimeout = Long.parseLong(shardNanoTimeout);
+
+ String shardSizeLimit = arguments.getString(EXTRA_SHARD_SIZE_LIMIT);
+ if (shardSizeLimit != null) mShardSizeLimit = Integer.parseInt(shardSizeLimit);
+
+ String testListFilePath = arguments.getString(EXTRA_TEST_LIST_FILE);
+ if (testListFilePath != null) {
+ File testListFile = new File(testListFilePath);
+ try {
+ BufferedReader testListFileReader =
+ new BufferedReader(new FileReader(testListFile));
+
+ String test;
+ ArrayList<String> workingShard = new ArrayList<String>();
+ while ((test = testListFileReader.readLine()) != null) {
+ workingShard.add(test);
+ if (workingShard.size() == mShardSizeLimit) {
+ mShards.add(workingShard);
+ workingShard = new ArrayList<String>();
+ }
+ }
+
+ if (!workingShard.isEmpty()) {
+ mShards.add(workingShard);
+ }
+
+ testListFileReader.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Error reading %s", testListFile.getAbsolutePath(), e);
+ }
+ }
+
try {
mStdoutFile = File.createTempFile(
".temp_stdout_", ".txt", Environment.getExternalStorageDirectory());
@@ -66,62 +126,153 @@ public class NativeTestInstrumentationTestRunner extends Instrumentation {
finish(Activity.RESULT_CANCELED, new Bundle());
return;
}
+
start();
}
@Override
public void onStart() {
super.onStart();
- Bundle results = runTests();
- finish(Activity.RESULT_OK, results);
+
+ mReceiver = new TestStatusReceiver();
+ mReceiver.register(getContext());
+ mReceiver.registerCallback(new TestStatusReceiver.TestRunCallback() {
+ @Override
+ public void testRunStarted(int pid) {
+ if (pid != Process.myPid()) {
+ ShardMonitor m = new ShardMonitor(
+ pid, System.nanoTime() + mShardNanoTimeout);
+ mMonitors.put(pid, m);
+ mHandler.post(m);
+ }
+ }
+
+ @Override
+ public void testRunFinished(int pid) {
+ ShardMonitor m = mMonitors.get(pid);
+ if (m != null) {
+ m.stopped();
+ mMonitors.remove(pid);
+ }
+ mHandler.post(new ShardEnder(pid));
+ }
+ });
+
+ mHandler.post(new ShardStarter());
}
- /** Runs the tests in the NativeTestActivity and returns a Bundle containing the results.
- */
- private Bundle runTests() {
- Log.i(TAG, "Creating activity.");
- Activity activityUnderTest = startNativeTestActivity();
+ /** Monitors a test shard's execution. */
+ private class ShardMonitor implements Runnable {
+ private static final int MONITOR_FREQUENCY_MS = 1000;
- Log.i(TAG, "Waiting for tests to finish.");
- try {
- while (!activityUnderTest.isFinishing()) {
- Thread.sleep(100);
+ private long mExpirationNanoTime;
+ private int mPid;
+ private AtomicBoolean mStopped;
+
+ public ShardMonitor(int pid, long expirationNanoTime) {
+ mPid = pid;
+ mExpirationNanoTime = expirationNanoTime;
+ mStopped = new AtomicBoolean(false);
+ }
+
+ public void stopped() {
+ mStopped.set(true);
+ }
+
+ @Override
+ public void run() {
+ if (mStopped.get()) {
+ return;
+ }
+
+ if (isAppProcessAlive(getContext(), mPid)) {
+ if (System.nanoTime() > mExpirationNanoTime) {
+ Log.e(TAG, "Test process %d timed out.", mPid);
+ mHandler.post(new ShardEnder(mPid));
+ return;
+ } else {
+ mHandler.postDelayed(this, MONITOR_FREQUENCY_MS);
+ return;
+ }
}
- } catch (InterruptedException e) {
- Log.e(TAG, "Interrupted while waiting for activity to be destroyed: ", e);
+
+ Log.e(TAG, "Test process %d died unexpectedly.", mPid);
+ mHandler.post(new ShardEnder(mPid));
}
- Log.i(TAG, "Getting results.");
- Map<String, ResultsBundleGenerator.TestResult> results = parseResults(activityUnderTest);
+ }
- Log.i(TAG, "Parsing results and generating output.");
- return mBundleGenerator.generate(results);
+ private static boolean isAppProcessAlive(Context context, int pid) {
+ ActivityManager activityManager =
+ (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+ for (ActivityManager.RunningAppProcessInfo processInfo :
+ activityManager.getRunningAppProcesses()) {
+ if (processInfo.pid == pid) return true;
+ }
+ return false;
}
/** Starts the NativeTestActivty.
*/
- private Activity startNativeTestActivity() {
- Intent i = new Intent(Intent.ACTION_MAIN);
- i.setComponent(new ComponentName(getContext().getPackageName(), mNativeTestActivity));
- i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- if (mCommandLineFile != null) {
- Log.i(TAG, "Passing command line file extra: %s", mCommandLineFile);
- i.putExtra(NativeTestActivity.EXTRA_COMMAND_LINE_FILE, mCommandLineFile);
+ private class ShardStarter implements Runnable {
+ @Override
+ public void run() {
+ Intent i = new Intent(Intent.ACTION_MAIN);
+ i.setComponent(new ComponentName(getContext().getPackageName(), mNativeTestActivity));
+ i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ if (mCommandLineFile != null) {
+ Log.i(TAG, "Passing command line file extra: %s", mCommandLineFile);
+ i.putExtra(NativeTestActivity.EXTRA_COMMAND_LINE_FILE, mCommandLineFile);
+ }
+ if (mCommandLineFlags != null) {
+ Log.i(TAG, "Passing command line flag extra: %s", mCommandLineFlags);
+ i.putExtra(NativeTestActivity.EXTRA_COMMAND_LINE_FLAGS, mCommandLineFlags);
+ }
+ if (mShards != null && !mShards.isEmpty()) {
+ ArrayList<String> shard = mShards.remove();
+ i.putStringArrayListExtra(NativeTestActivity.EXTRA_SHARD, shard);
+ }
+ i.putExtra(NativeTestActivity.EXTRA_STDOUT_FILE, mStdoutFile.getAbsolutePath());
+ getContext().startActivity(i);
}
- if (mCommandLineFlags != null) {
- Log.i(TAG, "Passing command line flag extra: %s", mCommandLineFlags);
- i.putExtra(NativeTestActivity.EXTRA_COMMAND_LINE_FLAGS, mCommandLineFlags);
+ }
+
+ private class ShardEnder implements Runnable {
+ private static final int WAIT_FOR_DEATH_MILLIS = 10;
+
+ private int mPid;
+
+ public ShardEnder(int pid) {
+ mPid = pid;
+ }
+
+ @Override
+ public void run() {
+ if (mPid != Process.myPid()) {
+ Process.killProcess(mPid);
+ try {
+ while (isAppProcessAlive(getContext(), mPid)) {
+ Thread.sleep(WAIT_FOR_DEATH_MILLIS);
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG, "%d may still be alive.", mPid, e);
+ }
+ }
+ mResults.putAll(parseResults());
+
+ if (mShards != null && !mShards.isEmpty()) {
+ mHandler.post(new ShardStarter());
+ } else {
+ finish(Activity.RESULT_OK, mBundleGenerator.generate(mResults));
+ }
}
- i.putExtra(NativeTestActivity.EXTRA_STDOUT_FILE, mStdoutFile.getAbsolutePath());
- return startActivitySync(i);
}
/**
* Generates a map between test names and test results from the instrumented Activity's
* output.
*/
- private Map<String, ResultsBundleGenerator.TestResult> parseResults(
- Activity activityUnderTest) {
+ private Map<String, ResultsBundleGenerator.TestResult> parseResults() {
Map<String, ResultsBundleGenerator.TestResult> results =
new HashMap<String, ResultsBundleGenerator.TestResult>();
@@ -153,7 +304,7 @@ public class NativeTestInstrumentationTestRunner extends Instrumentation {
Log.i(TAG, l);
}
} catch (FileNotFoundException e) {
- Log.e(TAG, "Couldn't find stdout file file: ", e);
+ Log.e(TAG, "Couldn't find stdout file: ", e);
} catch (IOException e) {
Log.e(TAG, "Error handling stdout file: ", e);
} finally {
diff --git a/testing/android/native_test/java/src/org/chromium/native_test/NativeUnitTestActivity.java b/testing/android/native_test/java/src/org/chromium/native_test/NativeUnitTestActivity.java
index ae66727..2a2a8dd 100644
--- a/testing/android/native_test/java/src/org/chromium/native_test/NativeUnitTestActivity.java
+++ b/testing/android/native_test/java/src/org/chromium/native_test/NativeUnitTestActivity.java
@@ -18,7 +18,7 @@ import org.chromium.base.library_loader.NativeLibraries;
*/
public class NativeUnitTestActivity extends NativeTestActivity {
- private static final String TAG = Log.makeTag("native_test");
+ private static final String TAG = "cr.native_test";
@Override
public void onCreate(Bundle savedInstanceState) {
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
index e4af9b6..d8f0a55 100644
--- a/testing/android/reporter/java/src/org/chromium/test/reporter/TestStatusReceiver.java
+++ b/testing/android/reporter/java/src/org/chromium/test/reporter/TestStatusReceiver.java
@@ -8,7 +8,8 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.util.Log;
+
+import org.chromium.base.Log;
import java.util.ArrayList;
import java.util.List;
@@ -18,12 +19,13 @@ import java.util.List;
*/
public class TestStatusReceiver extends BroadcastReceiver {
- private static final String TAG = "ResultReceiver";
+ private static final String TAG = Log.makeTag("test.reporter");
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>();
+ private final List<TestRunCallback> mTestRunCallbacks = new ArrayList<TestRunCallback>();
/** An IntentFilter that matches the intents that this class can receive. */
private static final IntentFilter INTENT_FILTER;
@@ -33,6 +35,8 @@ public class TestStatusReceiver extends BroadcastReceiver {
filter.addAction(TestStatusReporter.ACTION_TEST_FAILED);
filter.addAction(TestStatusReporter.ACTION_TEST_PASSED);
filter.addAction(TestStatusReporter.ACTION_TEST_STARTED);
+ filter.addAction(TestStatusReporter.ACTION_TEST_RUN_STARTED);
+ filter.addAction(TestStatusReporter.ACTION_TEST_RUN_FINISHED);
try {
filter.addDataType(TestStatusReporter.DATA_TYPE_HEARTBEAT);
filter.addDataType(TestStatusReporter.DATA_TYPE_RESULT);
@@ -62,6 +66,12 @@ public class TestStatusReceiver extends BroadcastReceiver {
void testStarted(String testClass, String testMethod);
}
+ /** A callback used when a test run has started or finished. */
+ public interface TestRunCallback {
+ void testRunStarted(int pid);
+ void testRunFinished(int pid);
+ }
+
/** Register a callback for when a test has failed. */
public void registerCallback(FailCallback c) {
mFailCallbacks.add(c);
@@ -82,6 +92,11 @@ public class TestStatusReceiver extends BroadcastReceiver {
mStartCallbacks.add(c);
}
+ /** Register a callback for when a test run has started or finished. */
+ public void registerCallback(TestRunCallback c) {
+ mTestRunCallbacks.add(c);
+ }
+
/** Register this receiver using the provided context. */
public void register(Context c) {
c.registerReceiver(this, INTENT_FILTER);
@@ -94,6 +109,7 @@ public class TestStatusReceiver extends BroadcastReceiver {
*/
@Override
public void onReceive(Context context, Intent intent) {
+ int pid = intent.getIntExtra(TestStatusReporter.EXTRA_PID, 0);
String testClass = intent.getStringExtra(TestStatusReporter.EXTRA_TEST_CLASS);
String testMethod = intent.getStringExtra(TestStatusReporter.EXTRA_TEST_METHOD);
@@ -118,6 +134,16 @@ public class TestStatusReceiver extends BroadcastReceiver {
c.heartbeat();
}
break;
+ case TestStatusReporter.ACTION_TEST_RUN_STARTED:
+ for (TestRunCallback c: mTestRunCallbacks) {
+ c.testRunStarted(pid);
+ }
+ break;
+ case TestStatusReporter.ACTION_TEST_RUN_FINISHED:
+ for (TestRunCallback c: mTestRunCallbacks) {
+ c.testRunFinished(pid);
+ }
+ 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
index 6ac7312..0c5d3e6 100644
--- a/testing/android/reporter/java/src/org/chromium/test/reporter/TestStatusReporter.java
+++ b/testing/android/reporter/java/src/org/chromium/test/reporter/TestStatusReporter.java
@@ -24,8 +24,14 @@ public class TestStatusReporter {
"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 ACTION_TEST_RUN_STARTED =
+ "org.chromium.test.reporter.TestStatusReporter.TEST_RUN_STARTED";
+ public static final String ACTION_TEST_RUN_FINISHED =
+ "org.chromium.test.reporter.TestStatusReporter.TEST_RUN_FINISHED";
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_PID =
+ "org.chromium.test.reporter.TestStatusReporter.PID";
public static final String EXTRA_TEST_CLASS =
"org.chromium.test.reporter.TestStatusReporter.TEST_CLASS";
public static final String EXTRA_TEST_METHOD =
@@ -57,22 +63,18 @@ public class TestStatusReporter {
}
public void testStarted(String testClass, String testMethod) {
- sendBroadcast(testClass, testMethod, ACTION_TEST_STARTED);
+ sendTestBroadcast(testClass, testMethod, ACTION_TEST_STARTED);
}
public void testPassed(String testClass, String testMethod) {
- sendBroadcast(testClass, testMethod, ACTION_TEST_PASSED);
+ sendTestBroadcast(testClass, testMethod, ACTION_TEST_PASSED);
}
public void testFailed(String testClass, String testMethod) {
- sendBroadcast(testClass, testMethod, ACTION_TEST_FAILED);
+ sendTestBroadcast(testClass, testMethod, ACTION_TEST_FAILED);
}
- public void stopHeartbeat() {
- mKeepBeating.set(false);
- }
-
- private void sendBroadcast(String testClass, String testMethod, String action) {
+ private void sendTestBroadcast(String action, String testClass, String testMethod) {
Intent i = new Intent(action);
i.setType(DATA_TYPE_RESULT);
i.putExtra(EXTRA_TEST_CLASS, testClass);
@@ -80,4 +82,23 @@ public class TestStatusReporter {
mContext.sendBroadcast(i);
}
+ public void testRunStarted(int pid) {
+ sendTestRunBroadcast(ACTION_TEST_RUN_STARTED, pid);
+ }
+
+ public void testRunFinished(int pid) {
+ sendTestRunBroadcast(ACTION_TEST_RUN_FINISHED, pid);
+ }
+
+ private void sendTestRunBroadcast(String action, int pid) {
+ Intent i = new Intent(action);
+ i.setType(DATA_TYPE_RESULT);
+ i.putExtra(EXTRA_PID, pid);
+ mContext.sendBroadcast(i);
+ }
+
+ public void stopHeartbeat() {
+ mKeepBeating.set(false);
+ }
+
}