blob: 14c4366b05148d66d09d94926bb1ba98abd395a5 [file] [log] [blame]
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import fnmatch
import imp
import logging
import posixpath
import signal
import thread
import threading
from devil.utils import signal_handler
from pylib import valgrind_tools
from pylib.base import base_test_result
from pylib.base import test_run
from pylib.base import test_collection
from pylib.local.device import local_device_environment
_SIGTERM_TEST_LOG = (
' Suite execution terminated, probably due to swarming timeout.\n'
' Your test may not have run.')
def IncrementalInstall(device, apk_helper, installer_script):
"""Performs an incremental install.
Args:
device: Device to install on.
apk_helper: ApkHelper instance for the _incremental.apk.
installer_script: Path to the installer script for the incremental apk.
"""
try:
install_wrapper = imp.load_source('install_wrapper', installer_script)
except IOError:
raise Exception('Incremental install script not found: %s\n' %
installer_script)
params = install_wrapper.GetInstallParameters()
from incremental_install import installer
installer.Install(device, apk_helper, split_globs=params['splits'],
native_libs=params['native_libs'],
dex_files=params['dex_files'],
permissions=None) # Auto-grant permissions from manifest.
def SubstituteDeviceRoot(device_path, device_root):
if not device_path:
return device_root
elif isinstance(device_path, list):
return posixpath.join(*(p if p else device_root for p in device_path))
else:
return device_path
class TestsTerminated(Exception):
pass
class InvalidShardingSettings(Exception):
def __init__(self, shard_index, total_shards):
super(InvalidShardingSettings, self).__init__(
'Invalid sharding settings. shard_index: %d total_shards: %d'
% (shard_index, total_shards))
class LocalDeviceTestRun(test_run.TestRun):
def __init__(self, env, test_instance):
super(LocalDeviceTestRun, self).__init__(env, test_instance)
self._tools = {}
#override
def RunTests(self):
tests = self._GetTests()
exit_now = threading.Event()
@local_device_environment.handle_shard_failures
def run_tests_on_device(dev, tests, results):
for test in tests:
if exit_now.isSet():
thread.exit()
result = None
rerun = None
try:
result, rerun = self._RunTest(dev, test)
if isinstance(result, base_test_result.BaseTestResult):
results.AddResult(result)
elif isinstance(result, list):
results.AddResults(result)
else:
raise Exception(
'Unexpected result type: %s' % type(result).__name__)
except:
if isinstance(tests, test_collection.TestCollection):
rerun = test
raise
finally:
if isinstance(tests, test_collection.TestCollection):
if rerun:
tests.add(rerun)
tests.test_completed()
logging.info('Finished running tests on this device.')
def stop_tests(_signum, _frame):
logging.critical('Received SIGTERM. Stopping test execution.')
exit_now.set()
raise TestsTerminated()
try:
with signal_handler.SignalHandler(signal.SIGTERM, stop_tests):
tries = 0
results = []
while tries < self._env.max_tries and tests:
logging.info('STARTING TRY #%d/%d', tries + 1, self._env.max_tries)
logging.info('Will run %d tests on %d devices: %s',
len(tests), len(self._env.devices),
', '.join(str(d) for d in self._env.devices))
for t in tests:
logging.debug(' %s', t)
try_results = base_test_result.TestRunResults()
test_names = (self._GetUniqueTestName(t) for t in tests)
try_results.AddResults(
base_test_result.BaseTestResult(
t, base_test_result.ResultType.NOTRUN)
for t in test_names if not t.endswith('*'))
try:
if self._ShouldShard():
tc = test_collection.TestCollection(self._CreateShards(tests))
self._env.parallel_devices.pMap(
run_tests_on_device, tc, try_results).pGet(None)
else:
self._env.parallel_devices.pMap(
run_tests_on_device, tests, try_results).pGet(None)
except TestsTerminated:
for unknown_result in try_results.GetUnknown():
try_results.AddResult(
base_test_result.BaseTestResult(
unknown_result.GetName(),
base_test_result.ResultType.TIMEOUT,
log=_SIGTERM_TEST_LOG))
raise
finally:
results.append(try_results)
tries += 1
tests = self._GetTestsToRetry(tests, try_results)
logging.info('FINISHED TRY #%d/%d', tries, self._env.max_tries)
if tests:
logging.info('%d failed tests remain.', len(tests))
else:
logging.info('All tests completed.')
except TestsTerminated:
pass
return results
def _GetTestsToRetry(self, tests, try_results):
def is_failure_result(test_result):
return (
test_result is None
or test_result.GetType() not in (
base_test_result.ResultType.PASS,
base_test_result.ResultType.SKIP))
all_test_results = {r.GetName(): r for r in try_results.GetAll()}
def test_failed(name):
# When specifying a test filter, names can contain trailing wildcards.
# See local_device_gtest_run._ExtractTestsFromFilter()
if name.endswith('*'):
return any(fnmatch.fnmatch(n, name) and is_failure_result(t)
for n, t in all_test_results.iteritems())
return is_failure_result(all_test_results.get(name))
failed_tests = (t for t in tests if test_failed(self._GetUniqueTestName(t)))
return [t for t in failed_tests if self._ShouldRetry(t)]
def _ApplyExternalSharding(self, tests, shard_index, total_shards):
logging.info('Using external sharding settings. This is shard %d/%d',
shard_index, total_shards)
if total_shards < 0 or shard_index < 0 or total_shards <= shard_index:
raise InvalidShardingSettings(shard_index, total_shards)
return [
t for t in tests
if hash(self._GetUniqueTestName(t)) % total_shards == shard_index]
def GetTool(self, device):
if not str(device) in self._tools:
self._tools[str(device)] = valgrind_tools.CreateTool(
self._env.tool, device)
return self._tools[str(device)]
def _CreateShards(self, tests):
raise NotImplementedError
def _GetUniqueTestName(self, test):
# pylint: disable=no-self-use
return test
def _ShouldRetry(self, test):
# pylint: disable=no-self-use,unused-argument
return True
def _GetTests(self):
raise NotImplementedError
def _RunTest(self, device, test):
raise NotImplementedError
def _ShouldShard(self):
raise NotImplementedError
class NoTestsError(Exception):
"""Error for when no tests are found."""