0

[Android] Switch gtests to platform mode. (RELAND)

BUG=428729

Review URL: https://codereview.chromium.org/1358593002

Cr-Commit-Position: refs/heads/master@{#349931}
This commit is contained in:
jbudorick
2015-09-21 08:32:47 -07:00
committed by Commit bot
parent 61dd5d4b91
commit 566592abb5
12 changed files with 114 additions and 66 deletions

@ -3,9 +3,9 @@
# found in the LICENSE file. # found in the LICENSE file.
from pylib.gtest import gtest_test_instance from pylib.gtest import gtest_test_instance
from pylib.gtest import local_device_gtest_run
from pylib.instrumentation import instrumentation_test_instance from pylib.instrumentation import instrumentation_test_instance
from pylib.local.device import local_device_environment from pylib.local.device import local_device_environment
from pylib.local.device import local_device_gtest_run
from pylib.local.device import local_device_instrumentation_test_run from pylib.local.device import local_device_instrumentation_test_run
from pylib.remote.device import remote_device_environment from pylib.remote.device import remote_device_environment
from pylib.remote.device import remote_device_gtest_run from pylib.remote.device import remote_device_gtest_run

@ -23,6 +23,8 @@ BROWSER_TEST_SUITES = [
'content_browsertests', 'content_browsertests',
] ]
RUN_IN_SUB_THREAD_TEST_SUITES = ['net_unittests']
_DEFAULT_ISOLATE_FILE_PATHS = { _DEFAULT_ISOLATE_FILE_PATHS = {
'base_unittests': 'base/base_unittests.isolate', 'base_unittests': 'base/base_unittests.isolate',
@ -73,6 +75,8 @@ _DEPS_EXCLUSION_LIST = [
_EXTRA_NATIVE_TEST_ACTIVITY = ( _EXTRA_NATIVE_TEST_ACTIVITY = (
'org.chromium.native_test.NativeTestInstrumentationTestRunner.' 'org.chromium.native_test.NativeTestInstrumentationTestRunner.'
'NativeTestActivity') 'NativeTestActivity')
_EXTRA_RUN_IN_SUB_THREAD = (
'org.chromium.native_test.NativeTestActivity.RunInSubThread')
_EXTRA_SHARD_SIZE_LIMIT = ( _EXTRA_SHARD_SIZE_LIMIT = (
'org.chromium.native_test.NativeTestInstrumentationTestRunner.' 'org.chromium.native_test.NativeTestInstrumentationTestRunner.'
'ShardSizeLimit') 'ShardSizeLimit')
@ -148,6 +152,8 @@ class GtestTestInstance(test_instance.TestInstance):
self._extras = { self._extras = {
_EXTRA_NATIVE_TEST_ACTIVITY: self._activity, _EXTRA_NATIVE_TEST_ACTIVITY: self._activity,
} }
if self._suite in RUN_IN_SUB_THREAD_TEST_SUITES:
self._extras[_EXTRA_RUN_IN_SUB_THREAD] = 1
if self._suite in BROWSER_TEST_SUITES: if self._suite in BROWSER_TEST_SUITES:
self._extras[_EXTRA_SHARD_SIZE_LIMIT] = 1 self._extras[_EXTRA_SHARD_SIZE_LIMIT] = 1

@ -16,8 +16,8 @@ from devil.android.sdk import intent
from pylib import constants from pylib import constants
from pylib import pexpect from pylib import pexpect
from pylib.gtest import gtest_test_instance from pylib.gtest import gtest_test_instance
from pylib.gtest import local_device_gtest_run
from pylib.gtest.test_package import TestPackage from pylib.gtest.test_package import TestPackage
from pylib.local.device import local_device_gtest_run
class TestPackageApk(TestPackage): class TestPackageApk(TestPackage):
@ -40,7 +40,9 @@ class TestPackageApk(TestPackage):
self._package_info = constants.PACKAGE_INFO['gtest'] self._package_info = constants.PACKAGE_INFO['gtest']
if suite_name == 'net_unittests': if suite_name == 'net_unittests':
self._extras = {'RunInSubThread': None} self._extras = {
'org.chromium.native_test.NativeTestActivity.RunInSubThread': None
}
else: else:
self._extras = [] self._extras = []

@ -2,6 +2,9 @@
# Use of this source code is governed by a BSD-style license that can be # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
import logging
import threading
from devil.android import device_blacklist from devil.android import device_blacklist
from devil.android import device_errors from devil.android import device_errors
from devil.android import device_utils from devil.android import device_utils
@ -17,6 +20,7 @@ class LocalDeviceEnvironment(environment.Environment):
if args.blacklist_file if args.blacklist_file
else None) else None)
self._device_serial = args.test_device self._device_serial = args.test_device
self._devices_lock = threading.Lock()
self._devices = [] self._devices = []
self._max_tries = 1 + args.num_retries self._max_tries = 1 + args.num_retries
self._tool_name = args.tool self._tool_name = args.tool
@ -38,11 +42,13 @@ class LocalDeviceEnvironment(environment.Environment):
@property @property
def devices(self): def devices(self):
if not self._devices:
raise device_errors.NoDevicesError()
return self._devices return self._devices
@property @property
def parallel_devices(self): def parallel_devices(self):
return parallelizer.SyncParallelizer(self._devices) return parallelizer.SyncParallelizer(self.devices)
@property @property
def max_tries(self): def max_tries(self):
@ -56,3 +62,15 @@ class LocalDeviceEnvironment(environment.Environment):
def TearDown(self): def TearDown(self):
pass pass
def BlacklistDevice(self, device):
if not self._blacklist:
logging.warning(
'Attempted to blacklist %s, but no blacklist was provided.',
str(device))
return
device_serial = device.adb.GetDeviceSerial()
self._blacklist.Extend([device_serial])
with self._devices_lock:
self._devices = [d for d in self._devices if str(d) != device_serial]

@ -3,7 +3,6 @@
# found in the LICENSE file. # found in the LICENSE file.
import itertools import itertools
import logging
import os import os
import posixpath import posixpath
@ -108,10 +107,13 @@ class _ExeDelegate(object):
device.PushChangedFiles(host_device_tuples) device.PushChangedFiles(host_device_tuples)
def Run(self, test, device, flags=None, **kwargs): def Run(self, test, device, flags=None, **kwargs):
cmd = [ tool = self._test_run.GetTool(device).GetTestWrapper()
self._test_run.GetTool(device).GetTestWrapper(), if tool:
self._exe_device_path, cmd = [tool]
] else:
cmd = []
cmd.append(self._exe_device_path)
if test: if test:
cmd.append('--gtest_filter=%s' % ':'.join(test)) cmd.append('--gtest_filter=%s' % ':'.join(test))
if flags: if flags:
@ -130,14 +132,8 @@ class _ExeDelegate(object):
except (device_errors.CommandFailedError, KeyError): except (device_errors.CommandFailedError, KeyError):
pass pass
# TODO(jbudorick): Switch to just RunShellCommand once perezju@'s CL output = device.RunShellCommand(
# for long shell commands lands. cmd, cwd=cwd, env=env, check_return=True, large_output=True, **kwargs)
with device_temp_file.DeviceTempFile(device.adb) as script_file:
script_contents = ' '.join(cmd)
logging.info('script contents: %r', script_contents)
device.WriteFile(script_file.name, script_contents)
output = device.RunShellCommand(['sh', script_file.name], cwd=cwd,
env=env, **kwargs)
return output return output
def PullAppFiles(self, device, files, directory): def PullAppFiles(self, device, files, directory):
@ -168,6 +164,7 @@ class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun):
#override #override
def SetUp(self): def SetUp(self):
@local_device_test_run.handle_shard_failures
def individual_device_set_up(dev, host_device_tuples): def individual_device_set_up(dev, host_device_tuples):
# Install test APK. # Install test APK.
self._delegate.Install(dev) self._delegate.Install(dev)
@ -207,10 +204,16 @@ class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun):
#override #override
def _GetTests(self): def _GetTests(self):
tests = self._delegate.Run( @local_device_test_run.handle_shard_failures
None, self._env.devices[0], flags='--gtest_list_tests') def list_tests(dev):
tests = gtest_test_instance.ParseGTestListTests(tests) tests = self._delegate.Run(
tests = self._test_instance.FilterTests(tests) None, dev, flags='--gtest_list_tests', timeout=10)
tests = gtest_test_instance.ParseGTestListTests(tests)
tests = self._test_instance.FilterTests(tests)
return tests
test_lists = self._env.parallel_devices.pMap(list_tests).pGet(None)
tests = list(sorted(set().union(*[set(tl) for tl in test_lists if tl])))
return tests return tests
#override #override
@ -233,6 +236,7 @@ class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun):
#override #override
def TearDown(self): def TearDown(self):
@local_device_test_run.handle_shard_failures
def individual_device_tear_down(dev): def individual_device_tear_down(dev):
for s in self._servers[str(dev)]: for s in self._servers[str(dev)]:
s.TearDown() s.TearDown()

