diff options
author | jbudorick <jbudorick@chromium.org> | 2015-06-12 06:06:12 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-06-12 13:06:54 +0000 |
commit | b049103fa4848e304102a24e78c6ba994130a535 (patch) | |
tree | 3dc71fa65201527d337aedc11dba4c2fe51c1e07 | |
parent | 23490d44633f4eaeb76e03384e08e9fd3ef2909a (diff) | |
download | chromium_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}
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); + } + } |