@ -11,6 +11,27 @@ from pylib.base import test_run
from pylib.base import test_collection from pylib.base import test_collection
def handle_shard_failures(f):
"""A decorator that handles device failures for per-device functions.
Args:
f: the function being decorated. The function must take at least one
argument, and that argument must be the device.
"""
def wrapper(dev, *args, **kwargs):
try:
return f(dev, *args, **kwargs)
except device_errors.CommandFailedError:
logging.exception('Shard failed: %s(%s)', f.__name__, str(dev))
except device_errors.CommandTimeoutError:
logging.exception('Shard timed out: %s(%s)', f.__name__, str(dev))
except device_errors.DeviceUnreachableError:
logging.exception('Shard died: %s(%s)', f.__name__, str(dev))
return None
return wrapper
class LocalDeviceTestRun(test_run.TestRun): class LocalDeviceTestRun(test_run.TestRun):
def __init__(self, env, test_instance): def __init__(self, env, test_instance):
@ -21,6 +42,7 @@ class LocalDeviceTestRun(test_run.TestRun):
def RunTests(self): def RunTests(self):
tests = self._GetTests() tests = self._GetTests()
@handle_shard_failures
def run_tests_on_device(dev, tests, results): def run_tests_on_device(dev, tests, results):
for test in tests: for test in tests:
try: try:
@ -52,21 +74,14 @@ class LocalDeviceTestRun(test_run.TestRun):
for t in tests: for t in tests:
logging.debug(' %s', t) logging.debug(' %s', t)
try: try_results = base_test_result.TestRunResults()
try_results = base_test_result.TestRunResults() if self._ShouldShard():
if self._ShouldShard(): tc = test_collection.TestCollection(self._CreateShards(tests))
tc = test_collection.TestCollection(self._CreateShards(tests)) self._env.parallel_devices.pMap(
self._env.parallel_devices.pMap( run_tests_on_device, tc, try_results).pGet(None)
run_tests_on_device, tc, try_results).pGet(None) else:
else: self._env.parallel_devices.pMap(
self._env.parallel_devices.pMap( run_tests_on_device, tests, try_results).pGet(None)
run_tests_on_device, tests, try_results).pGet(None)
except device_errors.CommandFailedError:
logging.exception('Shard terminated: command failed')
except device_errors.CommandTimeoutError:
logging.exception('Shard terminated: command timed out')
except device_errors.DeviceUnreachableError:
logging.exception('Shard terminated: device became unreachable')
for result in try_results.GetAll(): for result in try_results.GetAll():
if result.GetType() in (base_test_result.ResultType.PASS, if result.GetType() in (base_test_result.ResultType.PASS,

@ -32,8 +32,6 @@ from pylib.base import test_dispatcher
from pylib.base import test_instance_factory from pylib.base import test_instance_factory
from pylib.base import test_run_factory from pylib.base import test_run_factory
from pylib.gtest import gtest_config 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 setup as gtest_setup
from pylib.gtest import test_options as gtest_test_options from pylib.gtest import test_options as gtest_test_options
from pylib.linker import setup as linker_setup from pylib.linker import setup as linker_setup
@ -944,9 +942,7 @@ def RunTestsCommand(args, parser): # pylint: disable=too-many-return-statements
raise Exception('Failed to reset test server port.') raise Exception('Failed to reset test server port.')
if command == 'gtest': if command == 'gtest':
if args.suite_name[0] in gtest_test_instance.BROWSER_TEST_SUITES: return RunTestsInPlatformMode(args, parser)
return RunTestsInPlatformMode(args, parser)
return _RunGTests(args, devices)
elif command == 'linker': elif command == 'linker':
return _RunLinkerTests(args, devices) return _RunLinkerTests(args, devices)
elif command == 'instrumentation': elif command == 'instrumentation':
@ -975,13 +971,16 @@ _SUPPORTED_IN_PLATFORM_MODE = [
def RunTestsInPlatformMode(args, parser): def RunTestsInPlatformMode(args, parser):
if args.command not in _SUPPORTED_IN_PLATFORM_MODE: def infra_error(message):
parser.error('%s is not yet supported in platform mode' % args.command) parser.exit(status=constants.INFRA_EXIT_CODE, message=message)
with environment_factory.CreateEnvironment(args, parser.error) as env: if args.command not in _SUPPORTED_IN_PLATFORM_MODE:
with test_instance_factory.CreateTestInstance(args, parser.error) as test: infra_error('%s is not yet supported in platform mode' % args.command)
with environment_factory.CreateEnvironment(args, infra_error) as env:
with test_instance_factory.CreateTestInstance(args, infra_error) as test:
with test_run_factory.CreateTestRun( with test_run_factory.CreateTestRun(
args, env, test, parser.error) as test_run: args, env, test, infra_error) as test_run:
results = test_run.RunTests() results = test_run.RunTests()
if args.environment == 'remote_device' and args.trigger: if args.environment == 'remote_device' and args.trigger:

@ -24,7 +24,8 @@ found in the LICENSE file.
android:name="org.chromium.base.BaseChromiumApplication"> android:name="org.chromium.base.BaseChromiumApplication">
<activity android:name=".NativeUnitTestActivity" <activity android:name=".NativeUnitTestActivity"
android:label="NativeTest" android:label="NativeTest"
android:configChanges="orientation|keyboardHidden"> android:configChanges="orientation|keyboardHidden"
android:process=":test_process">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />

@ -15,7 +15,7 @@ import java.io.File;
*/ */
public abstract class NativeBrowserTestActivity extends NativeTestActivity { public abstract class NativeBrowserTestActivity extends NativeTestActivity {
private static final String TAG = "cr.native_test"; private static final String TAG = "cr_NativeTest";
private static final String BROWSER_TESTS_FLAGS[] = { private static final String BROWSER_TESTS_FLAGS[] = {
// content::kSingleProcessTestsFlag // content::kSingleProcessTestsFlag

@ -32,13 +32,14 @@ public class NativeTestActivity extends Activity {
"org.chromium.native_test.NativeTestActivity.CommandLineFile"; "org.chromium.native_test.NativeTestActivity.CommandLineFile";
public static final String EXTRA_COMMAND_LINE_FLAGS = public static final String EXTRA_COMMAND_LINE_FLAGS =
"org.chromium.native_test.NativeTestActivity.CommandLineFlags"; "org.chromium.native_test.NativeTestActivity.CommandLineFlags";
public static final String EXTRA_RUN_IN_SUB_THREAD =
"org.chromium.native_test.NativeTestActivity.RunInSubThread";
public static final String EXTRA_SHARD = public static final String EXTRA_SHARD =
"org.chromium.native_test.NativeTestActivity.Shard"; "org.chromium.native_test.NativeTestActivity.Shard";
public static final String EXTRA_STDOUT_FILE = public static final String EXTRA_STDOUT_FILE =
"org.chromium.native_test.NativeTestActivity.StdoutFile"; "org.chromium.native_test.NativeTestActivity.StdoutFile";
private static final String TAG = "cr.native_test"; private static final String TAG = "cr_NativeTest";
private static final String EXTRA_RUN_IN_SUB_THREAD = "RunInSubThread";
private String mCommandLineFilePath; private String mCommandLineFilePath;
private StringBuilder mCommandLineFlags = new StringBuilder(); private StringBuilder mCommandLineFlags = new StringBuilder();
@ -57,6 +58,12 @@ public class NativeTestActivity extends Activity {
} }
private void parseArgumentsFromIntent(Intent intent) { private void parseArgumentsFromIntent(Intent intent) {
Log.i(TAG, "Extras:");
Bundle extras = intent.getExtras();
for (String s : extras.keySet()) {
Log.i(TAG, " %s", s);
}
mCommandLineFilePath = intent.getStringExtra(EXTRA_COMMAND_LINE_FILE); mCommandLineFilePath = intent.getStringExtra(EXTRA_COMMAND_LINE_FILE);
if (mCommandLineFilePath == null) { if (mCommandLineFilePath == null) {
mCommandLineFilePath = ""; mCommandLineFilePath = "";

@ -52,7 +52,7 @@ public class NativeTestInstrumentationTestRunner extends Instrumentation {
public static final String EXTRA_TEST_LIST_FILE = public static final String EXTRA_TEST_LIST_FILE =
"org.chromium.native_test.NativeTestInstrumentationTestRunner.TestList"; "org.chromium.native_test.NativeTestInstrumentationTestRunner.TestList";
private static final String TAG = "cr.native_test"; private static final String TAG = "cr_NativeTest";
private static final long DEFAULT_SHARD_NANO_TIMEOUT = 60 * 1000000000L; private static final long DEFAULT_SHARD_NANO_TIMEOUT = 60 * 1000000000L;
// Default to no size limit. // Default to no size limit.
@ -63,11 +63,10 @@ public class NativeTestInstrumentationTestRunner extends Instrumentation {
Pattern.compile("\\[ *([^ ]*) *\\] ?([^ ]+)( .*)?$"); Pattern.compile("\\[ *([^ ]*) *\\] ?([^ ]+)( .*)?$");
private ResultsBundleGenerator mBundleGenerator = new RobotiumBundleGenerator(); private ResultsBundleGenerator mBundleGenerator = new RobotiumBundleGenerator();
private String mCommandLineFile;
private String mCommandLineFlags;
private Handler mHandler = new Handler(); private Handler mHandler = new Handler();
private String mNativeTestActivity;
private Bundle mLogBundle = new Bundle(); private Bundle mLogBundle = new Bundle();
private SparseArray<ShardMonitor> mMonitors = new SparseArray<ShardMonitor>();
private String mNativeTestActivity;
private TestStatusReceiver mReceiver; private TestStatusReceiver mReceiver;
private Map<String, ResultsBundleGenerator.TestResult> mResults = private Map<String, ResultsBundleGenerator.TestResult> mResults =
new HashMap<String, ResultsBundleGenerator.TestResult>(); new HashMap<String, ResultsBundleGenerator.TestResult>();
@ -75,20 +74,23 @@ public class NativeTestInstrumentationTestRunner extends Instrumentation {
private long mShardNanoTimeout = DEFAULT_SHARD_NANO_TIMEOUT; private long mShardNanoTimeout = DEFAULT_SHARD_NANO_TIMEOUT;
private int mShardSizeLimit = DEFAULT_SHARD_SIZE_LIMIT; private int mShardSizeLimit = DEFAULT_SHARD_SIZE_LIMIT;
private File mStdoutFile; private File mStdoutFile;
private SparseArray<ShardMonitor> mMonitors = new SparseArray<ShardMonitor>(); private Bundle mTransparentArguments;
@Override @Override
public void onCreate(Bundle arguments) { public void onCreate(Bundle arguments) {
mCommandLineFile = arguments.getString(NativeTestActivity.EXTRA_COMMAND_LINE_FILE); mTransparentArguments = new Bundle(arguments);
mCommandLineFlags = arguments.getString(NativeTestActivity.EXTRA_COMMAND_LINE_FLAGS);
mNativeTestActivity = arguments.getString(EXTRA_NATIVE_TEST_ACTIVITY); mNativeTestActivity = arguments.getString(EXTRA_NATIVE_TEST_ACTIVITY);
if (mNativeTestActivity == null) mNativeTestActivity = DEFAULT_NATIVE_TEST_ACTIVITY; if (mNativeTestActivity == null) mNativeTestActivity = DEFAULT_NATIVE_TEST_ACTIVITY;
mTransparentArguments.remove(EXTRA_NATIVE_TEST_ACTIVITY);
String shardNanoTimeout = arguments.getString(EXTRA_SHARD_NANO_TIMEOUT); String shardNanoTimeout = arguments.getString(EXTRA_SHARD_NANO_TIMEOUT);
if (shardNanoTimeout != null) mShardNanoTimeout = Long.parseLong(shardNanoTimeout); if (shardNanoTimeout != null) mShardNanoTimeout = Long.parseLong(shardNanoTimeout);
mTransparentArguments.remove(EXTRA_SHARD_NANO_TIMEOUT);
String shardSizeLimit = arguments.getString(EXTRA_SHARD_SIZE_LIMIT); String shardSizeLimit = arguments.getString(EXTRA_SHARD_SIZE_LIMIT);
if (shardSizeLimit != null) mShardSizeLimit = Integer.parseInt(shardSizeLimit); if (shardSizeLimit != null) mShardSizeLimit = Integer.parseInt(shardSizeLimit);
mTransparentArguments.remove(EXTRA_SHARD_SIZE_LIMIT);
String testListFilePath = arguments.getString(EXTRA_TEST_LIST_FILE); String testListFilePath = arguments.getString(EXTRA_TEST_LIST_FILE);
if (testListFilePath != null) { if (testListFilePath != null) {
@ -116,6 +118,7 @@ public class NativeTestInstrumentationTestRunner extends Instrumentation {
Log.e(TAG, "Error reading %s", testListFile.getAbsolutePath(), e); Log.e(TAG, "Error reading %s", testListFile.getAbsolutePath(), e);
} }
} }
mTransparentArguments.remove(EXTRA_TEST_LIST_FILE);
try { try {
mStdoutFile = File.createTempFile( mStdoutFile = File.createTempFile(
@ -220,14 +223,7 @@ public class NativeTestInstrumentationTestRunner extends Instrumentation {
Intent i = new Intent(Intent.ACTION_MAIN); Intent i = new Intent(Intent.ACTION_MAIN);
i.setComponent(new ComponentName(getContext().getPackageName(), mNativeTestActivity)); i.setComponent(new ComponentName(getContext().getPackageName(), mNativeTestActivity));
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (mCommandLineFile != null) { i.putExtras(mTransparentArguments);
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()) { if (mShards != null && !mShards.isEmpty()) {
ArrayList<String> shard = mShards.remove(); ArrayList<String> shard = mShards.remove();
i.putStringArrayListExtra(NativeTestActivity.EXTRA_SHARD, shard); i.putStringArrayListExtra(NativeTestActivity.EXTRA_SHARD, shard);

@ -17,7 +17,7 @@ import org.chromium.base.library_loader.NativeLibraries;
*/ */
public class NativeUnitTestActivity extends NativeTestActivity { public class NativeUnitTestActivity extends NativeTestActivity {
private static final String TAG = "cr.native_test"; private static final String TAG = "cr_NativeTest";
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {