Merge "Snap for 5798008 from 9b49d23b8165404d5d97554b05c0d96a66d6566f to sdk-release" into sdk-release
diff --git a/.classpath b/.classpath
index 95c2966..6c19af3 100644
--- a/.classpath
+++ b/.classpath
@@ -13,7 +13,6 @@
<classpathentry kind="src" path="global_configuration"/>
<classpathentry excluding="Android.bp" kind="src" path="device_build_interfaces"/>
<classpathentry combineaccessrules="false" exported="true" kind="src" path="/tf-remote-client"/>
- <classpathentry combineaccessrules="false" kind="src" path="/LongevityHostRunner"/>
<classpathentry combineaccessrules="false" kind="src" path="/ddmlib"/>
<classpathentry combineaccessrules="false" kind="src" path="/loganalysis"/>
<classpathentry combineaccessrules="false" kind="src" path="/platform-annotations"/>
@@ -41,5 +40,6 @@
<classpathentry kind="var" path="TRADEFED_ROOT/out/soong/.intermediates/tools/tradefederation/core/tradefed-protos/linux_glibc_common/combined/tradefed-protos.jar"/>
<classpathentry kind="var" path="TRADEFED_ROOT/prebuilts/tools/common/m2/repository/com/google/code/gson/gson/2.8.0/gson-2.8.0.jar"/>
<classpathentry kind="var" path="TRADEFED_ROOT/out/soong/.intermediates/prebuilts/tools/common/m2/protobuf-java-util-prebuilt-jar/linux_glibc_common/combined/protobuf-java-util-prebuilt-jar.jar"/>
+ <classpathentry kind="var" path="TRADEFED_ROOT/out/soong/.intermediates/platform_testing/libraries/health/runners/longevity/host/longevity-base-lib/linux_glibc_common/javac/longevity-base-lib.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/Android.bp b/Android.bp
index 4b6d838..1d55ff1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -114,10 +114,7 @@
"junit-params",
"kxml2-2.3.0",
"libprotobuf-java-full",
- "longevity-host-lib",
- "perfetto_config-full",
"platform-test-annotations",
- "test-composers",
"tf-remote-client",
"tradefed-protos",
],
diff --git a/README.md b/README.md
index dbc3667..201d82b 100644
--- a/README.md
+++ b/README.md
@@ -17,3 +17,6 @@
See more details about Tradefed Architecture at:
https://source.android.com/devices/tech/test_infra/tradefed/architecture
+
+If you are a tests writer you should start looking in the test_framework/
+component which contains everything needed to write a tests in Tradefed.
diff --git a/atest/atest.py b/atest/atest.py
index 6baa28e..78fa306 100755
--- a/atest/atest.py
+++ b/atest/atest.py
@@ -32,10 +32,11 @@
import time
import platform
+from multiprocessing import Process
+
import atest_arg_parser
import atest_error
import atest_execution_info
-import atest_metrics
import atest_utils
import bug_detector
import cli_translator
@@ -49,6 +50,7 @@
from metrics import metrics_base
from metrics import metrics_utils
from test_runners import regression_test_runner
+from tools import atest_tools
EXPECTED_VARS = frozenset([
constants.ANDROID_BUILD_TOP,
@@ -66,6 +68,12 @@
TEST_COUNT = 'test_count'
TEST_TYPE = 'test_type'
+# Tasks that must run in the build time but unable to build by soong.
+# (e.g subprocesses that invoke host commands.)
+EXTRA_TASKS = {
+ 'index-targets': atest_tools.index_targets
+}
+
def _parse_args(argv):
"""Parse command line arguments.
@@ -157,6 +165,8 @@
extra_args[constants.POST_PATCH_ITERATIONS] = args.generate_new_metrics
if args.instant:
extra_args[constants.INSTANT] = args.instant
+ if args.secondary_user:
+ extra_args[constants.SECONDARY_USER] = args.secondary_user
if args.host:
extra_args[constants.HOST] = args.host
if args.dry_run:
@@ -528,7 +538,6 @@
args = _parse_args(argv)
_configure_logging(args.verbose)
_validate_args(args)
- atest_metrics.log_start_event()
metrics_utils.get_start_time()
metrics.AtestStartEvent(
command_line=' '.join(argv),
@@ -581,11 +590,20 @@
# args.steps will be None if none of -bit set, else list of params set.
steps = args.steps if args.steps else constants.ALL_STEPS
if build_targets and constants.BUILD_STEP in steps:
+ if constants.TEST_STEP in steps:
+ # Run extra tasks with building deps concurrently.
+ # When only "-b" is given(without -t), will not index targets.
+ for task in EXTRA_TASKS.values():
+ proc = Process(target=task)
+ # Daemonlise proc so it terminates with the main process.
+ proc.daemon = True
+ proc.start()
# Add module-info.json target to the list of build targets to keep the
# file up to date.
build_targets.add(mod_info.module_info_target)
build_start = time.time()
- success = atest_utils.build(build_targets, args.verbose)
+ success = atest_utils.build(build_targets, verbose=args.verbose,
+ env_vars=constants.ATEST_BUILD_ENV)
metrics.BuildFinishEvent(
duration=metrics_utils.convert_duration(time.time() - build_start),
success=success,
diff --git a/atest/atest_arg_parser.py b/atest/atest_arg_parser.py
index 2c9711f..7571160 100644
--- a/atest/atest_arg_parser.py
+++ b/atest/atest_arg_parser.py
@@ -83,6 +83,8 @@
'if the module supports it. Note: running a test '
'that does not support instant with --instant '
'will result in nothing running.')
+ self.add_argument('--secondary-user', action='store_true',
+ help='Run test with secondary user.')
# Options related to Test Mapping
self.add_argument('-p', '--test-mapping', action='store_true',
help='Run tests in TEST_MAPPING files.')
diff --git a/atest/atest_utils.py b/atest/atest_utils.py
index 983c935..2dc1b6b 100644
--- a/atest/atest_utils.py
+++ b/atest/atest_utils.py
@@ -59,6 +59,7 @@
'sample_test_cmd_result.json')
TEST_INFO_CACHE_ROOT = os.path.join(os.path.expanduser('~'), '.atest',
'info_cache')
+_DEFAULT_TERMINAL_WIDTH = 80
def _capture_fail_section(full_log):
"""Return the error message from the build output.
@@ -97,9 +98,11 @@
stderr=subprocess.STDOUT, env=env_vars)
sys.stdout.write('\n')
# Determine the width of the terminal. We'll need to clear this many
- # characters when carriage returning.
- _, term_width = os.popen('stty size', 'r').read().split()
- term_width = int(term_width)
+ # characters when carriage returning. Set default value as 80.
+ term_width = _DEFAULT_TERMINAL_WIDTH
+ stty_size = os.popen('stty size').read()
+ if stty_size:
+ term_width = int(stty_size.split()[1])
white_space = " " * int(term_width)
full_output = []
while proc.poll() is None:
diff --git a/atest/atest_utils_unittest.py b/atest/atest_utils_unittest.py
index b41f113..f2c125c 100755
--- a/atest/atest_utils_unittest.py
+++ b/atest/atest_utils_unittest.py
@@ -42,10 +42,12 @@
TEST_SUITE_A = 'FakeSuiteA'
TEST_MODULE_CLASS_A = 'FAKE_MODULE_CLASS_A'
TEST_INSTALL_LOC_A = set(['host', 'device'])
+TEST_FINDER_A = 'MODULE'
TEST_INFO_A = test_info.TestInfo(TEST_MODULE_NAME_A, TEST_RUNNER_A,
TEST_BUILD_TARGET_A, TEST_DATA_A,
TEST_SUITE_A, TEST_MODULE_CLASS_A,
TEST_INSTALL_LOC_A)
+TEST_INFO_A.test_finder = TEST_FINDER_A
#pylint: disable=protected-access
class AtestUtilsUnittests(unittest.TestCase):
@@ -362,10 +364,10 @@
"""Test method update_test_info_cache and load_test_info_cache."""
test_reference = 'myTestRefA'
test_cache_dir = tempfile.mkdtemp()
- atest_utils.update_test_info_cache(test_reference, TEST_INFO_A,
+ atest_utils.update_test_info_cache(test_reference, [TEST_INFO_A],
test_cache_dir)
- unittest_utils.assert_equal_testinfos(
- self, TEST_INFO_A,
+ unittest_utils.assert_equal_testinfo_sets(
+ self, set([TEST_INFO_A]),
atest_utils.load_test_info_cache(test_reference, test_cache_dir))
if __name__ == "__main__":
diff --git a/atest/cli_translator.py b/atest/cli_translator.py
index b56e79a..97d7616 100644
--- a/atest/cli_translator.py
+++ b/atest/cli_translator.py
@@ -39,6 +39,7 @@
TEST_MAPPING = 'TEST_MAPPING'
FUZZY_FINDER = 'FUZZY'
+CACHE_FINDER = 'CACHE'
# Pattern used to identify comments start with '//' or '#' in TEST_MAPPING.
_COMMENTS_RE = re.compile(r'(?m)[\s\t]*(#|//).*|(\".*?\")')
@@ -98,18 +99,22 @@
except atest_error.TestDiscoveryException as e:
find_test_err_msg = e
if found_test_infos:
+ finder_info = finder.finder_info
for test_info in found_test_infos:
if tm_test_detail:
test_info.data[constants.TI_MODULE_ARG] = (
tm_test_detail.options)
test_info.from_test_mapping = True
test_info.host = tm_test_detail.host
+ if finder_info != CACHE_FINDER:
+ test_info.test_finder = finder_info
test_infos.add(test_info)
test_found = True
- finder_info = finder.finder_info
print("Found '%s' as %s" % (
atest_utils.colorize(test, constants.GREEN),
finder_info))
+ if finder_info == CACHE_FINDER and test_infos:
+ test_finders.append(list(test_infos)[0].test_finder)
test_finders.append(finder_info)
test_info_str = ','.join([str(x) for x in found_test_infos])
break
diff --git a/atest/constants_default.py b/atest/constants_default.py
index 676f993..f1b21f0 100644
--- a/atest/constants_default.py
+++ b/atest/constants_default.py
@@ -16,6 +16,7 @@
Various globals used by atest.
"""
+import os
import re
MODE = 'DEFAULT'
@@ -45,6 +46,7 @@
DRY_RUN = 'DRY_RUN'
ANDROID_SERIAL = 'ANDROID_SERIAL'
INSTANT = 'INSTANT'
+SECONDARY_USER = 'SECONDARY_USER'
# Application exit codes.
EXIT_CODE_SUCCESS = 0
@@ -66,8 +68,6 @@
MODULE_CLASS_NATIVE_TESTS = 'NATIVE_TESTS'
MODULE_CLASS_JAVA_LIBRARIES = 'JAVA_LIBRARIES'
MODULE_TEST_CONFIG = 'test_config'
-CC_EXT_RE = re.compile(r'.*\.(cc|cpp)$', re.I)
-JAVA_EXT_RE = re.compile(r'.*\.(java|kt)$', re.I)
# Env constants
ANDROID_BUILD_TOP = 'ANDROID_BUILD_TOP'
@@ -168,3 +168,34 @@
# ATest TF
ATEST_TF_MODULE = 'atest-tradefed'
+
+# Build environment variable for each build on ATest
+# With SOONG_COLLECT_JAVA_DEPS enabled, out/soong/module_bp_java_deps.json will
+# be generated when make.
+ATEST_BUILD_ENV = {'SOONG_COLLECT_JAVA_DEPS':'true'}
+
+# Atest cache root and relative dirs/caches.
+INDEX_DIR = os.path.join(os.getenv(ANDROID_HOST_OUT, ''), 'indexes')
+LOCATE_CACHE = os.path.join(INDEX_DIR, 'mlocate.db')
+INT_INDEX = os.path.join(INDEX_DIR, 'integration.idx')
+CLASS_INDEX = os.path.join(INDEX_DIR, 'classes.idx')
+CC_CLASS_INDEX = os.path.join(INDEX_DIR, 'cc_classes.idx')
+PACKAGE_INDEX = os.path.join(INDEX_DIR, 'packages.idx')
+QCLASS_INDEX = os.path.join(INDEX_DIR, 'fqcn.idx')
+MODULE_INDEX = os.path.join(INDEX_DIR, 'modules.idx')
+
+# Regeular Expressions
+CC_EXT_RE = re.compile(r'.*\.(cc|cpp)$')
+JAVA_EXT_RE = re.compile(r'.*\.(java|kt)$')
+# e.g. /path/to/ccfile.cc: TEST_F(test_name, method_name){
+CC_OUTPUT_RE = re.compile(r'(?P<file_path>/.*):\s*TEST(_F|_P)?[ ]*\('
+ r'(?P<test_name>\w+)\s*,\s*(?P<method_name>\w+)\)'
+ r'\s*\{')
+CC_GREP_RE = r'^[ ]*TEST(_P|_F)?[ ]*\([[:alnum:]].*,'
+# e.g. /path/to/Javafile.java:package com.android.settings.accessibility
+# grab the path, Javafile(class) and com.android.settings.accessibility(package)
+CLASS_OUTPUT_RE = re.compile(r'(?P<java_path>.*/(?P<class>[A-Z]\w+)\.\w+)[:].*')
+QCLASS_OUTPUT_RE = re.compile(r'(?P<java_path>.*/(?P<class>[A-Z]\w+)\.\w+)'
+ r'[:]\s*package\s+(?P<package>[^(;|\s)]+)\s*')
+PACKAGE_OUTPUT_RE = re.compile(r'(?P<java_path>.*)[:]\s*package\s+'
+ r'(?P<package>[^(;|\s)]+)\s*')
diff --git a/atest/module_info.py b/atest/module_info.py
index 1cd911f..5e0104a 100644
--- a/atest/module_info.py
+++ b/atest/module_info.py
@@ -80,7 +80,8 @@
logging.debug('Generating %s - this is required for '
'initial runs.', _MODULE_INFO)
atest_utils.build([module_info_target],
- logging.getLogger().isEnabledFor(logging.DEBUG))
+ verbose=logging.getLogger().isEnabledFor(logging.DEBUG),
+ env_vars=constants.ATEST_BUILD_ENV)
return module_info_target, module_file_path
def _load_module_info_file(self, force_build, module_file):
diff --git a/atest/test_finder_handler.py b/atest/test_finder_handler.py
index 236f177..360c66e 100644
--- a/atest/test_finder_handler.py
+++ b/atest/test_finder_handler.py
@@ -36,18 +36,21 @@
# Explanation of REFERENCE_TYPEs:
# ----------------------------------
# 0. MODULE: LOCAL_MODULE or LOCAL_PACKAGE_NAME value in Android.mk/Android.bp.
-# 1. MODULE_CLASS: Combo of MODULE and CLASS as "module:class".
-# 2. PACKAGE: package in java file. Same as file path to java file.
-# 3. MODULE_PACKAGE: Combo of MODULE and PACKAGE as "module:package".
-# 4. MODULE_FILE_PATH: File path to dir of tests or test itself.
-# 5. INTEGRATION_FILE_PATH: File path to config xml in one of the 4 integration
+# 1. CLASS: Names which the same with a ClassName.java/kt file.
+# 2. QUALIFIED_CLASS: String like "a.b.c.ClassName".
+# 3. MODULE_CLASS: Combo of MODULE and CLASS as "module:class".
+# 4. PACKAGE: Package in java file. Same as file path to java file.
+# 5. MODULE_PACKAGE: Combo of MODULE and PACKAGE as "module:package".
+# 6. MODULE_FILE_PATH: File path to dir of tests or test itself.
+# 7. INTEGRATION_FILE_PATH: File path to config xml in one of the 4 integration
# config directories.
-# 6. INTEGRATION: xml file name in one of the 4 integration config directories.
-# 7. SUITE: Value of the "run-suite-tag" in xml config file in 4 config dirs.
+# 8. INTEGRATION: xml file name in one of the 4 integration config directories.
+# 9. SUITE: Value of the "run-suite-tag" in xml config file in 4 config dirs.
# Same as value of "test-suite-tag" in AndroidTest.xml files.
-# 8. CC_CLASS: Test case in cc file.
-# 9. SUITE_PLAN: Suite name such as cts.
-# 10. SUITE_PLAN_FILE_PATH: File path to config xml in the suite config directories.
+# 10. CC_CLASS: Test case in cc file.
+# 11. SUITE_PLAN: Suite name such as cts.
+# 12. SUITE_PLAN_FILE_PATH: File path to config xml in the suite config directories.
+# 13. CACHE: A pseudo type that runs cache_finder without finding test in real.
_REFERENCE_TYPE = atest_enum.AtestEnum(['MODULE', 'CLASS', 'QUALIFIED_CLASS',
'MODULE_CLASS', 'PACKAGE',
'MODULE_PACKAGE', 'MODULE_FILE_PATH',
@@ -142,7 +145,7 @@
_REFERENCE_TYPE.MODULE_FILE_PATH,
_REFERENCE_TYPE.INTEGRATION,
_REFERENCE_TYPE.SUITE_PLAN_FILE_PATH,
- # TODO: Comment in SUITE when it's supported
+ # TODO: Uncomment in SUITE when it's supported
# _REFERENCE_TYPE.SUITE
]
if '.' in ref:
@@ -182,7 +185,7 @@
# If this ever becomes not the case, then we need to include path below.
return [_REFERENCE_TYPE.CACHE,
_REFERENCE_TYPE.INTEGRATION,
- # TODO: Comment in SUITE when it's supported
+ # TODO: Uncomment in SUITE when it's supported
# _REFERENCE_TYPE.SUITE,
_REFERENCE_TYPE.MODULE,
_REFERENCE_TYPE.SUITE_PLAN,
diff --git a/atest/test_finders/cache_finder.py b/atest/test_finders/cache_finder.py
index 3937ef0..5b7bd07 100644
--- a/atest/test_finders/cache_finder.py
+++ b/atest/test_finders/cache_finder.py
@@ -18,6 +18,7 @@
import atest_utils
from test_finders import test_finder_base
+from test_finders import test_info
class CacheFinder(test_finder_base.TestFinderBase):
"""Cache Finder class."""
@@ -26,6 +27,24 @@
def __init__(self, **kwargs):
super(CacheFinder, self).__init__()
+ def _is_latest_testinfos(self, test_infos):
+ """Check whether test_infos are up-to-date.
+
+ Args:
+ test_infos: A list of TestInfo.
+
+ Returns:
+ True if all keys in test_infos and TestInfo object are equal.
+ Otherwise, False.
+ """
+ sorted_base_ti = sorted(
+ vars(test_info.TestInfo(None, None, None)).keys())
+ for cached_test_info in test_infos:
+ sorted_cache_ti = sorted(vars(cached_test_info).keys())
+ if not sorted_cache_ti == sorted_base_ti:
+ return False
+ return True
+
def find_test_by_cache(self, test_reference):
"""Find the matched test_infos in saved caches.
@@ -33,6 +52,10 @@
test_reference: A string of the path to the test's file or dir.
Returns:
- A list of TestInfo namedtuple if cache found, else None.
+ A list of TestInfo namedtuple if cache found and is in latest
+ TestInfo format, else None.
"""
- return atest_utils.load_test_info_cache(test_reference)
+ test_infos = atest_utils.load_test_info_cache(test_reference)
+ if test_infos and self._is_latest_testinfos(test_infos):
+ return test_infos
+ return None
diff --git a/atest/test_finders/cache_finder_unittest.py b/atest/test_finders/cache_finder_unittest.py
index 92de278..7797ea3 100755
--- a/atest/test_finders/cache_finder_unittest.py
+++ b/atest/test_finders/cache_finder_unittest.py
@@ -36,19 +36,27 @@
@mock.patch.object(atest_utils, 'get_test_info_cache_path')
def test_find_test_by_cache(self, mock_get_cache_path):
"""Test find_test_by_cache method."""
- cached_test = 'mytest1'
- uncached_test = 'mytest2'
+ uncached_test = 'mytest1'
+ cached_test = 'hello_world_test'
+ uncached_test2 = 'mytest2'
test_cache_root = os.path.join(uc.TEST_DATA_DIR, 'cache_root')
- # Hit matched cache file, should return cached test infos.
+ # Hit matched cache file but no original_finder in it,
+ # should return None.
mock_get_cache_path.return_value = os.path.join(
test_cache_root,
'cd66f9f5ad63b42d0d77a9334de6bb73.cache')
+ self.assertIsNone(self.cache_finder.find_test_by_cache(uncached_test))
+ # Hit matched cache file and original_finder is in it,
+ # should return cached test infos.
+ mock_get_cache_path.return_value = os.path.join(
+ test_cache_root,
+ '78ea54ef315f5613f7c11dd1a87f10c7.cache')
self.assertIsNotNone(self.cache_finder.find_test_by_cache(cached_test))
# Does not hit matched cache file, should return cached test infos.
mock_get_cache_path.return_value = os.path.join(
test_cache_root,
'39488b7ac83c56d5a7d285519fe3e3fd.cache')
- self.assertIsNone(self.cache_finder.find_test_by_cache(uncached_test))
+ self.assertIsNone(self.cache_finder.find_test_by_cache(uncached_test2))
if __name__ == '__main__':
unittest.main()
diff --git a/atest/test_finders/module_finder.py b/atest/test_finders/module_finder.py
index 91b10be..d41e0c8 100644
--- a/atest/test_finders/module_finder.py
+++ b/atest/test_finders/module_finder.py
@@ -444,8 +444,7 @@
else:
search_dir = self.root_dir
package_paths = test_finder_utils.run_find_cmd(
- test_finder_utils.FIND_REFERENCE_TYPE.PACKAGE, search_dir,
- package.replace('.', '/'))
+ test_finder_utils.FIND_REFERENCE_TYPE.PACKAGE, search_dir, package)
# Package path will be the full path to the dir represented by package.
if not package_paths:
return None
diff --git a/atest/test_finders/module_finder_unittest.py b/atest/test_finders/module_finder_unittest.py
index 14041b0..1cb756f 100755
--- a/atest/test_finders/module_finder_unittest.py
+++ b/atest/test_finders/module_finder_unittest.py
@@ -290,6 +290,7 @@
mock_checkoutput.return_value = ''
self.assertIsNone(self.mod_finder.find_test_by_package_name('Not pkg'))
+ @mock.patch('os.path.isdir', return_value=False)
@mock.patch.object(module_finder.ModuleFinder, '_is_vts_module',
return_value=False)
@mock.patch.object(module_finder.ModuleFinder, '_get_build_targets')
@@ -297,7 +298,7 @@
@mock.patch('os.path.isfile', side_effect=unittest_utils.isfile_side_effect)
#pylint: disable=unused-argument
def test_find_test_by_module_and_package(self, _isfile, mock_checkoutput,
- mock_build, _vts):
+ mock_build, _vts, _isdir):
"""Test find_test_by_module_and_package."""
self.mod_finder.module_info.is_auto_gen_test_config.return_value = False
self.mod_finder.module_info.is_robolectric_test.return_value = False
@@ -308,7 +309,11 @@
constants.MODULE_CLASS: []}
self.mod_finder.module_info.get_module_info.return_value = mod_info
t_infos = self.mod_finder.find_test_by_module_and_package(MODULE_PACKAGE)
+ self.assertEqual(t_infos, None)
+ _isdir.return_value = True
+ t_infos = self.mod_finder.find_test_by_module_and_package(MODULE_PACKAGE)
unittest_utils.assert_equal_testinfos(self, t_infos[0], uc.PACKAGE_INFO)
+
# with method, raises
module_pkg_with_method = '%s:%s#%s' % (uc.MODULE2_NAME, uc.PACKAGE,
uc.METHOD_NAME)
diff --git a/atest/test_finders/test_finder_utils.py b/atest/test_finders/test_finder_utils.py
index 63d9014..8635603 100644
--- a/atest/test_finders/test_finder_utils.py
+++ b/atest/test_finders/test_finder_utils.py
@@ -20,6 +20,7 @@
import logging
import multiprocessing
import os
+import pickle
import re
import subprocess
import time
@@ -36,16 +37,11 @@
# assume the apk name is the build target.
_APK_RE = re.compile(r'^[^/]+\.apk$', re.I)
# RE for checking if TEST or TEST_F is in a cc file or not.
-_CC_CLASS_RE = re.compile(r'TEST(_F)?[ ]*\(', re.I)
+_CC_CLASS_RE = re.compile(r'^[ ]*TEST(_F|_P)?[ ]*\(', re.I)
# RE for checking if there exists one of the methods in java file.
_JAVA_METHODS_PATTERN = r'.*[ ]+({0})\(.*'
# RE for checking if there exists one of the methods in cc file.
-_CC_METHODS_PATTERN = r'[ ]*TEST(_F|_P)?[ ]*\(.*,[ ]*({0})\).*'
-# RE for checking if finding output matches the cc format.
-# e.g. file_path:TEST_F(test_name, method_name){
-_CC_OUTPUT_RE = re.compile(r'(?P<file_path>/.*):[ ]*TEST(_F|_P)?[ ]*\('
- r'(?P<test_name>\w+)[ ]*,[ ]*(?P<method_name>\w+)\)'
- r'[ ]*\{')
+_CC_METHODS_PATTERN = r'^[ ]*TEST(_F|_P)?[ ]*\(.*,[ ]*({0})\).*'
# Parse package name from the package declaration line of a java or a kotlin file.
# Group matches "foo.bar" of line "package foo.bar;" or "package foo.bar"
_PACKAGE_RE = re.compile(r'\s*package\s+(?P<package>[^(;|\s)]+)\s*', re.I)
@@ -86,7 +82,16 @@
FIND_REFERENCE_TYPE.CC_CLASS: r"find {0} {1} -type f -print"
r"| egrep -i '/*test.*\.(cc|cpp)$'"
r"| xargs -P" + str(_CPU_COUNT) +
- r" egrep -sH '[ ]*TEST(_F|_P)?[ ]*\({2}' || true"
+ r" egrep -sH '^[ ]*TEST(_F|_P)?[ ]*\({2}' || true"
+}
+
+# Map ref_type with its index file.
+FIND_INDEXES = {
+ FIND_REFERENCE_TYPE.CLASS: constants.CLASS_INDEX,
+ FIND_REFERENCE_TYPE.QUALIFIED_CLASS: constants.QCLASS_INDEX,
+ FIND_REFERENCE_TYPE.PACKAGE: constants.PACKAGE_INDEX,
+ FIND_REFERENCE_TYPE.INTEGRATION: constants.INT_INDEX,
+ FIND_REFERENCE_TYPE.CC_CLASS: constants.CC_CLASS_INDEX
}
# XML parsing related constants.
@@ -253,7 +258,7 @@
/<some_root>/cts/tests/jank/src/android/jank/cts/ui/CtsDeviceJankUi.java
Args:
- output: A string output of a unix 'find' command.
+ output: A string or list output of a unix 'find' command.
methods: A set of method names.
Returns:
@@ -262,10 +267,11 @@
if not output:
return None
verified_tests = set()
- output_lines = output.strip('\n').split('\n')
- for test in output_lines:
- # compare _CC_OUTPUT_RE with output
- match_obj = _CC_OUTPUT_RE.match(test)
+ if isinstance(output, str):
+ output = output.splitlines()
+ for test in output:
+ # compare CC_OUTPUT_RE with output
+ match_obj = constants.CC_OUTPUT_RE.match(test)
if match_obj:
# cc/cpp
fpath = match_obj.group('file_path')
@@ -404,15 +410,32 @@
Return:
A list of the path to the target.
+ If the search_dir is inexistent, None will be returned.
"""
- prune_cond = _get_prune_cond_of_ignored_dirs()
- find_cmd = FIND_CMDS[ref_type].format(search_dir, prune_cond, target)
- start = time.time()
+ # If module_info.json is outdated, finding in the search_dir can result in
+ # raising exception. Return null immediately can guild users to run
+ # --rebuild-module-info to resolve the problem.
+ if not os.path.isdir(search_dir):
+ logging.debug('\'%s\' does not exist!', search_dir)
+ return None
ref_name = FIND_REFERENCE_TYPE[ref_type]
- logging.debug('Executing %s find cmd: %s', ref_name, find_cmd)
- out = subprocess.check_output(find_cmd, shell=True)
+ start = time.time()
+ if os.path.isfile(FIND_INDEXES[ref_type]):
+ _dict, out = {}, None
+ with open(FIND_INDEXES[ref_type], 'rb') as index:
+ _dict = pickle.load(index)
+ if _dict.get(target):
+ logging.debug('Found %s in %s', target, FIND_INDEXES[ref_type])
+ out = [path for path in _dict.get(target) if search_dir in path]
+ else:
+ prune_cond = _get_prune_cond_of_ignored_dirs()
+ if '.' in target:
+ target = target.replace('.', '/')
+ find_cmd = FIND_CMDS[ref_type].format(search_dir, prune_cond, target)
+ logging.debug('Executing %s find cmd: %s', ref_name, find_cmd)
+ out = subprocess.check_output(find_cmd, shell=True)
+ logging.debug('%s find cmd out: %s', ref_name, out)
logging.debug('%s find completed in %ss', ref_name, time.time() - start)
- logging.debug('%s find cmd out: %s', ref_name, out)
return extract_test_path(out, methods)
@@ -430,16 +453,12 @@
A list of the path to the java/cc file.
"""
if is_native_test:
- find_target = class_name
ref_type = FIND_REFERENCE_TYPE.CC_CLASS
- return run_find_cmd(ref_type, search_dir, find_target, methods)
- if '.' in class_name:
- find_target = class_name.replace('.', '/')
+ elif '.' in class_name:
ref_type = FIND_REFERENCE_TYPE.QUALIFIED_CLASS
else:
- find_target = class_name
ref_type = FIND_REFERENCE_TYPE.CLASS
- return run_find_cmd(ref_type, search_dir, find_target, methods)
+ return run_find_cmd(ref_type, search_dir, class_name, methods)
def is_equal_or_sub_dir(sub_dir, parent_dir):
diff --git a/atest/test_finders/test_finder_utils_unittest.py b/atest/test_finders/test_finder_utils_unittest.py
index 4c17678..c6015d7 100755
--- a/atest/test_finders/test_finder_utils_unittest.py
+++ b/atest/test_finders/test_finder_utils_unittest.py
@@ -433,26 +433,44 @@
test_result = test_finder_utils.search_integration_dirs(INT_FILE_NAME, int_dirs)
unittest_utils.assert_strict_equal(self, test_result, paths)
+ @mock.patch('os.path.isfile', return_value=False)
@mock.patch('os.environ.get', return_value=uc.TEST_CONFIG_DATA_DIR)
@mock.patch('__builtin__.raw_input', return_value='0')
- def test_find_class_file(self, mock_input, _mock_env):
+ # pylint: disable=too-many-statements
+ def test_find_class_file(self, mock_input, _mock_env, _mock_isfile):
"""Test find_class_file."""
+ # 1. Java class(find).
java_tmp_test_result = []
mock_input.return_value = '0'
java_class = os.path.join(uc.FIND_PATH, uc.FIND_PATH_TESTCASE_JAVA + '.java')
java_tmp_test_result.extend(test_finder_utils.find_class_file(uc.FIND_PATH,
uc.FIND_PATH_TESTCASE_JAVA))
-
mock_input.return_value = '1'
kt_class = os.path.join(uc.FIND_PATH, uc.FIND_PATH_TESTCASE_JAVA + '.kt')
java_tmp_test_result.extend(test_finder_utils.find_class_file(uc.FIND_PATH,
uc.FIND_PATH_TESTCASE_JAVA))
-
self.assertTrue(java_class in java_tmp_test_result)
self.assertTrue(kt_class in java_tmp_test_result)
+ # 2. Java class(read index).
del java_tmp_test_result[:]
mock_input.return_value = '0'
+ _mock_isfile = True
+ test_finder_utils.FIND_INDEXES['CLASS'] = uc.CLASS_INDEX
+ java_class = os.path.join(uc.FIND_PATH, uc.FIND_PATH_TESTCASE_JAVA + '.java')
+ java_tmp_test_result.extend(test_finder_utils.find_class_file(uc.FIND_PATH,
+ uc.FIND_PATH_TESTCASE_JAVA))
+ mock_input.return_value = '1'
+ kt_class = os.path.join(uc.FIND_PATH, uc.FIND_PATH_TESTCASE_JAVA + '.kt')
+ java_tmp_test_result.extend(test_finder_utils.find_class_file(uc.FIND_PATH,
+ uc.FIND_PATH_TESTCASE_JAVA))
+ self.assertTrue(java_class in java_tmp_test_result)
+ self.assertTrue(kt_class in java_tmp_test_result)
+
+ # 3. Qualified Java class(find).
+ del java_tmp_test_result[:]
+ mock_input.return_value = '0'
+ _mock_isfile = False
java_qualified_class = '{0}.{1}'.format(uc.FIND_PATH_FOLDER, uc.FIND_PATH_TESTCASE_JAVA)
java_tmp_test_result.extend(test_finder_utils.find_class_file(uc.FIND_PATH,
java_qualified_class))
@@ -462,7 +480,23 @@
self.assertTrue(java_class in java_tmp_test_result)
self.assertTrue(kt_class in java_tmp_test_result)
+ # 4. Qualified Java class(read index).
+ del java_tmp_test_result[:]
+ mock_input.return_value = '0'
+ _mock_isfile = True
+ test_finder_utils.FIND_INDEXES['QUALIFIED_CLASS'] = uc.QCLASS_INDEX
+ java_qualified_class = '{0}.{1}'.format(uc.FIND_PATH_FOLDER, uc.FIND_PATH_TESTCASE_JAVA)
+ java_tmp_test_result.extend(test_finder_utils.find_class_file(uc.FIND_PATH,
+ java_qualified_class))
+ mock_input.return_value = '1'
+ java_tmp_test_result.extend(test_finder_utils.find_class_file(uc.FIND_PATH,
+ java_qualified_class))
+ self.assertTrue(java_class in java_tmp_test_result)
+ self.assertTrue(kt_class in java_tmp_test_result)
+
+ # 5. CC class(find).
cc_tmp_test_result = []
+ _mock_isfile = False
mock_input.return_value = '0'
cpp_class = os.path.join(uc.FIND_PATH, uc.FIND_PATH_FILENAME_CC + '.cpp')
cc_tmp_test_result.extend(test_finder_utils.find_class_file(uc.FIND_PATH,
@@ -473,7 +507,23 @@
cc_tmp_test_result.extend(test_finder_utils.find_class_file(uc.FIND_PATH,
uc.FIND_PATH_TESTCASE_CC,
True))
+ self.assertTrue(cpp_class in cc_tmp_test_result)
+ self.assertTrue(cc_class in cc_tmp_test_result)
+ # 6. CC class(read index).
+ del cc_tmp_test_result[:]
+ mock_input.return_value = '0'
+ _mock_isfile = True
+ test_finder_utils.FIND_INDEXES['CC_CLASS'] = uc.CC_CLASS_INDEX
+ cpp_class = os.path.join(uc.FIND_PATH, uc.FIND_PATH_FILENAME_CC + '.cpp')
+ cc_tmp_test_result.extend(test_finder_utils.find_class_file(uc.FIND_PATH,
+ uc.FIND_PATH_TESTCASE_CC,
+ True))
+ mock_input.return_value = '1'
+ cc_class = os.path.join(uc.FIND_PATH, uc.FIND_PATH_FILENAME_CC + '.cc')
+ cc_tmp_test_result.extend(test_finder_utils.find_class_file(uc.FIND_PATH,
+ uc.FIND_PATH_TESTCASE_CC,
+ True))
self.assertTrue(cpp_class in cc_tmp_test_result)
self.assertTrue(cc_class in cc_tmp_test_result)
diff --git a/atest/test_finders/test_info.py b/atest/test_finders/test_info.py
index 18e3dbf..2c24b3a 100644
--- a/atest/test_finders/test_info.py
+++ b/atest/test_finders/test_info.py
@@ -57,15 +57,18 @@
# True if the test should run on host and require no device. The
# attribute is only set through TEST_MAPPING file.
self.host = False
+ # A string of test finder
+ self.test_finder = ''
def __str__(self):
host_info = (' - runs on host without device required.' if self.host
else '')
return ('test_name: %s - test_runner:%s - build_targets:%s - data:%s - '
- 'suite:%s - module_class: %s - install_locations:%s%s' % (
+ 'suite:%s - module_class: %s - install_locations:%s%s - '
+ 'test_finder:%s' % (
self.test_name, self.test_runner, self.build_targets,
self.data, self.suite, self.module_class,
- self.install_locations, host_info))
+ self.install_locations, host_info, self.test_finder))
def get_supported_exec_mode(self):
"""Get the supported execution mode of the test.
diff --git a/atest/test_finders/tf_integration_finder_unittest.py b/atest/test_finders/tf_integration_finder_unittest.py
index a8b58cc..170da0c 100755
--- a/atest/test_finders/tf_integration_finder_unittest.py
+++ b/atest/test_finders/tf_integration_finder_unittest.py
@@ -63,13 +63,20 @@
return_value=uc.FULL_CLASS_NAME)
@mock.patch('subprocess.check_output')
@mock.patch('os.path.exists', return_value=True)
- @mock.patch('os.path.isfile', return_value=True)
+ @mock.patch('os.path.isfile', return_value=False)
+ @mock.patch('os.path.isdir', return_value=False)
#pylint: disable=unused-argument
- def test_find_test_by_integration_name(self, _isfile, _path, mock_find,
+ def test_find_test_by_integration_name(self, _isdir, _isfile, _path, mock_find,
_fcqn, _build):
- """Test find_test_by_integration_name."""
+ """Test find_test_by_integration_name.
+
+ Note that _isfile is always False since we don't index integration tests.
+ """
mock_find.return_value = os.path.join(uc.ROOT, uc.INT_DIR, uc.INT_NAME + '.xml')
t_infos = self.tf_finder.find_test_by_integration_name(uc.INT_NAME)
+ self.assertEqual(len(t_infos), 0)
+ _isdir.return_value = True
+ t_infos = self.tf_finder.find_test_by_integration_name(uc.INT_NAME)
unittest_utils.assert_equal_testinfos(self, t_infos[0], uc.INT_INFO)
t_infos = self.tf_finder.find_test_by_integration_name(INT_NAME_CLASS)
unittest_utils.assert_equal_testinfos(self, t_infos[0], INT_CLASS_INFO)
diff --git a/atest/test_runners/atest_tf_test_runner.py b/atest/test_runners/atest_tf_test_runner.py
index ffd1ac9..2a790e5 100644
--- a/atest/test_runners/atest_tf_test_runner.py
+++ b/atest/test_runners/atest_tf_test_runner.py
@@ -280,6 +280,7 @@
build_req.add(executable)
return build_req
+ # pylint: disable=too-many-branches
@staticmethod
def _parse_extra_args(extra_args):
"""Convert the extra args into something tf can understand.
@@ -326,6 +327,12 @@
args_to_append.append('--module-parameter')
args_to_append.append('instant_app')
continue
+ if constants.SECONDARY_USER == arg:
+ args_to_append.append('--enable-parameterized-modules')
+ args_to_append.append('--enable-optional-parameterization')
+ args_to_append.append('--module-parameter')
+ args_to_append.append('secondary_user')
+ continue
args_not_supported.append(arg)
return args_to_append, args_not_supported
diff --git a/atest/test_runners/robolectric_test_runner.py b/atest/test_runners/robolectric_test_runner.py
index dacbc62..b10a6e8 100644
--- a/atest/test_runners/robolectric_test_runner.py
+++ b/atest/test_runners/robolectric_test_runner.py
@@ -162,6 +162,8 @@
"""
buf = ''
while True:
+ # Make sure that ATest gets content from current position.
+ communication_file.seek(0, 1)
data = communication_file.read()
buf += data
reg = re.compile(r'(.|\n)*}\n\n')
diff --git a/atest/tools/atest_tools.py b/atest/tools/atest_tools.py
new file mode 100755
index 0000000..b333ff8
--- /dev/null
+++ b/atest/tools/atest_tools.py
@@ -0,0 +1,350 @@
+#!/usr/bin/env python
+# Copyright 2019, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Atest tool functions.
+"""
+
+from __future__ import print_function
+
+import logging
+import os
+import pickle
+import shutil
+import subprocess
+import sys
+
+import constants
+import module_info
+
+MAC_UPDB_SRC = os.path.join(os.path.dirname(__file__), 'updatedb_darwin.sh')
+MAC_UPDB_DST = os.path.join(os.getenv(constants.ANDROID_HOST_OUT, ''), 'bin')
+UPDATEDB = 'updatedb'
+LOCATE = 'locate'
+SEARCH_TOP = os.getenv(constants.ANDROID_BUILD_TOP, '')
+MACOSX = 'Darwin'
+OSNAME = os.uname()[0]
+
+# The list was generated by command:
+# find `gettop` -type d -wholename `gettop`/out -prune -o -type d -name '.*'
+# -print | awk -F/ '{{print $NF}}'| sort -u
+PRUNENAMES = ['.abc', '.appveyor', '.azure-pipelines',
+ '.bazelci', '.buildscript', '.ci', '.circleci', '.conan',
+ '.externalToolBuilders',
+ '.git', '.github', '.github-ci', '.google', '.gradle',
+ '.idea', '.intermediates',
+ '.jenkins',
+ '.kokoro',
+ '.libs_cffi_backend',
+ '.mvn',
+ '.prebuilt_info', '.private', '__pycache__',
+ '.repo',
+ '.semaphore', '.settings', '.static', '.svn',
+ '.test', '.travis', '.tx',
+ '.vscode']
+# Running locate + grep consumes tremendous amount of time in MacOS. Running it
+# with a physical script file can increase the performance.
+TMPRUN = '/tmp/._'
+
+def _mkdir_when_inexists(dirname):
+ if not os.path.isdir(dirname):
+ os.makedirs(dirname)
+
+def _install_updatedb():
+ """Install a customized updatedb for MacOS and ensure it is executable."""
+ _mkdir_when_inexists(MAC_UPDB_DST)
+ _mkdir_when_inexists(constants.INDEX_DIR)
+ if OSNAME == MACOSX:
+ shutil.copy2(MAC_UPDB_SRC, os.path.join(MAC_UPDB_DST, UPDATEDB))
+ os.chmod(os.path.join(MAC_UPDB_DST, UPDATEDB), 0755)
+
+def has_indexes():
+ """Detect if all index files are all available.
+
+ Returns:
+ True if indexes exist, False otherwise.
+ """
+ indexes = (constants.CLASS_INDEX, constants.QCLASS_INDEX,
+ constants.PACKAGE_INDEX, constants.CC_CLASS_INDEX)
+ for index in indexes:
+ if not os.path.isfile(index):
+ return False
+ return True
+
+def has_command(cmd):
+ """Detect if the command is available in PATH.
+
+ shutil.which('cmd') is only valid in Py3 so we need to customise it.
+
+ Args:
+ cmd: A string of the tested command.
+
+ Returns:
+ True if found, False otherwise."""
+ paths = os.getenv('PATH', '').split(':')
+ for path in paths:
+ if os.path.isfile(os.path.join(path, cmd)):
+ return True
+ return False
+
+def run_updatedb(search_root=SEARCH_TOP, output_cache=constants.LOCATE_CACHE,
+ **kwargs):
+ """Run updatedb and generate cache in $ANDROID_HOST_OUT/indexes/mlocate.db
+
+ Args:
+ search_root: The path of the search root(-U).
+ output_cache: The filename of the updatedb cache(-o).
+ kwargs: (optional)
+ prunepaths: A list of paths unwanted to be searched(-e).
+ prunenames: A list of dirname that won't be cached(-n).
+ """
+ prunenames = kwargs.pop('prunenames', ' '.join(PRUNENAMES))
+ prunepaths = kwargs.pop('prunepaths', os.path.join(search_root, 'out'))
+ if kwargs:
+ raise TypeError('Unexpected **kwargs: %r' % kwargs)
+ updatedb_cmd = [UPDATEDB, '-l0']
+ updatedb_cmd.append('-U%s' % search_root)
+ updatedb_cmd.append('-e%s' % prunepaths)
+ updatedb_cmd.append('-n%s' % prunenames)
+ updatedb_cmd.append('-o%s' % output_cache)
+ try:
+ _install_updatedb()
+ except IOError as e:
+ logging.error('Error installing updatedb: %s', e)
+
+ if not has_command(UPDATEDB):
+ return
+ logging.debug('Running updatedb... ')
+ try:
+ full_env_vars = os.environ.copy()
+ logging.debug('Executing: %s', updatedb_cmd)
+ subprocess.check_call(updatedb_cmd, stderr=subprocess.STDOUT,
+ env=full_env_vars)
+ except (KeyboardInterrupt, SystemExit):
+ logging.error('Process interrupted or failure.')
+ except subprocess.CalledProcessError as err:
+ logging.error('Error executing: %s', updatedb_cmd)
+ if err.output:
+ logging.error(err.output)
+
+def _dump_index(dump_file, output, output_re, key, value):
+ """Dump indexed data with pickle.
+
+ Args:
+ dump_file: A string of absolute path of the index file.
+ output: A string generated by locate and grep.
+ output_re: An regex which is used for grouping patterns.
+ key: A string for dictionary key, e.g. classname, package, cc_class, etc.
+ value: A set of path.
+
+ The data structure will be like:
+ {
+ 'Foo': {'/path/to/Foo.java', '/path2/to/Foo.kt'},
+ 'Boo': {'/path3/to/Boo.java'}
+ }
+ """
+ _dict = {}
+ with open(dump_file, 'wb') as cache_file:
+ for entry in output.splitlines():
+ match = output_re.match(entry)
+ if match:
+ _dict.setdefault(match.group(key), set()).add(match.group(value))
+ try:
+ pickle.dump(_dict, cache_file, protocol=2)
+ except IOError:
+ os.remove(dump_file)
+ logging.error('Failed in dumping %s', dump_file)
+
+def _get_cc_result(locatedb=None):
+ """Search all testable cc/cpp and grep TEST(), TEST_F() or TEST_P().
+
+ Return:
+ A string object generated by subprocess.
+ """
+ if not locatedb:
+ locatedb = constants.LOCATE_CACHE
+ cc_grep_re = r'^\s*TEST(_P|_F)?\s*\([[:alnum:]]+,'
+ if OSNAME == MACOSX:
+ find_cmd = (r"locate -d {0} '*.cpp' '*.cc' | grep -i test "
+ "| xargs egrep -sH '{1}' || true")
+ else:
+ find_cmd = (r"locate -d {0} / | egrep -i '/*.test.*\.(cc|cpp)$' "
+ "| xargs egrep -sH '{1}' || true")
+ find_cc_cmd = find_cmd.format(locatedb, cc_grep_re)
+ logging.debug('Probing CC classes:\n %s', find_cc_cmd)
+ return subprocess.check_output('echo \"%s\" > %s; sh %s'
+ % (find_cc_cmd, TMPRUN, TMPRUN), shell=True)
+
+def _get_java_result(locatedb=None):
+ """Search all testable java/kt and grep package.
+
+ Return:
+ A string object generated by subprocess.
+ """
+ if not locatedb:
+ locatedb = constants.LOCATE_CACHE
+ package_grep_re = r'^\s*package\s+[a-z][[:alnum:]]+[^{]'
+ if OSNAME == MACOSX:
+ find_cmd = r"locate -d%s '*.java' '*.kt'|grep -i test" % locatedb
+ else:
+ find_cmd = r"locate -d%s / | egrep -i '/*.test.*\.(java|kt)$'" % locatedb
+ find_java_cmd = find_cmd + '| xargs egrep -sH \'%s\' || true' % package_grep_re
+ logging.debug('Probing Java classes:\n %s', find_java_cmd)
+ return subprocess.check_output('echo \"%s\" > %s; sh %s'
+ % (find_java_cmd, TMPRUN, TMPRUN), shell=True)
+
+def _index_testable_modules(index):
+ """Dump testable modules read by tab completion.
+
+ Args:
+ index: A string path of the index file.
+ """
+ logging.debug('indexing testable modules.')
+ testable_modules = module_info.ModuleInfo().get_testable_modules()
+ with open(index, 'wb') as cache:
+ try:
+ pickle.dump(testable_modules, cache, protocol=2)
+ except IOError:
+ os.remove(cache)
+ logging.error('Failed in dumping %s', cache)
+
+def _index_cc_classes(output, index):
+ """Index Java classes.
+
+ The data structure is like:
+ {
+ 'FooTestCase': {'/path1/to/the/FooTestCase.java',
+ '/path2/to/the/FooTestCase.kt'}
+ }
+
+ Args:
+ output: A string object generated by _get_cc_result().
+ index: A string path of the index file.
+ """
+ logging.debug('indexing CC classes.')
+ _dump_index(dump_file=index, output=output,
+ output_re=constants.CC_OUTPUT_RE,
+ key='test_name', value='file_path')
+
+def _index_java_classes(output, index):
+ """Index Java classes.
+ The data structure is like:
+ {
+ 'FooTestCase': {'/path1/to/the/FooTestCase.java',
+ '/path2/to/the/FooTestCase.kt'}
+ }
+
+ Args:
+ output: A string object generated by _get_java_result().
+ index: A string path of the index file.
+ """
+ logging.debug('indexing Java classes.')
+ _dump_index(dump_file=index, output=output,
+ output_re=constants.CLASS_OUTPUT_RE,
+ key='class', value='java_path')
+
+def _index_packages(output, index):
+ """Index Java packages.
+ The data structure is like:
+ {
+ 'a.b.c.d': {'/path1/to/a/b/c/d/FooTestCase.java',
+ '/path2/to/a/b/c/d/FooTestCase.kt'}
+ }
+
+ Args:
+ output: A string object generated by _get_java_result().
+ index: A string path of the index file.
+ """
+ logging.debug('indexing packages.')
+ _dump_index(dump_file=index,
+ output=output, output_re=constants.PACKAGE_OUTPUT_RE,
+ key='package', value='java_path')
+
+def _index_qualified_classes(output, index):
+ """Index Fully Qualified Java Classes(FQCN).
+ The data structure is like:
+ {
+ 'a.b.c.d.FooTestCase': {'/path1/to/a/b/c/d/FooTestCase.java',
+ '/path2/to/a/b/c/d/FooTestCase.kt'}
+ }
+
+ Args:
+ output: A string object generated by _get_java_result().
+ index: A string path of the index file.
+ """
+ logging.debug('indexing qualified classes.')
+ _dict = {}
+ with open(index, 'wb') as cache_file:
+ for entry in output.split('\n'):
+ match = constants.QCLASS_OUTPUT_RE.match(entry)
+ if match:
+ fqcn = match.group('package') + '.' + match.group('class')
+ _dict.setdefault(fqcn, set()).add(match.group('java_path'))
+ try:
+ pickle.dump(_dict, cache_file, protocol=2)
+ except (KeyboardInterrupt, SystemExit):
+ logging.error('Process interrupted or failure.')
+ os.remove(index)
+ except IOError:
+ logging.error('Failed in dumping %s', index)
+
+def index_targets(output_cache=constants.LOCATE_CACHE, **kwargs):
+ """The entrypoint of indexing targets.
+
+ Utilise mlocate database to index reference types of CLASS, CC_CLASS,
+ PACKAGE and QUALIFIED_CLASS. Testable module for tab completion is also
+ generated in this method.
+
+ Args:
+ output_cache: A file path of the updatedb cache(e.g. /path/to/mlocate.db).
+ kwargs: (optional)
+ class_index: A path string of the Java class index.
+ qclass_index: A path string of the qualified class index.
+ package_index: A path string of the package index.
+ cc_class_index: A path string of the CC class index.
+ module_index: A path string of the testable module index.
+ integration_index: A path string of the integration index.
+ """
+ class_index = kwargs.pop('class_index', constants.CLASS_INDEX)
+ qclass_index = kwargs.pop('qclass_index', constants.QCLASS_INDEX)
+ package_index = kwargs.pop('package_index', constants.PACKAGE_INDEX)
+ cc_class_index = kwargs.pop('cc_class_index', constants.CC_CLASS_INDEX)
+ module_index = kwargs.pop('module_index', constants.MODULE_INDEX)
+ # Uncomment below if we decide to support INTEGRATION.
+ #integration_index = kwargs.pop('integration_index', constants.INT_INDEX)
+ if kwargs:
+ raise TypeError('Unexpected **kwargs: %r' % kwargs)
+
+ # Step 0: generate mlocate database prior to indexing targets.
+ run_updatedb(SEARCH_TOP, constants.LOCATE_CACHE)
+ if not has_command(LOCATE):
+ return
+ # Step 1: generate output string for indexing targets.
+ logging.debug('Indexing targets... ')
+ cc_result = _get_cc_result(output_cache)
+ java_result = _get_java_result(output_cache)
+ # Step 2: index Java and CC classes.
+ _index_cc_classes(cc_result, cc_class_index)
+ _index_java_classes(java_result, class_index)
+ _index_qualified_classes(java_result, qclass_index)
+ _index_packages(java_result, package_index)
+ _index_testable_modules(module_index)
+ if os.path.isfile(TMPRUN):
+ os.remove(TMPRUN)
+
+if __name__ == '__main__':
+ if not os.getenv(constants.ANDROID_HOST_OUT, ''):
+ sys.exit()
+ index_targets()
diff --git a/atest/tools/atest_tools_unittest.py b/atest/tools/atest_tools_unittest.py
new file mode 100755
index 0000000..bd7ccf1
--- /dev/null
+++ b/atest/tools/atest_tools_unittest.py
@@ -0,0 +1,92 @@
+#!/usr/bin/env python
+#
+# Copyright 2019, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Unittest for atest_tools."""
+
+import os
+import pickle
+import platform
+import subprocess
+import sys
+import unittest
+
+import atest_tools
+sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
+# pylint: disable=wrong-import-position
+import unittest_constants as uc
+
+SEARCH_ROOT = uc.TEST_DATA_DIR
+PRUNEPATH = uc.TEST_CONFIG_DATA_DIR
+
+
+class AtestToolsUnittests(unittest.TestCase):
+ """"Unittest Class for atest_tools.py."""
+
+ def test_index_targets(self):
+ """Test method index_targets."""
+ if atest_tools.has_command('updatedb'):
+ atest_tools.run_updatedb(SEARCH_ROOT, uc.LOCATE_CACHE,
+ prunepaths=PRUNEPATH)
+ # test_config/ is excluded so that a.xml won't be found.
+ locate_cmd1 = ['locate', '-d', uc.LOCATE_CACHE, '/a.xml']
+ # locate always return 0 when not found in Darwin, therefore,
+ # check null return in Darwin and return value in Linux.
+ if platform.system() == 'Darwin':
+ self.assertEqual(subprocess.check_output(locate_cmd1), "")
+ else:
+ self.assertEqual(subprocess.call(locate_cmd1), 1)
+ # module-info.json can be found in the search_root.
+ locate_cmd2 = ['locate', '-d', uc.LOCATE_CACHE, 'module-info.json']
+ self.assertEqual(subprocess.call(locate_cmd2), 0)
+ else:
+ self.assertEqual(atest_tools.has_command('updatedb'), False)
+
+ if atest_tools.has_command('locate'):
+ atest_tools.index_targets(uc.LOCATE_CACHE,
+ class_index=uc.CLASS_INDEX,
+ qclass_index=uc.QCLASS_INDEX,
+ cc_class_index=uc.CC_CLASS_INDEX,
+ package_index=uc.PACKAGE_INDEX)
+ _dict = {}
+ # Test finding a Java class
+ with open(uc.CLASS_INDEX, 'rb') as _cache:
+ _dict = pickle.load(_cache)
+ self.assertIsNotNone(_dict.get('PathTesting'))
+ # Test finding a CC class
+ with open(uc.CC_CLASS_INDEX, 'rb') as _cache:
+ _dict = pickle.load(_cache)
+ self.assertIsNotNone(_dict.get('HelloWorldTest'))
+ # Test finding a package
+ with open(uc.PACKAGE_INDEX, 'rb') as _cache:
+ _dict = pickle.load(_cache)
+ self.assertIsNotNone(_dict.get('android.jank.cts.ui'))
+ # Test finding a fully qualified class name
+ with open(uc.QCLASS_INDEX, 'rb') as _cache:
+ _dict = pickle.load(_cache)
+ self.assertIsNotNone(_dict.get('android.jank.cts.ui.PathTesting'))
+ # Clean up.
+ targets_to_delete = (uc.LOCATE_CACHE,
+ uc.CLASS_INDEX,
+ uc.QCLASS_INDEX,
+ uc.CC_CLASS_INDEX,
+ uc.PACKAGE_INDEX)
+ for idx in targets_to_delete:
+ os.remove(idx)
+ else:
+ self.assertEqual(atest_tools.has_command('locate'), False)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/atest/tools/atest_updatedb.py b/atest/tools/atest_updatedb.py
deleted file mode 100755
index 6b0c8f8..0000000
--- a/atest/tools/atest_updatedb.py
+++ /dev/null
@@ -1,105 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2019, The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""
-Atest updatedb functions.
-"""
-
-from __future__ import print_function
-
-import logging
-import os
-import platform
-import shutil
-import subprocess
-
-AND_HOSTOUT_DIR = os.getenv('ANDROID_HOST_OUT', '')
-MAC_UPDB_SRC = os.path.join(os.path.dirname(__file__), 'updatedb_darwin.sh')
-MAC_UPDB_DST = os.path.join(AND_HOSTOUT_DIR, 'bin')
-UPDATEDB = 'updatedb'
-
-# updatedb does not support ".*" so below are excluded explicitly.
-_PRUNENAMES = ['.abc', '.appveyor', '.azure-pipelines',
- '.bazelci', '.buildscript',
- '.ci', '.circleci',
- '.conan',
- '.externalToolBuilders',
- '.git', '.github', '.google', '.gradle',
- '.idea', '.intermediates',
- '.kokoro',
- '.mvn',
- '.prebuilt_info', '.private', '__pycache__',
- '.repo',
- '.semaphore', '.settings',
- '.static', '.svn',
- '.test', '.travis', '.tx',
- '.vscode']
-_CACHE = 'locate.database'
-
-def _install_updatedb():
- """Install a customized updatedb for MacOS."""
- if platform.system() == 'Darwin':
- if not os.path.isdir(MAC_UPDB_DST):
- os.makedirs(MAC_UPDB_DST)
- shutil.copy2(MAC_UPDB_SRC, os.path.join(MAC_UPDB_DST, UPDATEDB))
- os.chmod(os.path.join(MAC_UPDB_DST, UPDATEDB), 0755)
-
-
-def run_updatedb(**kwargs):
- """Run updatedb and generate cache in $ANDROID_HOST_OUT/locate.database
-
- Args:
- search_root: The path of the search root(-U).
- prunepaths: A list of paths unwanted to be searched(-e).
- prunenames: A list of dirname that won't be cached(-n).
- output_cache: The filename of the updatedb cache(-o).
-
- Returns:
- Boolean of the status of updatedb execution, True if update successfully,
- False otherwise.
- """
- repo_root = os.getenv('ANDROID_BUILD_TOP', '')
- search_root = kwargs.get('search_root', repo_root)
- prunepaths = kwargs.get('prunepaths', os.path.join(search_root, 'out'))
- prunenames = kwargs.get('prunenames', ' '.join(_PRUNENAMES))
- output_cache = kwargs.get('output_cache',
- os.path.join(AND_HOSTOUT_DIR, _CACHE))
- if not os.path.exists(os.path.dirname(output_cache)):
- os.makedirs(os.path.dirname(output_cache))
- updatedb_cmd = [UPDATEDB, '-l0']
- updatedb_cmd.append('-U%s' % search_root)
- updatedb_cmd.append('-e%s' % prunepaths)
- updatedb_cmd.append('-n%s' % prunenames)
- updatedb_cmd.append('-o%s' % output_cache)
- try:
- _install_updatedb()
- except IOError as e:
- logging.error('Error installing updatedb: %s', e)
- return False
- print('Running updatedb for locate...')
- try:
- full_env_vars = os.environ.copy()
- logging.debug('Executing: %s', updatedb_cmd)
- subprocess.check_call(updatedb_cmd, stderr=subprocess.STDOUT,
- env=full_env_vars)
- return True
- except subprocess.CalledProcessError as err:
- logging.error('Error executing: %s', updatedb_cmd)
- if err.output:
- logging.error(err.output)
- return False
-
-if __name__ == '__main__':
- run_updatedb()
diff --git a/atest/tools/atest_updatedb_unittest.py b/atest/tools/atest_updatedb_unittest.py
deleted file mode 100755
index 923c5dc..0000000
--- a/atest/tools/atest_updatedb_unittest.py
+++ /dev/null
@@ -1,56 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2019, The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""Unittest for atest_updatedb."""
-
-import os
-import platform
-import subprocess
-import sys
-import unittest
-
-import atest_updatedb
-sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
-# pylint: disable=wrong-import-position
-import unittest_constants
-
-SEARCH_ROOT = unittest_constants.TEST_DATA_DIR
-PRUNEPATH = unittest_constants.TEST_CONFIG_DATA_DIR
-_CACHE = '/tmp/locate.database'
-
-
-class AtestUpdatedbUnittests(unittest.TestCase):
- """"Unittest Class for atest_updatedb.py."""
-
- def test_atest_updatedb(self):
- """Test method run_updatedb."""
- atest_updatedb.run_updatedb(search_root=SEARCH_ROOT,
- prunepaths=PRUNEPATH,
- output_cache=_CACHE)
- # test_config/ is excluded so that a.xml won't be found.
- locate_cmd1 = ['locate', '-d', _CACHE, '/a.xml']
- # locate always return 0 when not found in Darwin, therefore,
- # check null return in Darwin and return value in Linux.
- if platform.system() == 'Darwin':
- self.assertEqual(subprocess.check_output(locate_cmd1), "")
- else:
- self.assertEqual(subprocess.call(locate_cmd1), 1)
- # module-info.json can be found in the search_root.
- locate_cmd2 = ['locate', '-d', _CACHE, 'module-info.json']
- self.assertEqual(subprocess.call(locate_cmd2), 0)
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/atest/unittest_constants.py b/atest/unittest_constants.py
index 03bf5c0..c757936 100644
--- a/atest/unittest_constants.py
+++ b/atest/unittest_constants.py
@@ -238,3 +238,10 @@
FUZZY_MOD1 = 'Mod1'
FUZZY_MOD2 = 'nod2'
FUZZY_MOD3 = 'mod3mod3'
+
+LOCATE_CACHE = '/tmp/mcloate.db'
+CLASS_INDEX = '/tmp/classes.idx'
+QCLASS_INDEX = '/tmp/fqcn.idx'
+CC_CLASS_INDEX = '/tmp/cc_classes.idx'
+PACKAGE_INDEX = '/tmp/packages.idx'
+MODULE_INDEX = '/tmp/modules.idx'
diff --git a/atest/unittest_data/cache_root/78ea54ef315f5613f7c11dd1a87f10c7.cache b/atest/unittest_data/cache_root/78ea54ef315f5613f7c11dd1a87f10c7.cache
new file mode 100644
index 0000000..54e9794
--- /dev/null
+++ b/atest/unittest_data/cache_root/78ea54ef315f5613f7c11dd1a87f10c7.cache
@@ -0,0 +1,77 @@
+c__builtin__
+set
+p0
+((lp1
+ccopy_reg
+_reconstructor
+p2
+(ctest_finders.test_info
+TestInfo
+p3
+c__builtin__
+object
+p4
+Ntp5
+Rp6
+(dp7
+S'install_locations'
+p8
+g0
+((lp9
+S'device'
+p10
+aS'host'
+p11
+atp12
+Rp13
+sS'test_runner'
+p14
+S'AtestTradefedTestRunner'
+p15
+sS'module_class'
+p16
+(lp17
+VNATIVE_TESTS
+p18
+asS'from_test_mapping'
+p19
+I00
+sS'test_finder'
+p20
+S'MODULE'
+p21
+sS'build_targets'
+p22
+g0
+((lp23
+VMODULES-IN-platform_testing-tests-example-native
+p24
+atp25
+Rp26
+sS'host'
+p27
+I00
+sS'test_name'
+p28
+S'hello_world_test'
+p29
+sS'suite'
+p30
+NsS'data'
+p31
+(dp32
+S'rel_config'
+p33
+Vplatform_testing/tests/example/native/AndroidTest.xml
+p34
+sS'filter'
+p35
+c__builtin__
+frozenset
+p36
+((lp37
+tp38
+Rp39
+ssbatp40
+Rp41
+.
diff --git a/common_util/com/android/tradefed/util/RunUtil.java b/common_util/com/android/tradefed/util/RunUtil.java
index fd27843..a2aef47 100644
--- a/common_util/com/android/tradefed/util/RunUtil.java
+++ b/common_util/com/android/tradefed/util/RunUtil.java
@@ -299,7 +299,7 @@
@Override
public Process runCmdInBackground(final String... command) throws IOException {
final String fullCmd = Arrays.toString(command);
- CLog.v("Running %s", fullCmd);
+ CLog.v("Running in background: %s", fullCmd);
return createProcessBuilder(command).start();
}
@@ -308,7 +308,7 @@
*/
@Override
public Process runCmdInBackground(final List<String> command) throws IOException {
- CLog.v("Running %s", command);
+ CLog.v("Running in background: %s", command);
return createProcessBuilder(command).start();
}
@@ -318,7 +318,7 @@
@Override
public Process runCmdInBackground(List<String> command, OutputStream output)
throws IOException {
- CLog.v("Running %s", command);
+ CLog.v("Running in background: %s", command);
Process process = createProcessBuilder(command).start();
inheritIO(
process.getInputStream(),
diff --git a/common_util/com/android/tradefed/util/zip/CentralDirectoryInfo.java b/common_util/com/android/tradefed/util/zip/CentralDirectoryInfo.java
index 49ec5a7..55676b9 100644
--- a/common_util/com/android/tradefed/util/zip/CentralDirectoryInfo.java
+++ b/common_util/com/android/tradefed/util/zip/CentralDirectoryInfo.java
@@ -220,4 +220,41 @@
mExtraFieldLength = ByteArrayUtil.getInt(data, startOffset + 30, 2);
mFileCommentLength = ByteArrayUtil.getInt(data, startOffset + 32, 2);
}
+
+ @Override
+ public boolean equals(Object o) {
+ return this.toString().equals(o.toString());
+ }
+
+ @Override
+ public int hashCode() {
+ return this.toString().hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "Compression Method: %d\n"
+ + "Crc: %d\n"
+ + "Compressed Size: %d\n"
+ + "Uncompressed Size: %d\n"
+ + "Local Header Offset: %d\n"
+ + "Internal File Attributes: %d\n"
+ + "External File Attributes: %d\n"
+ + "File Name: %s\n"
+ + "File Name Length: %d\n"
+ + "Extra Field Length: %d\n"
+ + "File Comment Length: %d",
+ mCompressionMethod,
+ mCrc,
+ mCompressedSize,
+ mUncompressedSize,
+ mLocalHeaderOffset,
+ mInternalFileAttributes,
+ mExternalFileAttributes,
+ mFileName,
+ mFileNameLength,
+ mExtraFieldLength,
+ mFileCommentLength);
+ }
}
diff --git a/common_util/com/android/tradefed/util/zip/MergedZipEntryCollection.java b/common_util/com/android/tradefed/util/zip/MergedZipEntryCollection.java
index ed1e8fc..0527f47 100644
--- a/common_util/com/android/tradefed/util/zip/MergedZipEntryCollection.java
+++ b/common_util/com/android/tradefed/util/zip/MergedZipEntryCollection.java
@@ -62,7 +62,7 @@
* @return a list of {@link MergedZipEntryCollection}, each contains a list of
* {@link CentralDirectoryInfo} that are stored closely inside the zip file.
*/
- public static List<MergedZipEntryCollection> CreateCollections(
+ public static List<MergedZipEntryCollection> createCollections(
List<CentralDirectoryInfo> zipEntries) {
if (zipEntries.size() == 0) {
return new ArrayList<MergedZipEntryCollection>();
diff --git a/device_build_interfaces/README.md b/device_build_interfaces/README.md
new file mode 100644
index 0000000..b3508a4
--- /dev/null
+++ b/device_build_interfaces/README.md
@@ -0,0 +1,7 @@
+# Trade Federation Device and Build Interfaces Component
+
+A Tradefed component for our Device and Build interfaces
+and base classes.
+
+This directory should only contain classes related to our
+device and build handling.
diff --git a/device_build_interfaces/com/android/tradefed/build/IBuildInfo.java b/device_build_interfaces/com/android/tradefed/build/IBuildInfo.java
index 919bc31..8fd2026 100644
--- a/device_build_interfaces/com/android/tradefed/build/IBuildInfo.java
+++ b/device_build_interfaces/com/android/tradefed/build/IBuildInfo.java
@@ -305,4 +305,18 @@
public default Set<File> getRemoteFiles() {
return null;
}
+
+ /**
+ * Stage a file that's part of remote files in the build info's root dir.
+ *
+ * <p>TODO(b/138416078): Remove this interface and its caller when modules required by a test
+ * can be properly built output to the test module's directory itself.
+ *
+ * @param fileName Name of the file to be located in remote files.
+ * @param workingDir a {@link File} object of the directory to stage the file.
+ * @return the {@link File} object of the file staged in local workingDir.
+ */
+ public default File stageRemoteFile(String fileName, File workingDir) {
+ return null;
+ }
}
diff --git a/device_build_interfaces/com/android/tradefed/device/INativeDevice.java b/device_build_interfaces/com/android/tradefed/device/INativeDevice.java
index df7420a..5ea6f47 100644
--- a/device_build_interfaces/com/android/tradefed/device/INativeDevice.java
+++ b/device_build_interfaces/com/android/tradefed/device/INativeDevice.java
@@ -1332,6 +1332,7 @@
* Helper method runs the "pidof" and "stat" command and returns {@link ProcessInfo} object with
* PID and process start time of the given process.
*
+ * @param processName the proces name String.
* @return ProcessInfo of given processName
*/
public ProcessInfo getProcessByName(String processName) throws DeviceNotAvailableException;
@@ -1345,20 +1346,52 @@
/**
* Helper method collects the boot history map with boot time and boot reason since the given
- * time in second since epoch from device. The current device utcEpochTime in second can be
- * obtained by adb shell command "date +%s". Method {@link #getDeviceDate} uses adb shell
- * command "date +%s" to get device UTC Time since Epoch and return the value in scale of
- * millisecond.
+ * time since epoch from device and the time unit specified. The current device utcEpochTime in
+ * Millisecond can be obtained by method {@link #getDeviceDate}.
*
+ * @param utcEpochTime the device time since Epoch.
+ * @param timeUnit the time unit <code>TimeUnit</code>.
* @return Map of boot time (UTC time in second since Epoch) and boot reason
*/
- public Map<Long, String> getBootHistorySince(long utcEpochTime)
+ public Map<Long, String> getBootHistorySince(long utcEpochTime, TimeUnit timeUnit)
throws DeviceNotAvailableException;
- /** Returns the pid of the service or null if something went wrong. */
+ /**
+ * Returns the pid of the service or null if something went wrong.
+ *
+ * @param process The proces name String.
+ */
public String getProcessPid(String process) throws DeviceNotAvailableException;
/**
+ * Helper method to check whether device soft-restarted since the UTC time since epoch from
+ * device and its {@link TimeUnit}. Soft-Restart refers to system_server restarted outside of a
+ * device hard reboot (for example: requested reboot). The current device utcEpochTime in
+ * Milliseccond can be obtained by method {@link #getDeviceDate}.
+ *
+ * @param utcEpochTime the device time in second since epoch.
+ * @param timeUnit the time unit <code>TimeUnit</code> for the given utcEpochTime.
+ * @return {@code true} if device soft-restarted
+ * @throws RuntimeException if device has abnormal boot reason
+ * @throws DeviceNotAvailableException
+ */
+ public boolean deviceSoftRestartedSince(long utcEpochTime, TimeUnit timeUnit)
+ throws DeviceNotAvailableException;
+
+ /**
+ * Helper method to check if device soft-restarted by comparing current system_server with
+ * previous system_server {@link ProcessInfo}. Use {@link #getProcessByName} to get {@link
+ * ProcessInfo}.
+ *
+ * @param prevSystemServerProcess the previous system_server process {@link ProcessInfo}.
+ * @return {@code true} if device soft-restarted
+ * @throws RuntimeException if device has abnormal boot reason
+ * @throws DeviceNotAvailableException
+ */
+ public boolean deviceSoftRestarted(ProcessInfo prevSystemServerProcess)
+ throws DeviceNotAvailableException;
+
+ /**
* Log a message in the logcat of the device. This is a safe call that will not throw even if
* the logging fails.
*
diff --git a/device_build_interfaces/com/android/tradefed/device/TestDeviceOptions.java b/device_build_interfaces/com/android/tradefed/device/TestDeviceOptions.java
index eb62635..d8c4cf0 100644
--- a/device_build_interfaces/com/android/tradefed/device/TestDeviceOptions.java
+++ b/device_build_interfaces/com/android/tradefed/device/TestDeviceOptions.java
@@ -260,6 +260,18 @@
// END ====================== Options Related to Virtual Devices ======================
+ // Option related to Remote Device only
+
+ public static final String REMOTE_TF_VERSION_OPTION = "remote-tf-version";
+
+ @Option(
+ name = REMOTE_TF_VERSION_OPTION,
+ description =
+ "The TF to push to the remote VM to drive the invocation. If null, current TF "
+ + "will be pushed."
+ )
+ private File mRemoteTFVersion = null;
+
/** Check whether adb root should be enabled on boot for this device */
public boolean isEnableAdbRoot() {
return mEnableAdbRoot;
@@ -637,6 +649,11 @@
return mCrosPassword;
}
+ /** The file pointing to the directory of the Tradefed version to be pushed to the remote. */
+ public File getRemoteTf() {
+ return mRemoteTFVersion;
+ }
+
public static String getCreateCommandByInstanceType(InstanceType type) {
switch (type) {
case CHEEPS:
diff --git a/invocation_interfaces/Android.bp b/invocation_interfaces/Android.bp
index 5208e51..b034c6c 100644
--- a/invocation_interfaces/Android.bp
+++ b/invocation_interfaces/Android.bp
@@ -19,6 +19,7 @@
"com/**/*.java",
],
libs: [
+ "ddmlib-prebuilt",
"guava",
"tradefed-common-util",
"tradefed-protos",
diff --git a/src/com/android/tradefed/result/ITestSummaryListener.java b/invocation_interfaces/com/android/tradefed/result/ITestSummaryListener.java
similarity index 100%
rename from src/com/android/tradefed/result/ITestSummaryListener.java
rename to invocation_interfaces/com/android/tradefed/result/ITestSummaryListener.java
diff --git a/src/com/android/tradefed/result/InvocationStatus.java b/invocation_interfaces/com/android/tradefed/result/InvocationStatus.java
similarity index 100%
rename from src/com/android/tradefed/result/InvocationStatus.java
rename to invocation_interfaces/com/android/tradefed/result/InvocationStatus.java
diff --git a/src/com/android/tradefed/result/InvocationSummaryHelper.java b/invocation_interfaces/com/android/tradefed/result/InvocationSummaryHelper.java
similarity index 100%
rename from src/com/android/tradefed/result/InvocationSummaryHelper.java
rename to invocation_interfaces/com/android/tradefed/result/InvocationSummaryHelper.java
diff --git a/src/com/android/tradefed/result/TestResult.java b/invocation_interfaces/com/android/tradefed/result/TestResult.java
similarity index 93%
rename from src/com/android/tradefed/result/TestResult.java
rename to invocation_interfaces/com/android/tradefed/result/TestResult.java
index 3d33df3..0a0141f 100644
--- a/src/com/android/tradefed/result/TestResult.java
+++ b/invocation_interfaces/com/android/tradefed/result/TestResult.java
@@ -16,8 +16,9 @@
package com.android.tradefed.result;
import com.android.ddmlib.testrunner.TestResult.TestStatus;
+import com.android.tradefed.metrics.proto.MetricMeasurement.Measurements;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
-import com.android.tradefed.testtype.retry.MergeStrategy;
+import com.android.tradefed.retry.MergeStrategy;
import com.google.common.base.Joiner;
@@ -30,6 +31,8 @@
/** Container for a result of a single test. */
public class TestResult {
+ // Key that mark that an aggregation is hiding a failure.
+ public static final String IS_FLAKY = "is_flaky";
private TestStatus mStatus;
private String mStackTrace;
@@ -157,6 +160,14 @@
return a == b || (a != null && a.equals(b));
}
+ private void markFlaky() {
+ mProtoMetrics.put(
+ IS_FLAKY,
+ Metric.newBuilder()
+ .setMeasurements(Measurements.newBuilder().setSingleString("true").build())
+ .build());
+ }
+
/**
* Merge the attempts for a same test case based on the merging strategy.
*
@@ -222,6 +233,9 @@
// We prioritize passing the test due to the merging strategy.
if (pass > 0) {
mergedResult.setStatus(TestStatus.PASSED);
+ if (fail > 0) {
+ mergedResult.markFlaky();
+ }
} else if (fail == 0) {
if (ignored > 0) {
mergedResult.setStatus(TestStatus.IGNORED);
diff --git a/src/com/android/tradefed/result/TestRunResult.java b/invocation_interfaces/com/android/tradefed/result/TestRunResult.java
similarity index 99%
rename from src/com/android/tradefed/result/TestRunResult.java
rename to invocation_interfaces/com/android/tradefed/result/TestRunResult.java
index 6be64fc..6885abe 100644
--- a/src/com/android/tradefed/result/TestRunResult.java
+++ b/invocation_interfaces/com/android/tradefed/result/TestRunResult.java
@@ -18,7 +18,7 @@
import com.android.ddmlib.testrunner.TestResult.TestStatus;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
-import com.android.tradefed.testtype.retry.MergeStrategy;
+import com.android.tradefed.retry.MergeStrategy;
import com.android.tradefed.util.proto.TfMetricProtoUtil;
import com.google.common.base.Joiner;
diff --git a/src/com/android/tradefed/testtype/retry/MergeStrategy.java b/invocation_interfaces/com/android/tradefed/retry/MergeStrategy.java
similarity index 97%
rename from src/com/android/tradefed/testtype/retry/MergeStrategy.java
rename to invocation_interfaces/com/android/tradefed/retry/MergeStrategy.java
index d8c6869..37aa2da 100644
--- a/src/com/android/tradefed/testtype/retry/MergeStrategy.java
+++ b/invocation_interfaces/com/android/tradefed/retry/MergeStrategy.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.tradefed.testtype.retry;
+package com.android.tradefed.retry;
/** Describes how the results should be aggregated when multiple attempts are present. */
public enum MergeStrategy {
diff --git a/src/com/android/tradefed/testtype/retry/RetryStrategy.java b/invocation_interfaces/com/android/tradefed/retry/RetryStrategy.java
similarity index 96%
rename from src/com/android/tradefed/testtype/retry/RetryStrategy.java
rename to invocation_interfaces/com/android/tradefed/retry/RetryStrategy.java
index dcce258..2b0beda 100644
--- a/src/com/android/tradefed/testtype/retry/RetryStrategy.java
+++ b/invocation_interfaces/com/android/tradefed/retry/RetryStrategy.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.tradefed.testtype.retry;
+package com.android.tradefed.retry;
/** The Retry Strategy to be used when re-running some tests. */
public enum RetryStrategy {
diff --git a/res/config/instrumentations.xml b/res/config/instrumentations.xml
index 88c7d50..7df6186 100644
--- a/res/config/instrumentations.xml
+++ b/res/config/instrumentations.xml
@@ -14,11 +14,16 @@
limitations under the License.
-->
<configuration description="Runs all the Android instrumentation tests on an existing device">
+ <option name="compress-files" value="false" />
+
+ <logger class="com.android.tradefed.log.FileLogger">
+ <option name="log-level" value="verbose" />
+ <option name="log-level-display" value="debug" />
+ </logger>
<target_preparer class="com.android.tradefed.targetprep.InstallApkSetup">
<!-- Use "apk-path" option to specify which apk to install -->
</target_preparer>
<test class="com.android.tradefed.testtype.InstalledInstrumentationsTest" />
-
</configuration>
diff --git a/src/com/android/tradefed/build/BuildInfo.java b/src/com/android/tradefed/build/BuildInfo.java
index f1a92fd..414abe2 100644
--- a/src/com/android/tradefed/build/BuildInfo.java
+++ b/src/com/android/tradefed/build/BuildInfo.java
@@ -19,6 +19,8 @@
import com.android.tradefed.build.proto.BuildInformation;
import com.android.tradefed.build.proto.BuildInformation.BuildFile;
import com.android.tradefed.build.proto.BuildInformation.KeyBuildFilePair;
+import com.android.tradefed.config.ConfigurationException;
+import com.android.tradefed.config.DynamicRemoteFileResolver;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.util.FileUtil;
@@ -698,7 +700,6 @@
return null;
}
-
/** {@inheritDoc} */
@Override
public Set<File> getRemoteFiles() {
@@ -711,4 +712,25 @@
}
return remoteFiles;
}
+
+ /** {@inheritDoc} */
+ @Override
+ public File stageRemoteFile(String fileName, File workingDir) {
+ List<String> includeFilters = Arrays.asList(String.format("/%s$", fileName));
+ for (File file : getRemoteFiles()) {
+ try {
+ new DynamicRemoteFileResolver()
+ .resolvePartialDownloadZip(
+ workingDir, file.toString(), includeFilters, null);
+ } catch (ConfigurationException e) {
+ throw new RuntimeException(e);
+ }
+
+ File stagedFile = FileUtil.findFile(workingDir, fileName);
+ if (stagedFile != null) {
+ return stagedFile;
+ }
+ }
+ return null;
+ }
}
diff --git a/src/com/android/tradefed/build/DeviceBuildDescriptor.java b/src/com/android/tradefed/build/DeviceBuildDescriptor.java
index 880e546..088114a 100644
--- a/src/com/android/tradefed/build/DeviceBuildDescriptor.java
+++ b/src/com/android/tradefed/build/DeviceBuildDescriptor.java
@@ -16,6 +16,7 @@
package com.android.tradefed.build;
import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.DeviceProperties;
import com.android.tradefed.device.ITestDevice;
/**
@@ -99,8 +100,11 @@
throws DeviceNotAvailableException {
b.addBuildAttribute(DEVICE_BUILD_ID, device.getBuildId());
b.addBuildAttribute(DEVICE_BUILD_ALIAS, device.getBuildAlias());
- String buildFlavor = String.format("%s-%s", device.getProperty("ro.product.name"),
- device.getProperty("ro.build.type"));
+ String buildFlavor =
+ String.format(
+ "%s-%s",
+ device.getProperty(DeviceProperties.PRODUCT),
+ device.getProperty(DeviceProperties.BUILD_TYPE));
b.addBuildAttribute(DEVICE_BUILD_FLAVOR, buildFlavor);
b.addBuildAttribute(DEVICE_DESC, generateDeviceDesc(device));
b.addBuildAttribute(DEVICE_PRODUCT, generateDeviceProduct(device));
@@ -119,12 +123,15 @@
public static String generateDeviceDesc(ITestDevice device)
throws DeviceNotAvailableException {
// brand is typically all lower case. Capitalize it
- String brand = device.getProperty("ro.product.brand");
+ String brand = device.getProperty(DeviceProperties.BRAND);
if (brand.length() > 1) {
brand = String.format("%s%s", brand.substring(0, 1).toUpperCase(), brand.substring(1));
}
- return String.format("%s %s %s", brand, device.getProperty("ro.product.model"),
- device.getProperty("ro.build.version.release"));
+ return String.format(
+ "%s %s %s",
+ brand,
+ device.getProperty("ro.product.model"),
+ device.getProperty(DeviceProperties.RELEASE_VERSION));
}
/**
diff --git a/src/com/android/tradefed/build/FileDownloadCache.java b/src/com/android/tradefed/build/FileDownloadCache.java
index 2f06bcd..88fd416 100644
--- a/src/com/android/tradefed/build/FileDownloadCache.java
+++ b/src/com/android/tradefed/build/FileDownloadCache.java
@@ -108,22 +108,30 @@
mCacheRoot.getAbsolutePath()));
}
} else {
- Log.d(LOG_TAG, String.format("Building file cache from contents at %s",
- mCacheRoot.getAbsolutePath()));
- // create an unsorted list of all the files in mCacheRoot. Need to create list first
- // rather than inserting in Map directly because Maps cannot be sorted
- List<FilePair> cacheEntryList = new LinkedList<FilePair>();
- addFiles(mCacheRoot, new Stack<String>(), cacheEntryList);
- // now sort them based on file timestamp, to get them in LRU order
- Collections.sort(cacheEntryList, new FileTimeComparator());
- // now insert them into the map
- for (FilePair cacheEntry : cacheEntryList) {
- mCacheMap.put(cacheEntry.mRelPath, cacheEntry.mFile);
- mCurrentCacheSize += cacheEntry.mFile.length();
- }
- // this would be an unusual situation, but check if current cache is already too big
- if (mCurrentCacheSize > getMaxFileCacheSize()) {
- incrementAndAdjustCache(0);
+ mCacheMapLock.lock();
+ try {
+ Log.d(
+ LOG_TAG,
+ String.format(
+ "Building file cache from contents at %s",
+ mCacheRoot.getAbsolutePath()));
+ // create an unsorted list of all the files in mCacheRoot. Need to create list first
+ // rather than inserting in Map directly because Maps cannot be sorted
+ List<FilePair> cacheEntryList = new LinkedList<FilePair>();
+ addFiles(mCacheRoot, new Stack<String>(), cacheEntryList);
+ // now sort them based on file timestamp, to get them in LRU order
+ Collections.sort(cacheEntryList, new FileTimeComparator());
+ // now insert them into the map
+ for (FilePair cacheEntry : cacheEntryList) {
+ mCacheMap.put(cacheEntry.mRelPath, cacheEntry.mFile);
+ mCurrentCacheSize += cacheEntry.mFile.length();
+ }
+ // this would be an unusual situation, but check if current cache is already too big
+ if (mCurrentCacheSize > getMaxFileCacheSize()) {
+ incrementAndAdjustCache(0);
+ }
+ } finally {
+ mCacheMapLock.unlock();
}
}
}
diff --git a/src/com/android/tradefed/build/FileDownloadCacheWrapper.java b/src/com/android/tradefed/build/FileDownloadCacheWrapper.java
index ecf3f44..0860bfa 100644
--- a/src/com/android/tradefed/build/FileDownloadCacheWrapper.java
+++ b/src/com/android/tradefed/build/FileDownloadCacheWrapper.java
@@ -59,13 +59,13 @@
/** {@inheritDoc} */
@Override
- public void downloadPartialFiles(
+ public void downloadZippedFiles(
File destDir,
String remoteFilePath,
List<String> includeFilters,
List<String> excludeFilters)
throws BuildRetrievalError, IOException {
- mDelegateDownloader.downloadPartialFiles(
+ mDelegateDownloader.downloadZippedFiles(
destDir, remoteFilePath, includeFilters, excludeFilters);
}
}
diff --git a/src/com/android/tradefed/build/IFileDownloader.java b/src/com/android/tradefed/build/IFileDownloader.java
index b3196dd..0ae0168 100644
--- a/src/com/android/tradefed/build/IFileDownloader.java
+++ b/src/com/android/tradefed/build/IFileDownloader.java
@@ -46,6 +46,24 @@
public void downloadFile(String relativeRemotePath, File destFile) throws BuildRetrievalError;
/**
+ * Alternate form of {@link #downloadFile(String, File)}, that allows caller to download a
+ * section of the file and save to a specific destination file.
+ *
+ * @param relativeRemotePath the remote path to the file to download, relative to an
+ * implementation-specific root.
+ * @param destFile the file to place the downloaded contents into. Should not exist.
+ * @param startOffset the start offset in the remote file.
+ * @param size the number of bytes to download from the remote file. Set it to a negative value
+ * to download the whole file.
+ * @throws BuildRetrievalError if file could not be downloaded
+ */
+ public default void downloadFile(
+ String remoteFilePath, File destFile, long startOffset, long size)
+ throws BuildRetrievalError {
+ throw new UnsupportedOperationException("Partial downloading is not implemented.");
+ }
+
+ /**
* Check local file's freshness. If local file is the same as remote file, then it's fresh. If
* not, local file is stale. This is mainly used for cache. The default implementation will
* always return true, so if the file is immutable it will never need to check freshness.
@@ -73,7 +91,7 @@
* @param excludeFilters a list of filters to skip downloading matching files.
* @throws BuildRetrievalError if files could not be downloaded.
*/
- public default void downloadPartialFiles(
+ public default void downloadZippedFiles(
File destDir,
String remoteFilePath,
List<String> includeFilters,
diff --git a/src/com/android/tradefed/build/OtaZipfileBuildProvider.java b/src/com/android/tradefed/build/OtaZipfileBuildProvider.java
index 14617c7..8ac1fa9 100644
--- a/src/com/android/tradefed/build/OtaZipfileBuildProvider.java
+++ b/src/com/android/tradefed/build/OtaZipfileBuildProvider.java
@@ -18,6 +18,7 @@
import com.android.tradefed.config.Option;
import com.android.tradefed.config.Option.Importance;
+import com.android.tradefed.device.DeviceProperties;
import com.android.tradefed.util.ZipUtil;
import java.io.BufferedReader;
@@ -49,7 +50,7 @@
throw new BuildRetrievalError(
"Error processing build.prop contents from file: " + getOtaPath(), e);
}
- String bid = buildProp.getProperty("ro.build.version.incremental");
+ String bid = buildProp.getProperty(DeviceProperties.BUILD_ID);
IDeviceBuildInfo buildInfo = new DeviceBuildInfo(bid, bid);
buildInfo.setOtaPackageFile(new File(getOtaPath()), bid);
return buildInfo;
diff --git a/src/com/android/tradefed/command/CommandOptions.java b/src/com/android/tradefed/command/CommandOptions.java
index 64ef014..94bc35f 100644
--- a/src/com/android/tradefed/command/CommandOptions.java
+++ b/src/com/android/tradefed/command/CommandOptions.java
@@ -22,7 +22,7 @@
import com.android.tradefed.config.OptionUpdateRule;
import com.android.tradefed.device.metric.AutoLogCollector;
import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.testtype.retry.RetryStrategy;
+import com.android.tradefed.retry.RetryStrategy;
import com.android.tradefed.util.UniqueMultiMap;
import java.util.LinkedHashSet;
@@ -201,6 +201,7 @@
private String mHostLogSuffix = null;
// [Options related to auto-retry]
+ @Deprecated
@Option(
name = "max-testcase-run-count",
description =
@@ -209,6 +210,7 @@
)
private int mMaxRunLimit = 1;
+ @Deprecated
@Option(
name = "retry-strategy",
description =
@@ -217,6 +219,7 @@
)
private RetryStrategy mRetryStrategy = RetryStrategy.NO_RETRY;
+ @Deprecated
@Option(
name = "auto-retry",
description =
@@ -558,28 +561,4 @@
public boolean shouldReportModuleProgression() {
return mReportModuleProgression;
}
-
- /** {@inheritDoc} */
- @Override
- public int getMaxRetryCount() {
- return mMaxRunLimit;
- }
-
- /** {@inheritDoc} */
- @Override
- public void setMaxRetryCount(int maxRetryCount) {
- mMaxRunLimit = maxRetryCount;
- }
-
- /** {@inheritDoc} */
- @Override
- public RetryStrategy getRetryStrategy() {
- return mRetryStrategy;
- }
-
- /** {@inheritDoc} */
- @Override
- public boolean isAutoRetryEnabled() {
- return mEnableAutoRetry;
- }
}
diff --git a/src/com/android/tradefed/command/ICommandOptions.java b/src/com/android/tradefed/command/ICommandOptions.java
index 9e5061d..a6682ae 100644
--- a/src/com/android/tradefed/command/ICommandOptions.java
+++ b/src/com/android/tradefed/command/ICommandOptions.java
@@ -17,7 +17,6 @@
package com.android.tradefed.command;
import com.android.tradefed.device.metric.AutoLogCollector;
-import com.android.tradefed.testtype.retry.RetryStrategy;
import com.android.tradefed.util.UniqueMultiMap;
import java.util.Set;
@@ -200,16 +199,4 @@
/** Whether or not to report progression of remote invocation at module level. */
public boolean shouldReportModuleProgression();
-
- /** The maximum number of attempts during auto-retry. */
- public int getMaxRetryCount();
-
- /** Set the max retry count allowed during auto-retry. */
- public void setMaxRetryCount(int maxRetryCount);
-
- /** The {@link RetryStrategy} used during auto-retry. */
- public RetryStrategy getRetryStrategy();
-
- /** Whether or not to enable auto-retry. */
- public boolean isAutoRetryEnabled();
}
diff --git a/src/com/android/tradefed/config/Configuration.java b/src/com/android/tradefed/config/Configuration.java
index 242c566..a339f25 100644
--- a/src/com/android/tradefed/config/Configuration.java
+++ b/src/com/android/tradefed/config/Configuration.java
@@ -40,12 +40,16 @@
import com.android.tradefed.targetprep.multi.IMultiTargetPreparer;
import com.android.tradefed.testtype.IRemoteTest;
import com.android.tradefed.testtype.StubTest;
+import com.android.tradefed.testtype.retry.BaseRetryDecision;
+import com.android.tradefed.testtype.retry.IRetryDecision;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.IDisableable;
import com.android.tradefed.util.MultiMap;
import com.android.tradefed.util.QuotationAwareTokenizer;
+import com.android.tradefed.util.SystemUtil;
import com.android.tradefed.util.keystore.IKeyStoreClient;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import org.json.JSONArray;
@@ -98,6 +102,7 @@
public static final String METRIC_POST_PROCESSOR_TYPE_NAME = "metric_post_processor";
public static final String SANDBOX_TYPE_NAME = "sandbox";
public static final String SANBOX_OPTIONS_TYPE_NAME = "sandbox_options";
+ public static final String RETRY_DECISION_TYPE_NAME = "retry_decision";
private static Map<String, ObjTypeInfo> sObjTypeMap = null;
private static Set<String> sMultiDeviceSupportedTag = null;
@@ -185,6 +190,7 @@
METRIC_POST_PROCESSOR_TYPE_NAME,
new ObjTypeInfo(BasePostProcessor.class, true));
sObjTypeMap.put(SANBOX_OPTIONS_TYPE_NAME, new ObjTypeInfo(SandboxOptions.class, false));
+ sObjTypeMap.put(RETRY_DECISION_TYPE_NAME, new ObjTypeInfo(IRetryDecision.class, false));
}
return sObjTypeMap;
}
@@ -240,6 +246,7 @@
setDeviceMetricCollectors(new ArrayList<>());
setPostProcessors(new ArrayList<>());
setConfigurationObjectNoThrow(SANBOX_OPTIONS_TYPE_NAME, new SandboxOptions());
+ setConfigurationObjectNoThrow(RETRY_DECISION_TYPE_NAME, new BaseRetryDecision());
}
/**
@@ -349,6 +356,12 @@
return (ILogSaver) getConfigurationObject(LOG_SAVER_TYPE_NAME);
}
+ /** {@inheritDoc} */
+ @Override
+ public IRetryDecision getRetryDecision() {
+ return (IRetryDecision) getConfigurationObject(RETRY_DECISION_TYPE_NAME);
+ }
+
/**
* {@inheritDoc}
*/
@@ -1299,10 +1312,14 @@
/** {@inheritDoc} */
@Override
public void resolveDynamicOptions() throws ConfigurationException {
- ICommandOptions options = getCommandOptions();
- if (options.getShardCount() != null && options.getShardIndex() == null) {
- CLog.w("Skipping download due to local sharding detected.");
- return;
+ // Resolve regardless of sharding if we are in remote environment because we know that's
+ // where the execution will occur.
+ if (!isRemoteEnvironment()) {
+ ICommandOptions options = getCommandOptions();
+ if (options.getShardCount() != null && options.getShardIndex() == null) {
+ CLog.w("Skipping download due to local sharding detected.");
+ return;
+ }
}
ArgsOptionParser argsParser = new ArgsOptionParser(getAllConfigurationObjects());
@@ -1311,6 +1328,12 @@
mRemoteFiles.addAll(argsParser.validateRemoteFilePath());
}
+ /** Returns whether or not the environment of TF is a remote invocation. */
+ @VisibleForTesting
+ protected boolean isRemoteEnvironment() {
+ return SystemUtil.isRemoteEnvironment();
+ }
+
/** {@inheritDoc} */
@Override
public void cleanDynamicOptionFiles() {
@@ -1528,6 +1551,13 @@
excludeFilters,
printDeprecatedOptions,
printUnchangedOptions);
+ ConfigurationUtil.dumpClassToXml(
+ serializer,
+ RETRY_DECISION_TYPE_NAME,
+ getRetryDecision(),
+ excludeFilters,
+ printDeprecatedOptions,
+ printUnchangedOptions);
serializer.endTag(null, ConfigurationUtil.CONFIGURATION_NAME);
serializer.endDocument();
diff --git a/src/com/android/tradefed/config/IConfiguration.java b/src/com/android/tradefed/config/IConfiguration.java
index 53751cc..1da3daf 100644
--- a/src/com/android/tradefed/config/IConfiguration.java
+++ b/src/com/android/tradefed/config/IConfiguration.java
@@ -31,6 +31,7 @@
import com.android.tradefed.targetprep.ITargetPreparer;
import com.android.tradefed.targetprep.multi.IMultiTargetPreparer;
import com.android.tradefed.testtype.IRemoteTest;
+import com.android.tradefed.testtype.retry.IRetryDecision;
import com.android.tradefed.util.keystore.IKeyStoreClient;
import org.json.JSONArray;
@@ -111,6 +112,9 @@
*/
public ILogSaver getLogSaver();
+ /** Returns the {@link IRetryDecision} used for the invocation. */
+ public IRetryDecision getRetryDecision();
+
/**
* Gets the {@link IMultiTargetPreparer}s from the configuration.
*
diff --git a/src/com/android/tradefed/config/OptionCopier.java b/src/com/android/tradefed/config/OptionCopier.java
index 3968371..5f339df 100644
--- a/src/com/android/tradefed/config/OptionCopier.java
+++ b/src/com/android/tradefed/config/OptionCopier.java
@@ -64,6 +64,43 @@
}
/**
+ * Copy the given option from {@link Option} fields in <var>origObject</var> to
+ * <var>destObject</var>
+ *
+ * @param origObject the {@link Object} to copy from
+ * @param destObject the {@link Object} tp copy to
+ * @param optionName the name of the option to copy.
+ * @throws ConfigurationException if options failed to copy
+ */
+ public static void copyOptions(Object origObject, Object destObject, String optionName)
+ throws ConfigurationException {
+ Collection<Field> origFields = OptionSetter.getOptionFieldsForClass(origObject.getClass());
+ Map<String, Field> destFieldMap = getFieldOptionMap(destObject);
+ for (Field origField : origFields) {
+ final Option option = origField.getAnnotation(Option.class);
+ if (option.name().equals(optionName)) {
+ Field destField = destFieldMap.remove(option.name());
+ if (destField != null) {
+ Object origValue = OptionSetter.getFieldValue(origField, origObject);
+ OptionSetter.setFieldValue(option.name(), destObject, destField, origValue);
+ }
+ }
+ }
+ }
+
+ /**
+ * Identical to {@link #copyOptions(Object, Object, String)} but will log instead of throw if
+ * exception occurs.
+ */
+ public static void copyOptionsNoThrow(Object source, Object dest, String optionName) {
+ try {
+ copyOptions(source, dest, optionName);
+ } catch (ConfigurationException e) {
+ CLog.e(e);
+ }
+ }
+
+ /**
* Build a map of {@link Option#name()} to {@link Field} for given {@link Object}.
*
* @param destObject
diff --git a/src/com/android/tradefed/device/DeviceProperties.java b/src/com/android/tradefed/device/DeviceProperties.java
index b4fc512..a79c7aa 100644
--- a/src/com/android/tradefed/device/DeviceProperties.java
+++ b/src/com/android/tradefed/device/DeviceProperties.java
@@ -40,4 +40,18 @@
public static final String RELEASE_VERSION = "ro.build.version.release";
/** property name for device boot reason history */
public static final String BOOT_REASON_HISTORY = "persist.sys.boot.reason.history";
+ /** property name for the type of build */
+ public static final String BUILD_TYPE = "ro.build.type";
+ /** property name for the alias of the build name */
+ public static final String BUILD_ALIAS = "ro.build.id";
+ /** property name for the flavor of the device build */
+ public static final String BUILD_FLAVOR = "ro.build.flavor";
+ /** property name for whether or not the device is headless */
+ public static final String BUILD_HEADLESS = "ro.build.headless";
+ /** property name for the build id of the device */
+ public static final String BUILD_ID = "ro.build.version.incremental";
+ /** property name for the build codename of the device. Example: Q */
+ public static final String BUILD_CODENAME = "ro.build.version.codename";
+ /** property name for the build tags of the device */
+ public static final String BUILD_TAGS = "ro.build.tags";
}
diff --git a/src/com/android/tradefed/device/ManagedTestDeviceFactory.java b/src/com/android/tradefed/device/ManagedTestDeviceFactory.java
index 284c53e..faaf079 100644
--- a/src/com/android/tradefed/device/ManagedTestDeviceFactory.java
+++ b/src/com/android/tradefed/device/ManagedTestDeviceFactory.java
@@ -27,10 +27,10 @@
import com.android.tradefed.device.cloud.NestedRemoteDevice;
import com.android.tradefed.device.cloud.RemoteAndroidVirtualDevice;
import com.android.tradefed.device.cloud.VmRemoteDevice;
-import com.android.tradefed.invoker.RemoteInvocationExecution;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.util.IRunUtil;
import com.android.tradefed.util.RunUtil;
+import com.android.tradefed.util.SystemUtil;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
@@ -190,10 +190,7 @@
*/
@VisibleForTesting
protected boolean isRemoteEnvironment() {
- if ("1".equals(System.getenv(RemoteInvocationExecution.REMOTE_VM_VARIABLE))) {
- return true;
- }
- return false;
+ return SystemUtil.isRemoteEnvironment();
}
/** Create a {@link CollectingOutputReceiver}. */
diff --git a/src/com/android/tradefed/device/NativeDevice.java b/src/com/android/tradefed/device/NativeDevice.java
index 2ae8e43..ed01aec 100644
--- a/src/com/android/tradefed/device/NativeDevice.java
+++ b/src/com/android/tradefed/device/NativeDevice.java
@@ -148,6 +148,9 @@
/** Encrypting with wipe can take up to 20 minutes. */
private static final long ENCRYPTION_WIPE_TIMEOUT_MIN = 20;
+ /** The maximum system_server start delay in seconds after device boot up */
+ private static final int MAX_SYSTEM_SERVER_DELAY_AFTER_BOOT_UP_SEC = 10;
+
/** The time in ms to wait before starting logcat for a device */
private int mLogStartDelay = 5*1000;
@@ -156,16 +159,6 @@
/** The time in ms to wait for a recovery that we skip because of the NONE mode */
static final int NONE_RECOVERY_MODE_DELAY = 1000;
- static final String BUILD_ID_PROP = "ro.build.version.incremental";
- private static final String PRODUCT_NAME_PROP = "ro.product.name";
- private static final String BUILD_TYPE_PROP = "ro.build.type";
- private static final String BUILD_ALIAS_PROP = "ro.build.id";
- private static final String BUILD_FLAVOR = "ro.build.flavor";
- private static final String HEADLESS_PROP = "ro.build.headless";
- static final String BUILD_CODENAME_PROP = "ro.build.version.codename";
- static final String BUILD_TAGS = "ro.build.tags";
- private static final String PS_COMMAND = "ps -A || ps";
-
private static final String SIM_STATE_PROP = "gsm.sim.state";
private static final String SIM_OPERATOR_PROP = "gsm.operator.alpha";
@@ -619,7 +612,7 @@
*/
@Override
public String getBuildAlias() throws DeviceNotAvailableException {
- String alias = getProperty(BUILD_ALIAS_PROP);
+ String alias = getProperty(DeviceProperties.BUILD_ALIAS);
if (alias == null || alias.isEmpty()) {
return getBuildId();
}
@@ -631,7 +624,7 @@
*/
@Override
public String getBuildId() throws DeviceNotAvailableException {
- String bid = getProperty(BUILD_ID_PROP);
+ String bid = getProperty(DeviceProperties.BUILD_ID);
if (bid == null) {
CLog.w("Could not get device %s build id.", getSerialNumber());
return IBuildInfo.UNKNOWN_BUILD_ID;
@@ -644,12 +637,12 @@
*/
@Override
public String getBuildFlavor() throws DeviceNotAvailableException {
- String buildFlavor = getProperty(BUILD_FLAVOR);
+ String buildFlavor = getProperty(DeviceProperties.BUILD_FLAVOR);
if (buildFlavor != null && !buildFlavor.isEmpty()) {
return buildFlavor;
}
- String productName = getProperty(PRODUCT_NAME_PROP);
- String buildType = getProperty(BUILD_TYPE_PROP);
+ String productName = getProperty(DeviceProperties.PRODUCT);
+ String buildType = getProperty(DeviceProperties.BUILD_TYPE);
if (productName == null || buildType == null) {
CLog.w("Could not get device %s build flavor.", getSerialNumber());
return null;
@@ -3703,7 +3696,7 @@
public int getApiLevel() throws DeviceNotAvailableException {
int apiLevel = UNKNOWN_API_LEVEL;
try {
- String prop = getProperty("ro.build.version.sdk");
+ String prop = getProperty(DeviceProperties.SDK_VERSION);
apiLevel = Integer.parseInt(prop);
} catch (NumberFormatException nfe) {
// ignore, return unknown instead
@@ -3715,7 +3708,7 @@
@Override
public boolean checkApiLevelAgainstNextRelease(int strictMinLevel)
throws DeviceNotAvailableException {
- String codeName = getProperty(BUILD_CODENAME_PROP).trim();
+ String codeName = getProperty(DeviceProperties.BUILD_CODENAME).trim();
int apiLevel = getApiLevel() + ("REL".equals(codeName) ? 0 : 1);
if (strictMinLevel > apiLevel) {
return false;
@@ -3828,9 +3821,7 @@
executeShellCommand("TZ=UTC date -u " + dateString);
}
- /**
- * {@inheritDoc}
- */
+ /** {@inheritDoc} */
@Override
public long getDeviceDate() throws DeviceNotAvailableException {
String deviceTimeString = executeShellCommand("date +%s");
@@ -4084,7 +4075,7 @@
*/
@Override
public String getBuildSigningKeys() throws DeviceNotAvailableException {
- String buildTags = getProperty(BUILD_TAGS);
+ String buildTags = getProperty(DeviceProperties.BUILD_TAGS);
if (buildTags != null) {
String[] tags = buildTags.split(",");
for (String tag : tags) {
@@ -4203,7 +4194,7 @@
*/
@Override
public boolean isHeadless() throws DeviceNotAvailableException {
- if (getProperty(HEADLESS_PROP) != null) {
+ if (getProperty(DeviceProperties.BUILD_HEADLESS) != null) {
return true;
}
return false;
@@ -4239,8 +4230,8 @@
getAllocationState(),
getDisplayString(selector.getDeviceProductType(idevice)),
getDisplayString(selector.getDeviceProductVariant(idevice)),
- getDisplayString(idevice.getProperty("ro.build.version.sdk")),
- getDisplayString(idevice.getProperty("ro.build.id")),
+ getDisplayString(idevice.getProperty(DeviceProperties.SDK_VERSION)),
+ getDisplayString(idevice.getProperty(DeviceProperties.BUILD_ALIAS)),
getDisplayString(getBattery()),
getDeviceClass(),
getDisplayString(getMacAddress()),
@@ -4269,11 +4260,15 @@
if (pidString == null) {
return null;
}
+ long startTime = getProcessStartTimeByPid(pidString);
+ if (startTime == -1L) {
+ return null;
+ }
return new ProcessInfo(
getProcessUserByPid(pidString),
Integer.parseInt(pidString),
processName,
- getProcessStartTimeByPid(pidString));
+ startTime);
}
/** Return the process start time since epoch for the given pid string */
@@ -4305,7 +4300,9 @@
/** {@inheritDoc} */
@Override
public Map<Long, String> getBootHistory() throws DeviceNotAvailableException {
- String output = getProperty(DeviceProperties.BOOT_REASON_HISTORY);
+ // getProperty(DeviceProperties.BOOT_REASON_HISTORY) will not be able to handle boot history
+ // output format properly (tracked by b/139192891).
+ String output = executeShellCommand("getprop " + DeviceProperties.BOOT_REASON_HISTORY);
/* Sample output:
kernel_panic,1556587278
reboot,,1556238008
@@ -4331,17 +4328,131 @@
/** {@inheritDoc} */
@Override
- public Map<Long, String> getBootHistorySince(long utcEpochTime)
+ public Map<Long, String> getBootHistorySince(long utcEpochTime, TimeUnit timeUnit)
throws DeviceNotAvailableException {
+ long utcEpochTimeSec = TimeUnit.SECONDS.convert(utcEpochTime, timeUnit);
Map<Long, String> bootHistory = new LinkedHashMap<Long, String>();
for (Map.Entry<Long, String> entry : getBootHistory().entrySet()) {
- if (entry.getKey() > utcEpochTime) {
+ if (entry.getKey() > utcEpochTimeSec) {
bootHistory.put(entry.getKey(), entry.getValue());
}
}
return bootHistory;
}
+ private boolean hasNormalRebootSince(long utcEpochTime, TimeUnit timeUnit)
+ throws DeviceNotAvailableException {
+ Map<Long, String> bootHistory = getBootHistorySince(utcEpochTime, timeUnit);
+ if (bootHistory.isEmpty()) {
+ CLog.w("There is no reboot history since %s", utcEpochTime);
+ return false;
+ }
+
+ CLog.i(
+ "There are new boot history since %d. NewBootHistory = %s",
+ utcEpochTime, bootHistory);
+ // Check if there is reboot reason other than "reboot".
+ // Raise RuntimeException if there is abnormal reboot.
+ for (Map.Entry<Long, String> entry : bootHistory.entrySet()) {
+ if (!"reboot".equals(entry.getValue())) {
+ throw new RuntimeException(
+ String.format(
+ "Device %s has abnormal reboot reason %s at %d",
+ getSerialNumber(), entry.getValue(), entry.getKey()));
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Check current system process is restarted after last reboot
+ *
+ * @param the system_server {@link ProcessInfo}
+ * @return true if system_server process restarted after last reboot; false if not
+ * @throws DeviceNotAvailableException
+ */
+ private boolean checkSystemProcessRestartedAfterLastReboot(ProcessInfo systemServerProcess)
+ throws DeviceNotAvailableException {
+ // If time gap from last reboot to current system_server process start time is more than
+ // MAX_SYSTEM_SERVER_DELAY_AFTER_BOOT_UP seconds, we conclude the system_server restarted
+ // after boot up.
+ if (!hasNormalRebootSince(
+ systemServerProcess.getStartTime() - MAX_SYSTEM_SERVER_DELAY_AFTER_BOOT_UP_SEC,
+ TimeUnit.SECONDS)) {
+ CLog.i(
+ "Device last reboot is more than %s seconds away from current system_server "
+ + "process start time. The system_server process restarted after "
+ + "last boot up",
+ MAX_SYSTEM_SERVER_DELAY_AFTER_BOOT_UP_SEC);
+ return true;
+ } else {
+ // Current system_server start within MAX_SYSTEM_SERVER_DELAY_AFTER_BOOT_UP
+ // seconds after device last boot up
+ return false;
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean deviceSoftRestartedSince(long utcEpochTime, TimeUnit timeUnit)
+ throws DeviceNotAvailableException {
+ ProcessInfo currSystemServerProcess = getProcessByName("system_server");
+ if (currSystemServerProcess == null) {
+ CLog.i("The system_server process is not available on the device.");
+ return true;
+ }
+
+ // The system_server process started at or before utcEpochTime, there is no soft-restart
+ if (currSystemServerProcess.getStartTime()
+ <= TimeUnit.SECONDS.convert(utcEpochTime, timeUnit)) {
+ return false;
+ }
+
+ // The system_server process restarted after device utcEpochTime in second.
+ // Check if there is new reboot history, if no new reboot, device soft-restarted.
+ // If there is no normal reboot, soft-restart is detected.
+ if (!hasNormalRebootSince(utcEpochTime, timeUnit)) {
+ return true;
+ }
+
+ // There is new reboot since utcEpochTime. Check if system_server restarted after boot up.
+ return checkSystemProcessRestartedAfterLastReboot(currSystemServerProcess);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean deviceSoftRestarted(ProcessInfo prevSystemServerProcess)
+ throws DeviceNotAvailableException {
+ if (prevSystemServerProcess == null) {
+ CLog.i("The given system_server process is null. Abort deviceSoftRestarted check.");
+ return false;
+ }
+ ProcessInfo currSystemServerProcess = getProcessByName("system_server");
+ if (currSystemServerProcess == null) {
+ CLog.i("The system_server process is not available on the device.");
+ return true;
+ }
+
+
+ if (currSystemServerProcess.getPid() == prevSystemServerProcess.getPid()
+ && currSystemServerProcess.getStartTime()
+ == prevSystemServerProcess.getStartTime()) {
+ return false;
+ }
+
+ // The system_server process restarted.
+ // Check boot history with previous system_server start time.
+ // If there is no normal reboot, soft-restart is detected
+ if (!hasNormalRebootSince(prevSystemServerProcess.getStartTime(), TimeUnit.SECONDS)) {
+ return true;
+ }
+
+ // There is reboot since prevSystemServerProcess.getStartTime().
+ // Check if system_server restarted after boot up.
+ return checkSystemProcessRestartedAfterLastReboot(currSystemServerProcess);
+
+ }
+
/**
* Validates that the given input is a valid MAC address
*
diff --git a/src/com/android/tradefed/device/cloud/CommonLogRemoteFileUtil.java b/src/com/android/tradefed/device/cloud/CommonLogRemoteFileUtil.java
index 4b9d492..83b1c1f 100644
--- a/src/com/android/tradefed/device/cloud/CommonLogRemoteFileUtil.java
+++ b/src/com/android/tradefed/device/cloud/CommonLogRemoteFileUtil.java
@@ -19,10 +19,17 @@
import com.android.tradefed.device.TestDeviceOptions.InstanceType;
import com.android.tradefed.log.ITestLogger;
import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.FileInputStreamSource;
+import com.android.tradefed.result.InputStreamSource;
import com.android.tradefed.result.LogDataType;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.IRunUtil;
import com.android.tradefed.util.MultiMap;
+import com.android.tradefed.util.ZipUtil;
+import java.io.File;
+import java.io.IOException;
import java.util.List;
/**
@@ -35,6 +42,7 @@
public static final String NESTED_REMOTE_LOG_DIR = "/home/%s/cuttlefish_runtime/";
/** The directory where to find debug logs for an emulator instance. */
public static final String EMULATOR_REMOTE_LOG_DIR = "/home/%s/log/";
+ public static final String TOMBSTONES_ZIP_NAME = "tombstones-zip";
public static final MultiMap<InstanceType, KnownLogFileEntry> KNOWN_FILES_TO_FETCH =
new MultiMap<>();
@@ -53,6 +61,12 @@
InstanceType.CUTTLEFISH,
new KnownLogFileEntry(
NESTED_REMOTE_LOG_DIR + "cuttlefish_config.json", null, LogDataType.TEXT));
+ KNOWN_FILES_TO_FETCH.put(
+ InstanceType.CUTTLEFISH,
+ new KnownLogFileEntry(
+ NESTED_REMOTE_LOG_DIR + "launcher.log",
+ "cuttlefish_launcher.log",
+ LogDataType.TEXT));
// Emulator known files to collect
KNOWN_FILES_TO_FETCH.put(
InstanceType.EMULATOR,
@@ -128,6 +142,64 @@
}
/**
+ * Fetch and log the tombstones from the remote instance.
+ *
+ * @param testLogger The {@link ITestLogger} where to log the files.
+ * @param gceAvd The descriptor of the remote instance.
+ * @param options The {@link TestDeviceOptions} describing the device options
+ * @param runUtil A {@link IRunUtil} to execute commands.
+ */
+ public static void fetchTombstones(
+ ITestLogger testLogger,
+ GceAvdInfo gceAvd,
+ TestDeviceOptions options,
+ IRunUtil runUtil) {
+ if (gceAvd == null) {
+ CLog.e("GceAvdInfo was null, cannot collect remote files.");
+ return;
+ }
+ InstanceType type = options.getInstanceType();
+ if (!InstanceType.CUTTLEFISH.equals(type) && !InstanceType.REMOTE_AVD.equals(type)) {
+ return;
+ }
+ String pattern =
+ String.format(
+ "/home/%s/cuttlefish_runtime/tombstones/*", options.getInstanceUser());
+ CommandResult resultList =
+ GceManager.remoteSshCommandExecution(
+ gceAvd, options, runUtil, 60000, "ls", "-A1", pattern);
+ if (!CommandStatus.SUCCESS.equals(resultList.getStatus())) {
+ CLog.e("Failed to list the tombstones: %s", resultList.getStderr());
+ return;
+ }
+ if (resultList.getStdout().split("\n").length <= 0) {
+ return;
+ }
+ File tombstonesDir =
+ RemoteFileUtil.fetchRemoteDir(
+ gceAvd,
+ options,
+ runUtil,
+ 120000,
+ String.format(
+ "/home/%s/cuttlefish_runtime/tombstones",
+ options.getInstanceUser()));
+ if (tombstonesDir == null) {
+ CLog.w("No tombstones directory was pulled.");
+ return;
+ }
+ try {
+ File zipTombstones = ZipUtil.createZip(tombstonesDir);
+ try (InputStreamSource source = new FileInputStreamSource(zipTombstones, true)) {
+ testLogger.testLog(TOMBSTONES_ZIP_NAME, LogDataType.ZIP, source);
+ }
+ } catch (IOException e) {
+ CLog.e("Failed to zip the tombstones:");
+ CLog.e(e);
+ }
+ }
+
+ /**
* Captures a log from the remote destination.
*
* @param testLogger The {@link ITestLogger} where to log the files.
diff --git a/src/com/android/tradefed/device/cloud/GceManager.java b/src/com/android/tradefed/device/cloud/GceManager.java
index 508170f..b45b9fc 100644
--- a/src/com/android/tradefed/device/cloud/GceManager.java
+++ b/src/com/android/tradefed/device/cloud/GceManager.java
@@ -210,8 +210,18 @@
/** Build and return the command to launch GCE. Exposed for testing. */
protected List<String> buildGceCmd(File reportFile, IBuildInfo b) {
- List<String> gceArgs =
- ArrayUtil.list(getTestDeviceOptions().getAvdDriverBinary().getAbsolutePath());
+ File avdDriverFile = getTestDeviceOptions().getAvdDriverBinary();
+ if (!avdDriverFile.exists()) {
+ throw new RuntimeException(
+ String.format(
+ "Could not find the Acloud driver at %s",
+ avdDriverFile.getAbsolutePath()));
+ }
+ if (!avdDriverFile.canExecute()) {
+ // Set the executable bit if needed
+ FileUtil.chmodGroupRWX(avdDriverFile);
+ }
+ List<String> gceArgs = ArrayUtil.list(avdDriverFile.getAbsolutePath());
gceArgs.add(
TestDeviceOptions.getCreateCommandByInstanceType(
getTestDeviceOptions().getInstanceType()));
@@ -299,11 +309,16 @@
gceArgs.add("delete");
// Add extra args.
File f = null;
+ File config = null;
try {
+ config = FileUtil.createTempFile(getAvdConfigFile().getName(), "config");
gceArgs.add("--instance_names");
gceArgs.add(mGceAvdInfo.instanceName());
gceArgs.add("--config_file");
- gceArgs.add(getAvdConfigFile().getAbsolutePath());
+ // Copy the config in case it comes from a dynamic file. In order to ensure Acloud has
+ // the file until it's done with it.
+ FileUtil.copyFile(getAvdConfigFile(), config);
+ gceArgs.add(config.getAbsolutePath());
if (getTestDeviceOptions().getSerivceAccountJsonKeyFile() != null) {
gceArgs.add("--service_account_json_private_key_path");
gceArgs.add(
@@ -324,8 +339,11 @@
"Failed to tear down GCE %s with the following arg, %s",
mGceAvdInfo.instanceName(), gceArgs);
}
+ FileUtil.deleteFile(config);
} else {
- getRunUtil().runCmdInBackground(gceArgs.toArray(new String[gceArgs.size()]));
+ Process p = getRunUtil().runCmdInBackground(gceArgs);
+ AcloudDeleteCleaner cleaner = new AcloudDeleteCleaner(p, config);
+ cleaner.start();
}
} catch (IOException e) {
CLog.e("failed to create log file for GCE Teardown");
@@ -498,12 +516,15 @@
GceAvdInfo gceAvd, TestDeviceOptions options, IRunUtil runUtil, String... command) {
CommandResult res =
remoteSshCommandExecution(gceAvd, options, runUtil, BUGREPORT_TIMEOUT, command);
- if (!CommandStatus.SUCCESS.equals(res.getStatus())) {
- CLog.e("issue when attempting to execute '%s':", Arrays.asList(command));
- CLog.e("%s", res.getStderr());
- }
// We attempt to get a clean output from our command
String output = res.getStdout().trim();
+ if (!CommandStatus.SUCCESS.equals(res.getStatus())) {
+ CLog.e("issue when attempting to execute '%s':", Arrays.asList(command));
+ CLog.e("Stderr: %s", res.getStderr());
+ } else if (output.isEmpty()) {
+ CLog.e("Stdout from '%s' was empty", Arrays.asList(command));
+ CLog.e("Stderr: %s", res.getStderr());
+ }
return output;
}
@@ -636,4 +657,30 @@
}
return getTestDeviceOptions().getAvdConfigFile();
}
+
+ /**
+ * Thread that helps cleaning the copied config when the process is done. This ensures acloud is
+ * not missing its config until its done.
+ */
+ private class AcloudDeleteCleaner extends Thread {
+ private Process mProcess;
+ private File mConfigFile;
+
+ public AcloudDeleteCleaner(Process p, File config) {
+ setDaemon(true);
+ setName("acloud-delete-cleaner");
+ mProcess = p;
+ mConfigFile = config;
+ }
+
+ @Override
+ public void run() {
+ try {
+ mProcess.waitFor();
+ } catch (InterruptedException e) {
+ CLog.e(e);
+ }
+ FileUtil.deleteFile(mConfigFile);
+ }
+ }
}
diff --git a/src/com/android/tradefed/device/cloud/ManagedRemoteDevice.java b/src/com/android/tradefed/device/cloud/ManagedRemoteDevice.java
index 8e2b8f0..288f567 100644
--- a/src/com/android/tradefed/device/cloud/ManagedRemoteDevice.java
+++ b/src/com/android/tradefed/device/cloud/ManagedRemoteDevice.java
@@ -131,8 +131,11 @@
getGceHandler().cleanUp();
}
} finally {
+ // Reset the internal variable
+ mCopiedOptions = null;
if (mValidationConfig != null) {
mValidationConfig.cleanDynamicOptionFiles();
+ mValidationConfig = null;
}
// Ensure parent postInvocationTearDown is always called.
super.postInvocationTearDown(exception);
diff --git a/src/com/android/tradefed/device/cloud/NestedRemoteDevice.java b/src/com/android/tradefed/device/cloud/NestedRemoteDevice.java
index 70d319e..2b6f6ab 100644
--- a/src/com/android/tradefed/device/cloud/NestedRemoteDevice.java
+++ b/src/com/android/tradefed/device/cloud/NestedRemoteDevice.java
@@ -25,18 +25,17 @@
import com.android.tradefed.invoker.RemoteInvocationExecution;
import com.android.tradefed.log.ITestLogger;
import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.ByteArrayInputStreamSource;
import com.android.tradefed.result.FileInputStreamSource;
import com.android.tradefed.result.InputStreamSource;
import com.android.tradefed.result.LogDataType;
import com.android.tradefed.targetprep.TargetSetupError;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
-import com.android.tradefed.util.FileUtil;
import com.google.common.base.Joiner;
import java.io.File;
-import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
@@ -182,15 +181,28 @@
CLog.e("%s doesn't exists, skip logging it.", launcherLog.getAbsolutePath());
return;
}
- try {
- // Attempt to print the log
- CLog.e("Launcher.log content: %s", FileUtil.readStringFromFile(launcherLog));
- } catch (IOException e) {
- // Ignore
- CLog.e(e);
+ // TF runs as the primary user and may get a permission denied to read the launcher.log of
+ // other users. So use the shell to cat the file content.
+ CommandResult readLauncherLogRes =
+ getRunUtil()
+ .runTimedCmd(
+ 60000L,
+ "sudo",
+ "runuser",
+ "-l",
+ username,
+ "-c",
+ String.format("'cat %s'", launcherLog.getAbsolutePath()));
+ if (!CommandStatus.SUCCESS.equals(readLauncherLogRes.getStatus())) {
+ CLog.e(
+ "Failed to read Launcher.log content due to: %s",
+ readLauncherLogRes.getStderr());
+ return;
}
- try (InputStreamSource source = new FileInputStreamSource(launcherLog)) {
+ String content = readLauncherLogRes.getStdout();
+ try (InputStreamSource source = new ByteArrayInputStreamSource(content.getBytes())) {
logger.testLog(logName, LogDataType.TEXT, source);
}
+ CLog.d("See %s for the launcher.log that failed to start.", logName);
}
}
diff --git a/src/com/android/tradefed/device/cloud/RemoteFileUtil.java b/src/com/android/tradefed/device/cloud/RemoteFileUtil.java
index 0443c43..20f9970 100644
--- a/src/com/android/tradefed/device/cloud/RemoteFileUtil.java
+++ b/src/com/android/tradefed/device/cloud/RemoteFileUtil.java
@@ -53,7 +53,7 @@
try {
localFile =
FileUtil.createTempFile(
- FileUtil.getBaseName(fileName), FileUtil.getExtension(fileName));
+ FileUtil.getBaseName(fileName) + "_", FileUtil.getExtension(fileName));
if (fetchRemoteFile(
remoteInstance, options, runUtil, timeout, remoteFilePath, localFile)) {
return localFile;
diff --git a/src/com/android/tradefed/device/metric/DebugHostLogOnFailureCollector.java b/src/com/android/tradefed/device/metric/DebugHostLogOnFailureCollector.java
index 2b64f2f..da1e38a 100644
--- a/src/com/android/tradefed/device/metric/DebugHostLogOnFailureCollector.java
+++ b/src/com/android/tradefed/device/metric/DebugHostLogOnFailureCollector.java
@@ -32,23 +32,41 @@
private static final String NAME_FORMAT = "%s-debug-hostlog-on-failure";
- public Long offset = null;
+ private Long offset = null;
@Override
public void onTestRunStart(DeviceMetricData runData) {
+ offset = null;
// TODO: Improve the offset from the start of the method instead.
- offset = getLogger().getLog().size();
+ try (InputStreamSource source = getLogger().getLog()) {
+ if (source == null) {
+ CLog.e(
+ "Could not obtain the host logs for debugging. It won't be available "
+ + "in the event of test cases failures.");
+ return;
+ }
+ offset = source.size();
+ }
}
@Override
public void onTestFail(DeviceMetricData testData, TestDescription test) {
- try (InputStreamSource source = getLogger().getLog();
- InputStream stream = source.createInputStream()) {
- stream.skip(offset);
- try (InputStreamSource logSource =
- new SnapshotInputStreamSource("host-log-failure", stream)) {
- super.testLog(
- String.format(NAME_FORMAT, test.toString()), LogDataType.TEXT, logSource);
+ if (offset == null) {
+ return;
+ }
+ try (InputStreamSource source = getLogger().getLog()) {
+ if (source == null) {
+ return;
+ }
+ try (InputStream stream = source.createInputStream()) {
+ stream.skip(offset);
+ try (InputStreamSource logSource =
+ new SnapshotInputStreamSource("host-log-failure", stream)) {
+ super.testLog(
+ String.format(NAME_FORMAT, test.toString()),
+ LogDataType.TEXT,
+ logSource);
+ }
}
} catch (IOException e) {
CLog.e(e);
diff --git a/src/com/android/tradefed/device/metric/LogcatOnFailureCollector.java b/src/com/android/tradefed/device/metric/LogcatOnFailureCollector.java
index 066b425..4f01ffa 100644
--- a/src/com/android/tradefed/device/metric/LogcatOnFailureCollector.java
+++ b/src/com/android/tradefed/device/metric/LogcatOnFailureCollector.java
@@ -16,10 +16,14 @@
package com.android.tradefed.device.metric;
import com.android.annotations.VisibleForTesting;
+import com.android.tradefed.device.CollectingByteOutputReceiver;
+import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ILogcatReceiver;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.LogcatReceiver;
+import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
+import com.android.tradefed.result.ByteArrayInputStreamSource;
import com.android.tradefed.result.InputStreamSource;
import com.android.tradefed.result.LogDataType;
import com.android.tradefed.result.TestDescription;
@@ -34,16 +38,24 @@
private static final int MAX_LOGAT_SIZE_BYTES = 4 * 1024 * 1024;
/** Always include a bit of prior data to capture what happened before */
- private static final int OFFSET_CORRECTION = 20000;
+ private static final int OFFSET_CORRECTION = 10000;
private static final String NAME_FORMAT = "%s-%s-logcat-on-failure";
+ private static final String LOGCAT_COLLECT_CMD = "logcat -T 150";
+ // -t implies -d (dump) so it's a one time collection
+ private static final String LOGCAT_COLLECT_CMD_LEGACY = "logcat -t 5000";
+ private static final int API_LIMIT = 20;
+
private Map<ITestDevice, ILogcatReceiver> mLogcatReceivers = new HashMap<>();
private Map<ITestDevice, Integer> mOffset = new HashMap<>();
@Override
public void onTestRunStart(DeviceMetricData runData) {
for (ITestDevice device : getRealDevices()) {
+ if (getApiLevelNoThrow(device) < API_LIMIT) {
+ continue;
+ }
// In case of multiple runs for the same test runner, re-init the receiver.
initReceiver(device);
// Get the current offset of the buffer to be able to query later
@@ -62,17 +74,9 @@
@Override
public void onTestFail(DeviceMetricData testData, TestDescription test) {
- for (ITestDevice device : getRealDevices()) {
- // Delay slightly for the error to get in the logcat
- getRunUtil().sleep(100);
- try (InputStreamSource logcatSource =
- mLogcatReceivers
- .get(device)
- .getLogcatData(MAX_LOGAT_SIZE_BYTES, mOffset.get(device))) {
- String name = String.format(NAME_FORMAT, test.toString(), device.getSerialNumber());
- super.testLog(name, LogDataType.LOGCAT, logcatSource);
- }
- }
+ // Delay slightly for the error to get in the logcat
+ getRunUtil().sleep(100);
+ collectAndLog(test);
}
@Override
@@ -84,7 +88,7 @@
ILogcatReceiver createLogcatReceiver(ITestDevice device) {
// Use logcat -T 'count' to only print a few line before we start and not the full buffer
return new LogcatReceiver(
- device, "logcat -T 150", device.getOptions().getMaxLogcatDataSize(), 0);
+ device, LOGCAT_COLLECT_CMD, device.getOptions().getMaxLogcatDataSize(), 0);
}
@VisibleForTesting
@@ -92,6 +96,31 @@
return RunUtil.getDefault();
}
+ private void collectAndLog(TestDescription test) {
+ for (ITestDevice device : getRealDevices()) {
+ ILogcatReceiver receiver = mLogcatReceivers.get(device);
+ // Receiver is only initialized above API 19, if not supported, we use a legacy command
+ if (receiver == null) {
+ CollectingByteOutputReceiver outputReceiver = new CollectingByteOutputReceiver();
+ try {
+ device.executeShellCommand(LOGCAT_COLLECT_CMD_LEGACY, outputReceiver);
+ saveLogcatSource(
+ test,
+ new ByteArrayInputStreamSource(outputReceiver.getOutput()),
+ device.getSerialNumber());
+ } catch (DeviceNotAvailableException e) {
+ CLog.e(e);
+ }
+ continue;
+ }
+ // If supported get the logcat buffer
+ saveLogcatSource(
+ test,
+ receiver.getLogcatData(MAX_LOGAT_SIZE_BYTES, mOffset.get(device)),
+ device.getSerialNumber());
+ }
+ }
+
private void initReceiver(ITestDevice device) {
if (mLogcatReceivers.get(device) == null) {
ILogcatReceiver receiver = createLogcatReceiver(device);
@@ -108,4 +137,19 @@
mLogcatReceivers.clear();
mOffset.clear();
}
+
+ private int getApiLevelNoThrow(ITestDevice device) {
+ try {
+ return device.getApiLevel();
+ } catch (DeviceNotAvailableException e) {
+ return 1;
+ }
+ }
+
+ private void saveLogcatSource(TestDescription test, InputStreamSource source, String serial) {
+ try (InputStreamSource logcatSource = source) {
+ String name = String.format(NAME_FORMAT, test.toString(), serial);
+ super.testLog(name, LogDataType.LOGCAT, logcatSource);
+ }
+ }
}
diff --git a/src/com/android/tradefed/device/metric/README.md b/src/com/android/tradefed/device/metric/README.md
new file mode 100644
index 0000000..a812b9d
--- /dev/null
+++ b/src/com/android/tradefed/device/metric/README.md
@@ -0,0 +1,9 @@
+# Core Metric Collectors
+
+This folder contains the core implementation and interfaces of TradeFed metric Collectors.
+The most generic implementation, used by the test harness itself (sometimes in
+automatic fashion) are located here.
+
+Most specialized implementation that test writers can use are located at:
+platform/tools/tradefederation/core/test_framework/
+
diff --git a/src/com/android/tradefed/invoker/InvocationExecution.java b/src/com/android/tradefed/invoker/InvocationExecution.java
index f52555b..9663c34 100644
--- a/src/com/android/tradefed/invoker/InvocationExecution.java
+++ b/src/com/android/tradefed/invoker/InvocationExecution.java
@@ -37,9 +37,12 @@
import com.android.tradefed.device.metric.IMetricCollector;
import com.android.tradefed.device.metric.IMetricCollectorReceiver;
import com.android.tradefed.invoker.TestInvocation.Stage;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
import com.android.tradefed.invoker.shard.IShardHelper;
import com.android.tradefed.log.ITestLogger;
import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.ByteArrayInputStreamSource;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.ITestLoggerReceiver;
import com.android.tradefed.result.InputStreamSource;
@@ -56,7 +59,10 @@
import com.android.tradefed.testtype.IInvocationContextReceiver;
import com.android.tradefed.testtype.IMultiDeviceTest;
import com.android.tradefed.testtype.IRemoteTest;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.RunUtil;
import com.android.tradefed.util.SystemUtil;
import com.android.tradefed.util.SystemUtil.EnvVariable;
import com.android.tradefed.util.TimeUtil;
@@ -227,6 +233,7 @@
// Setup timing metric. It does not include flashing time on boot tests.
long setupDuration = System.currentTimeMillis() - start;
context.addInvocationTimingMetric(IInvocationContext.TimingEvent.SETUP, setupDuration);
+ InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.SETUP, setupDuration);
CLog.d("Setup duration: %s'", TimeUtil.formatElapsedTime(setupDuration));
// Upload the setup logcat after setup is complete.
for (String deviceName : context.getDeviceConfigNames()) {
@@ -404,6 +411,9 @@
deferredThrowable = preTargetTearDownException;
}
+ // Collect adb logs.
+ logHostAdb(logger);
+
if (deferredThrowable != null) {
throw deferredThrowable;
}
@@ -740,6 +750,39 @@
}
}
+ /** Collect the logs from $TMPDIR/adb.$UID.log. */
+ @VisibleForTesting
+ void logHostAdb(ITestLogger logger) {
+ String tmpDir = "/tmp";
+ if (System.getenv("TMPDIR") != null) {
+ tmpDir = System.getenv("TMPDIR");
+ }
+ CommandResult uidRes =
+ RunUtil.getDefault()
+ .runTimedCmd(60000, "id", "-u", System.getProperty("user.name"));
+ if (!CommandStatus.SUCCESS.equals(uidRes.getStatus())) {
+ CLog.e("Failed to collect UID for adb logs: %s", uidRes.getStderr());
+ return;
+ }
+ String uid = uidRes.getStdout().trim();
+ File adbLog = new File(tmpDir, String.format("adb.%s.log", uid));
+ if (!adbLog.exists()) {
+ CLog.i("Did not find adb log file: %s, upload skipped.", adbLog);
+ return;
+ }
+ CommandResult truncAdb =
+ RunUtil.getDefault()
+ .runTimedCmd(60000, "tail", "--bytes=10MB", adbLog.getAbsolutePath());
+ if (!CommandStatus.SUCCESS.equals(truncAdb.getStatus())) {
+ CLog.e("Fail to truncate the adb log: %s\n%s", adbLog, truncAdb.getStderr());
+ return;
+ }
+ try (InputStreamSource source =
+ new ByteArrayInputStreamSource(truncAdb.getStdout().getBytes())) {
+ logger.testLog("host_adb_log", LogDataType.TEXT, source);
+ }
+ }
+
/** Returns the external directory coming from the environment. */
@VisibleForTesting
File getExternalTestCasesDirs(EnvVariable envVar) {
diff --git a/src/com/android/tradefed/invoker/RemoteInvocationExecution.java b/src/com/android/tradefed/invoker/RemoteInvocationExecution.java
index f82442c..ecbdae5 100644
--- a/src/com/android/tradefed/invoker/RemoteInvocationExecution.java
+++ b/src/com/android/tradefed/invoker/RemoteInvocationExecution.java
@@ -22,10 +22,13 @@
import com.android.tradefed.clearcut.ClearcutClient;
import com.android.tradefed.command.CommandOptions;
import com.android.tradefed.command.CommandRunner;
+import com.android.tradefed.config.ConfigurationException;
+import com.android.tradefed.config.DynamicRemoteFileResolver;
import com.android.tradefed.config.GlobalConfiguration;
import com.android.tradefed.config.IConfiguration;
import com.android.tradefed.config.IDeviceConfiguration;
import com.android.tradefed.config.OptionCopier;
+import com.android.tradefed.config.OptionSetter;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.DeviceSelectionOptions;
import com.android.tradefed.device.TestDeviceOptions;
@@ -35,6 +38,8 @@
import com.android.tradefed.device.cloud.ManagedRemoteDevice;
import com.android.tradefed.device.cloud.MultiUserSetupUtil;
import com.android.tradefed.device.cloud.RemoteFileUtil;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
import com.android.tradefed.log.ITestLogger;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.FileInputStreamSource;
@@ -50,6 +55,7 @@
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.IRunUtil;
import com.android.tradefed.util.RunUtil;
+import com.android.tradefed.util.SystemUtil;
import com.android.tradefed.util.TimeUtil;
import com.android.tradefed.util.proto.TestRecordProtoUtil;
@@ -72,9 +78,9 @@
public static final long PUSH_TF_TIMEOUT = 150000L;
public static final long PULL_RESULT_TIMEOUT = 180000L;
public static final long REMOTE_PROCESS_RUNNING_WAIT = 15000L;
- public static final long LAUNCH_EXTRA_DEVICE = 10 * 60 * 1000L;
+ public static final long LAUNCH_EXTRA_DEVICE = 15 * 60 * 1000L;
+ public static final long SETUP_REMOTE_DIR_TIMEOUT = 10 * 60 * 1000L;
public static final long NEW_USER_TIMEOUT = 5 * 60 * 1000L;
- public static final String REMOTE_VM_VARIABLE = "REMOTE_VM_ENV";
public static final String REMOTE_USER_DIR = "/home/{$USER}/";
public static final String PROTO_RESULT_NAME = "output.pb";
@@ -164,23 +170,18 @@
// Log the overhead to start the device
long elapsedTime = System.currentTimeMillis() - startTime;
- context.getBuildInfos()
- .get(0)
- .addBuildAttribute(SHARDING_DEVICE_SETUP_TIME, Long.toString(elapsedTime));
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.SHARDING_DEVICE_SETUP_TIME, elapsedTime);
}
}
mRemoteAdbPath = String.format("/home/%s/bin/adb", options.getInstanceUser());
-
- String tfPath = System.getProperty("TF_JAR_DIR");
- if (tfPath == null) {
- listener.invocationFailed(new RuntimeException("Failed to find $TF_JAR_DIR."));
+ // Select the TF version that should be pushed to the remote VM
+ File tfToPush = getLocalTradefedPath(listener, options.getRemoteTf());
+ if (tfToPush == null) {
return;
}
- File currentTf = new File(tfPath).getAbsoluteFile();
- if (tfPath.equals(".")) {
- currentTf = new File("").getAbsoluteFile();
- }
+
mRemoteTradefedDir = mainRemoteDir + "tradefed/";
CommandResult createRemoteDir =
GceManager.remoteSshCommandExecution(
@@ -202,7 +203,7 @@
runUtil,
PUSH_TF_TIMEOUT,
mRemoteTradefedDir,
- currentTf);
+ tfToPush);
attempt++;
}
if (!result) {
@@ -211,7 +212,7 @@
return;
}
- mRemoteTradefedDir = mRemoteTradefedDir + currentTf.getName() + "/";
+ mRemoteTradefedDir = mRemoteTradefedDir + tfToPush.getName() + "/";
CommandResult listRemoteDir =
GceManager.remoteSshCommandExecution(
info, options, runUtil, 120000L, "ls", "-l", mRemoteTradefedDir);
@@ -242,6 +243,7 @@
new String[] {
GlobalConfiguration.SCHEDULER_TYPE_NAME,
GlobalConfiguration.HOST_OPTIONS_TYPE_NAME,
+ DynamicRemoteFileResolver.DYNAMIC_RESOLVER,
"android-build"
};
try {
@@ -329,7 +331,7 @@
StringBuilder tfCmdBuilder =
new StringBuilder("TF_GLOBAL_CONFIG=" + globalConfig.getName());
// Set an env variable to notify that this a remote environment.
- tfCmdBuilder.append(" " + REMOTE_VM_VARIABLE + "=1");
+ tfCmdBuilder.append(" " + SystemUtil.REMOTE_VM_VARIABLE + "=1");
// Disable clearcut in the remote
tfCmdBuilder.append(" " + ClearcutClient.DISABLE_CLEARCUT_KEY + "=1");
tfCmdBuilder.append(" ENTRY_CLASS=" + CommandRunner.class.getCanonicalName());
@@ -501,6 +503,13 @@
parser.processFileProto(resultFile);
}
} while (resultFile != null);
+
+ if (!parser.invocationEndedReached()) {
+ currentInvocationListener.invocationFailed(
+ new RuntimeException(
+ "Parsing of results protos might be incomplete: invocation ended "
+ + "of remote execution was not found."));
+ }
}
return stillRunning;
}
@@ -575,14 +584,18 @@
*/
@VisibleForTesting
File createRemoteConfig(IConfiguration config, ITestLogger logger, String resultDirPath)
- throws IOException {
+ throws IOException, ConfigurationException {
// Setup the remote reporting to a proto file
List<ITestInvocationListener> reporters = new ArrayList<>();
FileProtoResultReporter protoReporter = new FileProtoResultReporter();
+ OptionSetter protoResSetter = new OptionSetter(protoReporter);
if (config.getCommandOptions().shouldReportModuleProgression()) {
- protoReporter.setPeriodicWriting(true);
+ protoResSetter.setOptionValue(
+ FileProtoResultReporter.PERIODIC_PROTO_WRITING_OPTION, "true");
}
- protoReporter.setFileOutput(new File(resultDirPath + PROTO_RESULT_NAME));
+ protoResSetter.setOptionValue(
+ FileProtoResultReporter.PROTO_OUTPUT_FILE,
+ new File(resultDirPath + PROTO_RESULT_NAME).getPath());
reporters.add(protoReporter);
config.setTestInvocationListeners(reporters);
@@ -595,6 +608,11 @@
}
}
+ // Unset remote-tf-version to avoid re-downloading from remote VM.
+ OptionSetter deviceOptions =
+ new OptionSetter(config.getDeviceConfig().get(0).getDeviceOptions());
+ deviceOptions.setOptionValue(TestDeviceOptions.REMOTE_TF_VERSION_OPTION, "");
+
// Dump and log the configuration
File configFile = FileUtil.createTempFile(config.getName(), ".xml");
config.dumpXml(
@@ -608,6 +626,24 @@
return configFile;
}
+ /** Returns the Tradefed version that should be pushed to the remote to drive the invocation. */
+ private File getLocalTradefedPath(ITestInvocationListener listener, File remoteTf) {
+ if (remoteTf != null && remoteTf.exists()) {
+ return remoteTf;
+ }
+
+ String tfPath = System.getProperty("TF_JAR_DIR");
+ if (tfPath == null) {
+ listener.invocationFailed(new RuntimeException("Failed to find $TF_JAR_DIR."));
+ return null;
+ }
+ File currentTf = new File(tfPath).getAbsoluteFile();
+ if (tfPath.equals(".")) {
+ currentTf = new File("").getAbsoluteFile();
+ }
+ return currentTf;
+ }
+
private void fetchAndProcessResults(
boolean wasStillRunning,
ITestInvocationListener invocationListener,
@@ -685,7 +721,7 @@
info,
options,
runUtil,
- NEW_USER_TIMEOUT);
+ SETUP_REMOTE_DIR_TIMEOUT);
if (homeDirSetup != null) {
String errorMsg =
String.format("Failed to setup home dir: %s", homeDirSetup.getStderr());
diff --git a/src/com/android/tradefed/invoker/ShardListener.java b/src/com/android/tradefed/invoker/ShardListener.java
index 5805a3b..7cfc0d1 100644
--- a/src/com/android/tradefed/invoker/ShardListener.java
+++ b/src/com/android/tradefed/invoker/ShardListener.java
@@ -28,10 +28,13 @@
import com.android.tradefed.result.TestDescription;
import com.android.tradefed.result.TestResult;
import com.android.tradefed.result.TestRunResult;
+import com.android.tradefed.result.retry.ISupportGranularResults;
import com.android.tradefed.util.TimeUtil;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -40,10 +43,12 @@
* invocation split to run on multiple resources in parallel), and forwards them to another
* listener.
*/
-public class ShardListener extends CollectingTestListener {
+public class ShardListener extends CollectingTestListener implements ISupportGranularResults {
private ITestInvocationListener mMasterListener;
private IInvocationContext mModuleContext = null;
+ private int mAttemptInProgress = 0;
+ private boolean mEnableGranularResults = false;
/**
* Create a {@link ShardListener}.
@@ -57,6 +62,16 @@
mMasterListener = master;
}
+ /** {@inheritDoc} */
+ @Override
+ public boolean supportGranularResults() {
+ return mEnableGranularResults;
+ }
+
+ public void setSupportGranularResults(boolean enableGranularResults) {
+ mEnableGranularResults = enableGranularResults;
+ }
+
/**
* {@inheritDoc}
*/
@@ -118,6 +133,13 @@
mModuleContext = moduleContext;
}
+ /** {@inheritDoc} */
+ @Override
+ public void testRunStarted(String name, int numTests, int attemptNumber, long startTime) {
+ super.testRunStarted(name, numTests, attemptNumber, startTime);
+ mAttemptInProgress = attemptNumber;
+ }
+
/**
* {@inheritDoc}
*/
@@ -138,9 +160,11 @@
// testRunEnded only forwards if it's not part of a module. If it's a module
// testModuleEnded is in charge of forwarding all run results.
synchronized (mMasterListener) {
- forwardRunResults(getCurrentRunResults());
+ forwardRunResults(getCurrentRunResults(), mAttemptInProgress);
}
+ mAttemptInProgress = 0;
}
+
}
/** {@inheritDoc} */
@@ -149,34 +173,29 @@
super.testModuleEnded();
synchronized (mMasterListener) {
- IInvocationContext moduleContext = null;
- // TODO: Support attempts and retries
- for (TestRunResult runResult : getMergedTestRunResults()) {
- // Only consider run results of the module in progress
- if (getModuleContextForRunResult(runResult.getName()) != mModuleContext) {
- continue;
+ mMasterListener.testModuleStarted(mModuleContext);
+ List<String> resultNames = new ArrayList<String>();
+ if (mEnableGranularResults) {
+ for (int i = 0; i < mAttemptInProgress + 1; i++) {
+ List<TestRunResult> runResults = getTestRunForAttempts(i);
+ for (TestRunResult runResult : runResults) {
+ forwardRunResults(runResult, i);
+ resultNames.add(runResult.getName());
+ }
}
+ } else {
+ for (TestRunResult runResult : getMergedTestRunResults()) {
+ // Forward the run level results
+ forwardRunResults(runResult, 0);
+ resultNames.add(runResult.getName());
+ }
+ }
- // Stop or start the module
- if (moduleContext != null
- && !getModuleContextForRunResult(runResult.getName())
- .equals(moduleContext)) {
- mMasterListener.testModuleEnded();
- moduleContext = null;
- }
- if (moduleContext == null
- && getModuleContextForRunResult(runResult.getName()) != null) {
- moduleContext = getModuleContextForRunResult(runResult.getName());
- mMasterListener.testModuleStarted(moduleContext);
- }
- // Forward the run level results
- forwardRunResults(runResult);
+ // Ensure we don't carry results from one module to another.
+ for (String name : resultNames) {
+ clearResultsForName(name);
}
- // Close the last module
- if (moduleContext != null) {
- mMasterListener.testModuleEnded();
- moduleContext = null;
- }
+ mMasterListener.testModuleEnded();
}
mModuleContext = null;
}
@@ -193,9 +212,12 @@
}
}
- private void forwardRunResults(TestRunResult runResult) {
- // TODO: Support attempts and retries
- mMasterListener.testRunStarted(runResult.getName(), runResult.getExpectedTestCount());
+ private void forwardRunResults(TestRunResult runResult, int attempt) {
+ mMasterListener.testRunStarted(
+ runResult.getName(),
+ runResult.getExpectedTestCount(),
+ attempt,
+ runResult.getStartTime());
forwardTestResults(runResult.getTestResults());
if (runResult.isRunFailure()) {
mMasterListener.testRunFailed(runResult.getRunFailureMessage());
diff --git a/src/com/android/tradefed/invoker/TestInvocation.java b/src/com/android/tradefed/invoker/TestInvocation.java
index 8f18c26..e64c8fb 100644
--- a/src/com/android/tradefed/invoker/TestInvocation.java
+++ b/src/com/android/tradefed/invoker/TestInvocation.java
@@ -19,6 +19,7 @@
import com.android.tradefed.build.BuildRetrievalError;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.command.CommandRunner.ExitCode;
+import com.android.tradefed.config.ConfigurationException;
import com.android.tradefed.config.GlobalConfiguration;
import com.android.tradefed.config.IConfiguration;
import com.android.tradefed.device.DeviceNotAvailableException;
@@ -33,6 +34,7 @@
import com.android.tradefed.device.cloud.RemoteAndroidVirtualDevice;
import com.android.tradefed.guice.InvocationScope;
import com.android.tradefed.invoker.logger.InvocationMetricLogger;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
import com.android.tradefed.invoker.sandbox.ParentSandboxInvocationExecution;
import com.android.tradefed.invoker.sandbox.SandboxedInvocationExecution;
import com.android.tradefed.invoker.shard.ShardBuildCloner;
@@ -56,6 +58,7 @@
import com.android.tradefed.testtype.IRemoteTest;
import com.android.tradefed.testtype.IResumableTest;
import com.android.tradefed.testtype.IRetriableTest;
+import com.android.tradefed.testtype.retry.IRetryDecision;
import com.android.tradefed.testtype.retry.ResultAggregator;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.IRunUtil;
@@ -484,10 +487,14 @@
ITestInvocationListener listener, IConfiguration config, String name) {
ILeveledLogOutput logger = config.getLogOutput();
try (InputStreamSource globalLogSource = logger.getLog()) {
- if (config.getCommandOptions().getHostLogSuffix() != null) {
- name += config.getCommandOptions().getHostLogSuffix();
+ if (globalLogSource != null) {
+ if (config.getCommandOptions().getHostLogSuffix() != null) {
+ name += config.getCommandOptions().getHostLogSuffix();
+ }
+ listener.testLog(name, LogDataType.TEXT, globalLogSource);
+ } else {
+ CLog.i("Skip logging %s to a file with logger '%s'", name, logger);
}
- listener.testLog(name, LogDataType.TEXT, globalLogSource);
}
// once tradefed log is reported, all further log calls for this invocation can get lost
// unregister logger so future log calls get directed to the tradefed global log
@@ -620,6 +627,45 @@
return false;
}
+ /**
+ * Invoke {@link IConfiguration#resolveDynamicOptions()} to resolve the dynamic files.
+ *
+ * @param context the {@link IInvocationContext} of the invocation.
+ * @param config the {@link IConfiguration} of this test run.
+ * @param rescheduler the {@link IRescheduler}, for rescheduling portions of the invocation for
+ * execution on another resource(s)
+ * @param listener the {@link ITestInvocation} to report build download failures.
+ * @param invocationPath the {@link IInvocationExecution} driving the invocation.
+ * @param mode The current {@link RunMode} of the invocation.
+ * @return True if we successfully downloaded the build, false otherwise.
+ */
+ private boolean invokeRemoteDynamic(
+ IInvocationContext context,
+ IConfiguration config,
+ IRescheduler rescheduler,
+ ITestInvocationListener listener,
+ IInvocationExecution invocationPath,
+ RunMode mode) {
+ try {
+ // Don't resolve for remote invocation, wait until we are inside the remote.
+ if (!RunMode.REMOTE_INVOCATION.equals(mode)) {
+ config.resolveDynamicOptions();
+ }
+ return true;
+ } catch (RuntimeException | ConfigurationException e) {
+ // Report an empty invocation, so this error is sent to listeners
+ startInvocation(config, context, listener);
+ // Don't want to use #reportFailure, since that will call buildNotTested
+ listener.invocationFailed(e);
+ for (ITestDevice device : context.getDevices()) {
+ invocationPath.reportLogs(device, listener, Stage.ERROR);
+ }
+ reportHostLog(listener, config);
+ listener.invocationEnded(0L);
+ return false;
+ }
+ }
+
/** {@inheritDoc} */
@Override
public void invoke(
@@ -635,12 +681,12 @@
ITestInvocationListener listener = null;
// Auto retry feature
- if (config.getCommandOptions().isAutoRetryEnabled()
- && config.getCommandOptions().getMaxRetryCount() > 1) {
+ IRetryDecision decision = config.getRetryDecision();
+ ResultAggregator aggregator = null;
+ decision.setInvocationContext(context);
+ if (decision.isAutoRetryEnabled() && decision.getMaxRetryCount() > 1) {
CLog.d("Auto-retry enabled, using the ResultAggregator to handle multiple retries.");
- ResultAggregator aggregator =
- new ResultAggregator(
- allListeners, config.getCommandOptions().getRetryStrategy());
+ aggregator = new ResultAggregator(allListeners, decision.getRetryStrategy());
allListeners = Arrays.asList(aggregator);
}
@@ -685,7 +731,12 @@
}
getLogRegistry().registerLogger(leveledLogOutput);
mStatus = "resolving dynamic options";
- config.resolveDynamicOptions();
+ boolean resolverSuccess =
+ invokeRemoteDynamic(
+ context, config, rescheduler, listener, invocationPath, mode);
+ if (!resolverSuccess) {
+ return;
+ }
mStatus = "fetching build";
for (String deviceName : context.getDeviceConfigNames()) {
@@ -712,6 +763,8 @@
long fetchBuildDuration = System.currentTimeMillis() - start;
context.addInvocationTimingMetric(IInvocationContext.TimingEvent.FETCH_BUILD,
fetchBuildDuration);
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.FETCH_BUILD, fetchBuildDuration);
CLog.d("Fetch build duration: %s", TimeUtil.formatElapsedTime(fetchBuildDuration));
if (!providerSuccess) {
return;
@@ -762,6 +815,11 @@
// Log the chunk of parent host_log before sharding
reportHostLog(listener, config, TRADEFED_LOG_NAME + BEFORE_SHARDING_SUFFIX);
config.getLogSaver().invocationEnded(0L);
+ if (aggregator != null) {
+ // The host_log is not available yet to reporters that don't support
+ // granular results, so forward it.
+ aggregator.forwardAggregatedInvocationLogs();
+ }
return;
}
}
diff --git a/src/com/android/tradefed/invoker/logger/InvocationMetricLogger.java b/src/com/android/tradefed/invoker/logger/InvocationMetricLogger.java
index bf2eb87..d79a552 100644
--- a/src/com/android/tradefed/invoker/logger/InvocationMetricLogger.java
+++ b/src/com/android/tradefed/invoker/logger/InvocationMetricLogger.java
@@ -15,6 +15,8 @@
*/
package com.android.tradefed.invoker.logger;
+import com.android.tradefed.log.LogUtil.CLog;
+
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -24,19 +26,32 @@
/** Some special named key that we will always populate for the invocation. */
public enum InvocationMetricKey {
- FETCH_BUILD("fetch_build_time_ms"),
- SETUP("setup_time_ms");
+ WIFI_AP_NAME("wifi_ap_name", false),
+ CLEARED_RUN_ERROR("cleared_run_error", false),
+ FETCH_BUILD("fetch_build_time_ms", true),
+ SETUP("setup_time_ms", true),
+ SHARDING_DEVICE_SETUP_TIME("remote_device_sharding_setup_ms", true),
+ AUTO_RETRY_TIME("auto_retry_time_ms", true),
+ STAGE_TESTS_TIME("stage_tests_time_ms", true),
+ STAGE_TESTS_BYTES("stage_tests_bytes", true);
private final String mKeyName;
+ // Whether or not to add the value when the key is added again.
+ private final boolean mAdditive;
- private InvocationMetricKey(String key) {
+ private InvocationMetricKey(String key, boolean additive) {
mKeyName = key;
+ mAdditive = additive;
}
@Override
public String toString() {
return mKeyName;
}
+
+ public boolean shouldAdd() {
+ return mAdditive;
+ }
}
private InvocationMetricLogger() {}
@@ -54,6 +69,30 @@
* @param key The key under which the invocation metric will be tracked.
* @param value The value of the invocation metric.
*/
+ public static void addInvocationMetrics(InvocationMetricKey key, long value) {
+ if (key.shouldAdd()) {
+ String existingVal = getInvocationMetrics().get(key.toString());
+ long existingLong = 0L;
+ if (existingVal != null) {
+ try {
+ existingLong = Long.parseLong(existingVal);
+ } catch (NumberFormatException e) {
+ CLog.e(
+ "%s is expected to contain a number, instead found: %s",
+ key.toString(), existingVal);
+ }
+ }
+ value += existingLong;
+ }
+ addInvocationMetrics(key.toString(), Long.toString(value));
+ }
+
+ /**
+ * Add one key-value to be tracked at the invocation level.
+ *
+ * @param key The key under which the invocation metric will be tracked.
+ * @param value The value of the invocation metric.
+ */
public static void addInvocationMetrics(InvocationMetricKey key, String value) {
addInvocationMetrics(key.toString(), value);
}
diff --git a/src/com/android/tradefed/invoker/shard/ShardHelper.java b/src/com/android/tradefed/invoker/shard/ShardHelper.java
index b0935ee..ab734ec 100644
--- a/src/com/android/tradefed/invoker/shard/ShardHelper.java
+++ b/src/com/android/tradefed/invoker/shard/ShardHelper.java
@@ -40,6 +40,7 @@
import com.android.tradefed.testtype.IMultiDeviceTest;
import com.android.tradefed.testtype.IRemoteTest;
import com.android.tradefed.testtype.IShardableTest;
+import com.android.tradefed.testtype.retry.IRetryDecision;
import com.android.tradefed.util.QuotationAwareTokenizer;
import com.android.tradefed.util.keystore.IKeyStoreClient;
import com.android.tradefed.util.keystore.KeyStoreException;
@@ -69,6 +70,8 @@
CONFIG_OBJ_TO_CLONE.add(Configuration.LOGGER_TYPE_NAME);
// Deep clone of log_saver to ensure each shard manages its own logs
CONFIG_OBJ_TO_CLONE.add(Configuration.LOG_SAVER_TYPE_NAME);
+ // Deep clone RetryDecision to ensure each shard retry independently
+ CONFIG_OBJ_TO_CLONE.add(Configuration.RETRY_DECISION_TYPE_NAME);
}
/**
@@ -176,7 +179,7 @@
ShardBuildCloner.cloneBuildInfos(config, shardConfig, context);
shardConfig.setTestInvocationListeners(
- buildShardListeners(resultCollector, config.getTestInvocationListeners()));
+ buildShardListeners(resultCollector, config, config.getTestInvocationListeners()));
// Set the host_log suffix to avoid similar names
String suffix = String.format("_shard_index_%s", index);
@@ -317,7 +320,9 @@
* shard collector.
*/
private static List<ITestInvocationListener> buildShardListeners(
- ITestInvocationListener resultCollector, List<ITestInvocationListener> origListeners) {
+ ITestInvocationListener resultCollector,
+ IConfiguration config,
+ List<ITestInvocationListener> origListeners) {
List<ITestInvocationListener> shardListeners = new ArrayList<ITestInvocationListener>();
for (ITestInvocationListener l : origListeners) {
if (l instanceof IShardableListener) {
@@ -325,10 +330,19 @@
}
}
ShardListener origConfigListener = new ShardListener(resultCollector);
+ origConfigListener.setSupportGranularResults(isAutoRetryEnabled(config));
shardListeners.add(origConfigListener);
return shardListeners;
}
+ private static boolean isAutoRetryEnabled(IConfiguration config) {
+ IRetryDecision decision = config.getRetryDecision();
+ if (decision.isAutoRetryEnabled() && decision.getMaxRetryCount() > 0) {
+ return true;
+ }
+ return false;
+ }
+
private Collection<ITokenRequest> extractTokenTests(Collection<IRemoteTest> shardableTests) {
List<ITokenRequest> tokenPool = new ArrayList<>();
Iterator<IRemoteTest> itr = new ArrayList<>(shardableTests).iterator();
diff --git a/src/com/android/tradefed/invoker/shard/TestsPoolPoller.java b/src/com/android/tradefed/invoker/shard/TestsPoolPoller.java
index 85c0c28..b49b906 100644
--- a/src/com/android/tradefed/invoker/shard/TestsPoolPoller.java
+++ b/src/com/android/tradefed/invoker/shard/TestsPoolPoller.java
@@ -193,9 +193,6 @@
if (test instanceof IBuildReceiver) {
((IBuildReceiver) test).setBuild(mBuildInfo);
}
- if (test instanceof IConfigurationReceiver) {
- ((IConfigurationReceiver) test).setConfiguration(mConfig);
- }
if (test instanceof IDeviceTest) {
((IDeviceTest) test).setDevice(mDevice);
}
@@ -216,6 +213,11 @@
validationConfig.setTest(test);
validationConfig.validateOptions();
validationConfig.resolveDynamicOptions();
+ // Set the configuration after the validation, otherwise we override the config
+ // available to the test.
+ if (test instanceof IConfigurationReceiver) {
+ ((IConfigurationReceiver) test).setConfiguration(mConfig);
+ }
// Run the test itself and prevent random exception from stopping the poller.
if (test instanceof IMetricCollectorReceiver) {
((IMetricCollectorReceiver) test).setMetricCollectors(mCollectors);
diff --git a/src/com/android/tradefed/log/ILeveledLogOutput.java b/src/com/android/tradefed/log/ILeveledLogOutput.java
index bed4c4d..31ee5a7 100644
--- a/src/com/android/tradefed/log/ILeveledLogOutput.java
+++ b/src/com/android/tradefed/log/ILeveledLogOutput.java
@@ -49,13 +49,13 @@
/**
* Grabs a snapshot stream of the log data.
- * <p/>
- * Must not be called after {@link ILeveledLogOutput#closeLog()}.
- * <p/>
- * The returned stream is not guaranteed to have optimal performance. Callers may wish to
+ *
+ * <p>Must not be called after {@link ILeveledLogOutput#closeLog()}.
+ *
+ * <p>The returned stream is not guaranteed to have optimal performance. Callers may wish to
* wrap result in a {@link BufferedInputStream}.
*
- * @return a {@link InputStreamSource} of the log data
+ * @return a {@link InputStreamSource} of the log data. May return null if not supported.
* @throws IllegalStateException if called when log has been closed.
*/
public InputStreamSource getLog();
diff --git a/src/com/android/tradefed/log/LogRegistry.java b/src/com/android/tradefed/log/LogRegistry.java
index 2acb8eb..c96d25d 100644
--- a/src/com/android/tradefed/log/LogRegistry.java
+++ b/src/com/android/tradefed/log/LogRegistry.java
@@ -135,10 +135,13 @@
*/
@Override
public void dumpToGlobalLog(ILeveledLogOutput log) {
- try (InputStreamSource source = log.getLog();
- InputStream stream = source.createInputStream()) {
- mGlobalLogger.dumpToLog(stream);
- } catch (IOException e) {
+ try (InputStreamSource source = log.getLog()) {
+ if (source != null) {
+ try (InputStream stream = source.createInputStream()) {
+ mGlobalLogger.dumpToLog(stream);
+ }
+ }
+ } catch (IOException | RuntimeException e) {
System.err.println("Failed to dump log");
e.printStackTrace();
}
diff --git a/src/com/android/tradefed/log/StdoutLogger.java b/src/com/android/tradefed/log/StdoutLogger.java
index 50a68a1..d19dd44 100644
--- a/src/com/android/tradefed/log/StdoutLogger.java
+++ b/src/com/android/tradefed/log/StdoutLogger.java
@@ -19,7 +19,6 @@
import com.android.tradefed.config.Option;
import com.android.tradefed.config.Option.Importance;
import com.android.tradefed.config.OptionClass;
-import com.android.tradefed.result.ByteArrayInputStreamSource;
import com.android.tradefed.result.InputStreamSource;
import java.io.IOException;
@@ -80,8 +79,8 @@
*/
@Override
public InputStreamSource getLog() {
- // not supported - return empty stream
- return new ByteArrayInputStreamSource(new byte[0]);
+ // Not supported - return null
+ return null;
}
@Override
diff --git a/src/com/android/tradefed/result/CollectingTestListener.java b/src/com/android/tradefed/result/CollectingTestListener.java
index aa8a4c2..5aaba96 100644
--- a/src/com/android/tradefed/result/CollectingTestListener.java
+++ b/src/com/android/tradefed/result/CollectingTestListener.java
@@ -21,7 +21,7 @@
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
-import com.android.tradefed.testtype.retry.MergeStrategy;
+import com.android.tradefed.retry.MergeStrategy;
import com.google.common.annotations.VisibleForTesting;
diff --git a/src/com/android/tradefed/result/ConsoleResultReporter.java b/src/com/android/tradefed/result/ConsoleResultReporter.java
index 48fd727..082bbfd 100644
--- a/src/com/android/tradefed/result/ConsoleResultReporter.java
+++ b/src/com/android/tradefed/result/ConsoleResultReporter.java
@@ -23,9 +23,10 @@
import java.util.ArrayList;
import java.util.Collections;
-import java.util.LinkedList;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* Result reporter to print the test results to the console.
@@ -35,20 +36,26 @@
*/
@OptionClass(alias = "console-result-reporter")
public class ConsoleResultReporter extends CollectingTestListener implements ILogSaverListener {
- private static final String LOG_TAG = ConsoleResultReporter.class.getSimpleName();
@Option(name = "suppress-passed-tests", description = "For functional tests, ommit summary for "
+ "passing tests, only print failed and ignored ones")
private boolean mSuppressPassedTest = false;
- private List<LogFile> mLogFiles = new LinkedList<>();
+ private Set<LogFile> mLogFiles = new LinkedHashSet<>();
/**
* {@inheritDoc}
*/
@Override
public void invocationEnded(long elapsedTime) {
- Log.logAndDisplay(LogLevel.INFO, LOG_TAG, getInvocationSummary());
+ Log.logAndDisplay(LogLevel.INFO, this.getClass().getSimpleName(), getInvocationSummary());
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void logAssociation(String dataName, LogFile logFile) {
+ super.logAssociation(dataName, logFile);
+ mLogFiles.add(logFile);
}
/**
diff --git a/src/com/android/tradefed/result/InvocationToJUnitResultForwarder.java b/src/com/android/tradefed/result/InvocationToJUnitResultForwarder.java
index 41776e9..acddd3b 100644
--- a/src/com/android/tradefed/result/InvocationToJUnitResultForwarder.java
+++ b/src/com/android/tradefed/result/InvocationToJUnitResultForwarder.java
@@ -59,6 +59,7 @@
Test test = new TestIdentifierResult(testId);
// TODO: is it accurate to represent the trace as AssertionFailedError?
mJUnitListener.addFailure(test, new AssertionFailedError(trace));
+ Log.i(LOG_TAG, String.format("Test %s failed with:\n %s", testId.toString(), trace));
}
@Override
@@ -82,7 +83,7 @@
@Override
public void testRunFailed(String errorMessage) {
// TODO: no run failed method on TestListener - would be good to propagate this up
- Log.e(LOG_TAG, String.format("run failed: %s", errorMessage));
+ Log.e(LOG_TAG, String.format("Run failed: %s", errorMessage));
}
/**
@@ -107,7 +108,7 @@
/** {@inheritDoc} */
@Override
public void testStarted(TestDescription test) {
- Log.d(LOG_TAG, test.toString());
+ Log.d(LOG_TAG, String.format("Starting test: %s", test.toString()));
mJUnitListener.startTest(new TestIdentifierResult(test));
}
diff --git a/src/com/android/tradefed/result/LogSaverResultForwarder.java b/src/com/android/tradefed/result/LogSaverResultForwarder.java
index fc937e7..2488ee3 100644
--- a/src/com/android/tradefed/result/LogSaverResultForwarder.java
+++ b/src/com/android/tradefed/result/LogSaverResultForwarder.java
@@ -20,6 +20,8 @@
import com.android.tradefed.invoker.TestInvocation;
import com.android.tradefed.log.LogRegistry;
import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.util.StreamUtil;
+import com.android.tradefed.util.SystemUtil;
import java.io.IOException;
import java.io.InputStream;
@@ -69,9 +71,22 @@
private void reportEndHostLog(ILogSaver saver) {
LogRegistry registry = (LogRegistry) LogRegistry.getLogRegistry();
- try (InputStreamSource source = registry.getLogger().getLog();
- InputStream stream = source.createInputStream()) {
- saver.saveLogData(TestInvocation.TRADEFED_END_HOST_LOG, LogDataType.TEXT, stream);
+ try (InputStreamSource source = registry.getLogger().getLog()) {
+ if (source == null) {
+ CLog.e("%s stream was null, skip saving it.", TestInvocation.TRADEFED_END_HOST_LOG);
+ return;
+ }
+ try (InputStream stream = source.createInputStream()) {
+ saver.saveLogData(TestInvocation.TRADEFED_END_HOST_LOG, LogDataType.TEXT, stream);
+ if (SystemUtil.isRemoteEnvironment()) {
+ // In remote environment, dump to the stdout so we can get the logs in the
+ // console.
+ System.out.println(
+ String.format(
+ "===== Result Reporters =====\n%s",
+ StreamUtil.getStringFromStream(stream)));
+ }
+ }
} catch (IOException e) {
CLog.e(e);
}
@@ -88,6 +103,10 @@
public void testLog(String dataName, LogDataType dataType, InputStreamSource dataStream) {
testLogForward(dataName, dataType, dataStream);
try {
+ if (dataStream == null) {
+ CLog.w("Skip forwarding of '%s', data stream is null.", dataName);
+ return;
+ }
LogFile logFile = mLogSaver.saveLogData(dataName, dataType,
dataStream.createInputStream());
for (ITestInvocationListener listener : getListeners()) {
diff --git a/src/com/android/tradefed/result/SubprocessResultsReporter.java b/src/com/android/tradefed/result/SubprocessResultsReporter.java
index 9f9075d..0a2c47a 100644
--- a/src/com/android/tradefed/result/SubprocessResultsReporter.java
+++ b/src/com/android/tradefed/result/SubprocessResultsReporter.java
@@ -18,6 +18,7 @@
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.config.Option;
import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
import com.android.tradefed.util.FileUtil;
@@ -47,6 +48,7 @@
import java.net.Socket;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* Implements {@link ITestInvocationListener} to be specified as a result_reporter and forward from
@@ -225,8 +227,10 @@
if (mPrimaryBuildInfo == null) {
return;
}
- InvocationEndedEventInfo eventEnd =
- new InvocationEndedEventInfo(mPrimaryBuildInfo.getBuildAttributes());
+ Map<String, String> metrics = mPrimaryBuildInfo.getBuildAttributes();
+ // All the invocation level metrics collected
+ metrics.putAll(InvocationMetricLogger.getInvocationMetrics());
+ InvocationEndedEventInfo eventEnd = new InvocationEndedEventInfo(metrics);
printEvent(SubprocessTestResultsParser.StatusKeys.INVOCATION_ENDED, eventEnd);
// Upon invocation ended, trigger the end of the socket when the process finishes
SocketFinisher thread = new SocketFinisher();
diff --git a/src/com/android/tradefed/result/proto/FileProtoResultReporter.java b/src/com/android/tradefed/result/proto/FileProtoResultReporter.java
index 0d2fe41..f75215b 100644
--- a/src/com/android/tradefed/result/proto/FileProtoResultReporter.java
+++ b/src/com/android/tradefed/result/proto/FileProtoResultReporter.java
@@ -27,14 +27,18 @@
/** Proto reporter that dumps the {@link TestRecord} into a file. */
public class FileProtoResultReporter extends ProtoResultReporter {
+ public static final String PROTO_OUTPUT_FILE = "proto-output-file";
+
@Option(
- name = "proto-output-file",
+ name = PROTO_OUTPUT_FILE,
description = "File where the proto output will be saved. If unset, reporter will be inop."
)
private File mOutputFile = null;
+ public static final String PERIODIC_PROTO_WRITING_OPTION = "periodic-proto-writing";
+
@Option(
- name = "periodic-proto-writing",
+ name = PERIODIC_PROTO_WRITING_OPTION,
description =
"Whether or not to output intermediate proto per module following a numbered "
+ "sequence."
diff --git a/src/com/android/tradefed/result/proto/ProtoResultParser.java b/src/com/android/tradefed/result/proto/ProtoResultParser.java
index c715a99..18f9bca 100644
--- a/src/com/android/tradefed/result/proto/ProtoResultParser.java
+++ b/src/com/android/tradefed/result/proto/ProtoResultParser.java
@@ -18,6 +18,8 @@
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.invoker.InvocationContext;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
import com.android.tradefed.invoker.proto.InvocationContext.Context;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
@@ -32,6 +34,7 @@
import com.android.tradefed.result.proto.TestRecordProto.ChildReference;
import com.android.tradefed.result.proto.TestRecordProto.TestRecord;
import com.android.tradefed.testtype.suite.ModuleDefinition;
+import com.android.tradefed.util.MultiMap;
import com.android.tradefed.util.proto.TestRecordProtoUtil;
import com.google.common.base.Strings;
@@ -65,6 +68,7 @@
private boolean mQuietParsing = true;
private boolean mInvocationStarted = false;
+ private boolean mInvocationEnded = false;
/** Ctor. */
public ProtoResultParser(
@@ -163,6 +167,11 @@
}
}
+ /** Returns whether or not the parsing reached an invocation ended. */
+ public boolean invocationEndedReached() {
+ return mInvocationEnded;
+ }
+
private void evalChildrenProto(List<ChildReference> children, boolean isInRun) {
for (ChildReference child : children) {
TestRecord childProto = child.getInlineTestRecord();
@@ -255,6 +264,7 @@
}
log("Invocation ended proto");
+ mInvocationEnded = true;
if (!mReportInvocation) {
CLog.d("Skipping invocation ended reporting.");
return;
@@ -470,7 +480,28 @@
return;
}
// Copy invocation attributes
- receiverContext.addInvocationAttributes(endInvocationContext.getAttributes());
+ MultiMap<String, String> attributes = endInvocationContext.getAttributes();
+ for (InvocationMetricKey key : InvocationMetricKey.values()) {
+ if (!attributes.containsKey(key.toString())) {
+ continue;
+ }
+ List<String> values = attributes.get(key.toString());
+ attributes.remove(key.toString());
+
+ for (String val : values) {
+ if (key.shouldAdd()) {
+ try {
+ InvocationMetricLogger.addInvocationMetrics(key, Long.parseLong(val));
+ } catch (NumberFormatException e) {
+ CLog.e("Key %s should have a number value, instead was: %s", key, val);
+ CLog.e(e);
+ }
+ } else {
+ InvocationMetricLogger.addInvocationMetrics(key, val);
+ }
+ }
+ }
+ receiverContext.addInvocationAttributes(attributes);
}
private void log(String format, Object... obj) {
diff --git a/src/com/android/tradefed/result/proto/ProtoResultReporter.java b/src/com/android/tradefed/result/proto/ProtoResultReporter.java
index f415b4c..2cd7a42 100644
--- a/src/com/android/tradefed/result/proto/ProtoResultReporter.java
+++ b/src/com/android/tradefed/result/proto/ProtoResultReporter.java
@@ -15,6 +15,7 @@
*/
package com.android.tradefed.result.proto;
+import com.android.tradefed.config.Option;
import com.android.tradefed.config.OptionClass;
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.log.LogUtil.CLog;
@@ -28,6 +29,7 @@
import com.android.tradefed.result.proto.TestRecordProto.DebugInfo;
import com.android.tradefed.result.proto.TestRecordProto.TestRecord;
import com.android.tradefed.result.proto.TestRecordProto.TestStatus;
+import com.android.tradefed.result.retry.ISupportGranularResults;
import com.android.tradefed.testtype.suite.ModuleDefinition;
import com.android.tradefed.util.StreamUtil;
@@ -44,7 +46,15 @@
* extended to handle what to do with the final proto in {@link #processFinalProto(TestRecord)}.
*/
@OptionClass(alias = "proto-reporter")
-public abstract class ProtoResultReporter implements ITestInvocationListener, ILogSaverListener {
+public abstract class ProtoResultReporter
+ implements ITestInvocationListener, ILogSaverListener, ISupportGranularResults {
+
+ @Option(
+ name = "enable-granular-attempts",
+ description =
+ "Whether or not to allow this reporter receiving granular attempts. Feature flag."
+ )
+ private boolean mReportGranularResults = true;
private Stack<TestRecord.Builder> mLatestChild;
private TestRecord.Builder mInvocationRecordBuilder;
@@ -55,6 +65,11 @@
/** Whether or not a testModuleStart had currently been called. */
private boolean mModuleInProgress = false;
+ @Override
+ public boolean supportGranularResults() {
+ return mReportGranularResults;
+ }
+
/**
* Handling of the partial invocation test record proto after {@link
* #invocationStarted(IInvocationContext)} occurred.
diff --git a/src/com/android/tradefed/result/suite/FormattedGeneratorReporter.java b/src/com/android/tradefed/result/suite/FormattedGeneratorReporter.java
index 16216dd..bedd4a4 100644
--- a/src/com/android/tradefed/result/suite/FormattedGeneratorReporter.java
+++ b/src/com/android/tradefed/result/suite/FormattedGeneratorReporter.java
@@ -47,7 +47,9 @@
public void invocationFailed(Throwable cause) {
// Some exception indicate a harness level issue, the tests result cannot be trusted at
// that point so we should skip the reporting.
- if (cause instanceof TargetSetupError || cause instanceof RuntimeException) {
+ if (cause instanceof TargetSetupError
+ || cause instanceof RuntimeException
+ || cause instanceof OutOfMemoryError) {
mTestHarnessError = cause;
}
super.invocationFailed(cause);
diff --git a/src/com/android/tradefed/result/suite/SuiteResultReporter.java b/src/com/android/tradefed/result/suite/SuiteResultReporter.java
index 6fac2cc..5d5f74f 100644
--- a/src/com/android/tradefed/result/suite/SuiteResultReporter.java
+++ b/src/com/android/tradefed/result/suite/SuiteResultReporter.java
@@ -326,16 +326,17 @@
}
private void printModuleRetriesInformation() {
- if (mModuleRetrySuccess.isEmpty() || mTotalRetrySuccess == 0L) {
+ if (mModuleRetrySuccess.isEmpty() || mTotalRetryTime == 0L) {
return;
}
mSummary.append("============== Modules Retries Information ==============\n");
for (String t : mModuleRetrySuccess.keySet()) {
mSummary.append(
String.format(
- " %s: Retry Success (Failed test became Pass) = %s\n"
- + " Retry Failure (Fail test stayed Fail) = %s\n"
- + " Retry Time = %s\n",
+ " %s:\n"
+ + " Retry Success (Failed test became Pass) = %s\n"
+ + " Retry Failure (Failed test stayed Failed) = %s\n"
+ + " Retry Time = %s\n",
t,
mModuleRetrySuccess.get(t),
mModuleRetryFail.get(t),
@@ -345,8 +346,8 @@
mSummary.append(
String.format(
"Total Retry Success (Failed test became Pass) = %s\n"
- + "Total Retry Failure (Fail test stayed Fail) = %s\n"
- + "Total Retry Time = %s\n",
+ + "Total Retry Failure (Failed test stayed Failed) = %s\n"
+ + "Total Retry Time = %s\n",
mTotalRetrySuccess,
mTotalRetryFail,
TimeUtil.formatElapsedTime(mTotalRetryTime)));
diff --git a/src/com/android/tradefed/sandbox/TradefedSandbox.java b/src/com/android/tradefed/sandbox/TradefedSandbox.java
index eafa82c..a2dbc0b 100644
--- a/src/com/android/tradefed/sandbox/TradefedSandbox.java
+++ b/src/com/android/tradefed/sandbox/TradefedSandbox.java
@@ -323,6 +323,8 @@
createClasspath(mRootFolder), mRunUtil, args, mode, mGlobalConfig);
} catch (SandboxConfigurationException e) {
// TODO: Improve our detection of that scenario
+ CLog.e(e);
+ CLog.e("%s", args[0]);
if (e.getMessage().contains(String.format("Can not find local config %s", args[0]))
|| e.getMessage()
.contains(
@@ -431,11 +433,12 @@
private File handleChildMissingConfig(String[] args) {
IConfiguration parentConfig = null;
+ File tmpParentConfig = null;
+ PrintWriter pw = null;
try {
+ tmpParentConfig = FileUtil.createTempFile("parent-config", ".xml", mSandboxTmpFolder);
+ pw = new PrintWriter(tmpParentConfig);
parentConfig = ConfigurationFactory.getInstance().createConfigurationFromArgs(args);
- File tmpParentConfig =
- FileUtil.createTempFile("parent-config", ".xml", mSandboxTmpFolder);
- PrintWriter pw = new PrintWriter(tmpParentConfig);
// Do not print deprecated options to avoid compatibility issues, and do not print
// unchanged options.
parentConfig.dumpXml(pw, new ArrayList<>(), false, false);
@@ -443,7 +446,10 @@
} catch (ConfigurationException | IOException e) {
CLog.e("Parent doesn't understand the command either:");
CLog.e(e);
+ FileUtil.deleteFile(tmpParentConfig);
return null;
+ } finally {
+ StreamUtil.close(pw);
}
}
}
diff --git a/src/com/android/tradefed/suite/checker/SystemServerFileDescriptorChecker.java b/src/com/android/tradefed/suite/checker/SystemServerFileDescriptorChecker.java
index 839be17..c058d48 100644
--- a/src/com/android/tradefed/suite/checker/SystemServerFileDescriptorChecker.java
+++ b/src/com/android/tradefed/suite/checker/SystemServerFileDescriptorChecker.java
@@ -16,6 +16,7 @@
package com.android.tradefed.suite.checker;
import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.DeviceProperties;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.suite.checker.StatusCheckerResult.CheckStatus;
@@ -25,7 +26,6 @@
/** Process will fail to allocate beyond 1024, so heuristic considers 900 a bad state */
private static final int MAX_EXPECTED_FDS = 900;
- private static final String BUILD_TYPE_PROP = "ro.build.type";
private static final String USER_BUILD = "user";
private String mBuildType = null;
@@ -35,7 +35,7 @@
throws DeviceNotAvailableException {
if (mBuildType == null) {
// build type not initialized yet, check on device
- mBuildType = device.getProperty(BUILD_TYPE_PROP);
+ mBuildType = device.getProperty(DeviceProperties.BUILD_TYPE);
}
return new StatusCheckerResult(CheckStatus.SUCCESS);
}
diff --git a/src/com/android/tradefed/suite/checker/SystemServerStatusChecker.java b/src/com/android/tradefed/suite/checker/SystemServerStatusChecker.java
index 87366e9..537626b 100644
--- a/src/com/android/tradefed/suite/checker/SystemServerStatusChecker.java
+++ b/src/com/android/tradefed/suite/checker/SystemServerStatusChecker.java
@@ -16,13 +16,14 @@
package com.android.tradefed.suite.checker;
import com.android.annotations.VisibleForTesting;
+import com.android.tradefed.config.Option;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.suite.checker.StatusCheckerResult.CheckStatus;
import com.android.tradefed.util.ProcessInfo;
+import com.android.tradefed.util.StreamUtil;
-import java.util.Map;
/**
* Check if the pid of system_server has changed from before and after a module run. A new pid would
@@ -30,8 +31,14 @@
*/
public class SystemServerStatusChecker implements ISystemStatusChecker {
+ @Option(
+ name = "disable-recovery-reboot",
+ description =
+ "If status checker is detected down (no process), attempt to reboot the device."
+ )
+ private boolean mShouldRecover = true;
+
private ProcessInfo mSystemServerProcess;
- private Long mModuleStartTime = null;
/** {@inheritDoc} */
@Override
@@ -40,15 +47,16 @@
mSystemServerProcess = device.getProcessByName("system_server");
StatusCheckerResult result = new StatusCheckerResult(CheckStatus.SUCCESS);
if (mSystemServerProcess == null) {
+ if (mShouldRecover) {
+ device.reboot();
+ }
String message = "No valid system_server process is found.";
CLog.w(message);
result.setStatus(CheckStatus.FAILED);
result.setBugreportNeeded(true);
result.setErrorMessage(message);
- mModuleStartTime = null;
return result;
}
- mModuleStartTime = getCurrentTime();
return result;
}
@@ -62,48 +70,21 @@
+ "skipping system_server postExecutionCheck.");
return new StatusCheckerResult(CheckStatus.SUCCESS);
}
- String message = null;
- ProcessInfo currSystemServerProcess = device.getProcessByName("system_server");
- if (currSystemServerProcess == null) {
- message = "system_server is down";
- CLog.w(message);
+ try {
+ if (!device.deviceSoftRestarted(mSystemServerProcess)) {
+ return new StatusCheckerResult(CheckStatus.SUCCESS);
+ }
+ } catch (RuntimeException e) {
+ CLog.w(StreamUtil.getStackTrace(e));
StatusCheckerResult result = new StatusCheckerResult(CheckStatus.FAILED);
result.setBugreportNeeded(true);
- result.setErrorMessage(message);
+ result.setErrorMessage(StreamUtil.getStackTrace(e));
return result;
}
- if (currSystemServerProcess.getPid() == mSystemServerProcess.getPid()
- && currSystemServerProcess.getStartTime() == mSystemServerProcess.getStartTime()) {
- return new StatusCheckerResult(CheckStatus.SUCCESS);
- }
- //system_server restarted
- Map<Long, String> bootHistory =
- device.getBootHistorySince(mSystemServerProcess.getStartTime());
- CLog.i("The device reboot with boot history: %s", bootHistory);
- if (bootHistory.isEmpty()) {
- message = "system_server restarted without device reboot";
- } else {
- message = "system_server restarted with device boot history: " + bootHistory.toString();
- // Check if there is a TF triggered reboot with device.doReboot
- long lastExpectedReboot = device.getLastExpectedRebootTimeMillis();
- if (mModuleStartTime != null && lastExpectedReboot < mModuleStartTime) {
- // The reboot is not triggered by Tradefed host.
- CLog.w(
- "System_server restarted and Tradefed didn't trigger a reboot: "
- + "last expected reboot: %s, module start time: %s, "
- + "something went wrong.",
- lastExpectedReboot, mModuleStartTime);
- } else {
- // The reboot is triggered by Tradefed host
- CLog.i("Tradefed triggered reboot detected");
- return new StatusCheckerResult(CheckStatus.SUCCESS);
- }
- }
- CLog.w(message);
StatusCheckerResult result = new StatusCheckerResult(CheckStatus.FAILED);
result.setBugreportNeeded(true);
- result.setErrorMessage(message);
+ result.setErrorMessage("The system-server crashed during test execution");
return result;
}
diff --git a/src/com/android/tradefed/targetprep/AbstractTargetCleaner.java b/src/com/android/tradefed/targetprep/AbstractTargetCleaner.java
deleted file mode 100644
index af1894c..0000000
--- a/src/com/android/tradefed/targetprep/AbstractTargetCleaner.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tradefed.targetprep;
-
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.device.ITestDevice;
-
-/**
- * An {@link ITargetCleaner} class with a stub {@link #setUp} method
- */
-public abstract class AbstractTargetCleaner implements ITargetCleaner {
-
- /**
- * Implementation is a no-op
- *
- * {@inheritDoc}
- */
- @Override
- public void setUp(ITestDevice device, IBuildInfo buildInfo) {
- return;
- }
-}
diff --git a/src/com/android/tradefed/targetprep/BaseTargetPreparer.java b/src/com/android/tradefed/targetprep/BaseTargetPreparer.java
index b0d0ee4..ccb3bb7 100644
--- a/src/com/android/tradefed/targetprep/BaseTargetPreparer.java
+++ b/src/com/android/tradefed/targetprep/BaseTargetPreparer.java
@@ -16,6 +16,7 @@
package com.android.tradefed.targetprep;
import com.android.tradefed.config.Option;
+import com.android.tradefed.config.OptionCopier;
/**
* Base implementation class for {@link ITargetPreparer} that allows to control whether the object
@@ -23,11 +24,15 @@
*/
public abstract class BaseTargetPreparer implements ITargetPreparer {
- @Option(name = "disable", description = "disables the target preparer")
+ private static final String DISABLE_OPTION_NAME = "disable";
+
+ @Option(name = DISABLE_OPTION_NAME, description = "disables the target preparer")
private boolean mDisable = false;
+ private static final String DISABLE_TEARDOWN_OPTION_NAME = "disable-tear-down";
+
@Option(
- name = "disable-tear-down",
+ name = DISABLE_TEARDOWN_OPTION_NAME,
description = "disables the clean up step of a target cleaner"
)
private boolean mDisableTearDown = false;
@@ -48,11 +53,15 @@
@Override
public final void setDisable(boolean isDisabled) {
mDisable = isDisabled;
+ // Update the option this way to mark it as modified.
+ OptionCopier.copyOptionsNoThrow(this, this, DISABLE_OPTION_NAME);
}
/** {@inheritDoc} */
@Override
public final void setDisableTearDown(boolean isDisabled) {
mDisableTearDown = isDisabled;
+ // Update the option this way to mark it as modified.
+ OptionCopier.copyOptionsNoThrow(this, this, DISABLE_TEARDOWN_OPTION_NAME);
}
}
diff --git a/src/com/android/tradefed/targetprep/BuildInfoAttributePreparer.java b/src/com/android/tradefed/targetprep/BuildInfoAttributePreparer.java
deleted file mode 100644
index 307affc..0000000
--- a/src/com/android/tradefed/targetprep/BuildInfoAttributePreparer.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tradefed.targetprep;
-
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.config.Option;
-import com.android.tradefed.config.OptionClass;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/** A {@link ITargetPreparer} that adds arbitrary attributes to the {@link IBuildInfo}. */
-@OptionClass(alias = "buildinfo-preparer")
-public class BuildInfoAttributePreparer extends BaseTargetPreparer {
-
- @Option(name = "build-attribute", description = "build attributes to add")
- private Map<String, String> mBuildAttributes = new HashMap<String, String>();
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError,
- BuildError, DeviceNotAvailableException {
- for (Map.Entry<String, String> attr : mBuildAttributes.entrySet()) {
- String key = attr.getKey();
- String value = attr.getValue();
- buildInfo.addBuildAttribute(key, value);
- }
- }
-}
diff --git a/src/com/android/tradefed/targetprep/ConnectionChecker.java b/src/com/android/tradefed/targetprep/ConnectionChecker.java
deleted file mode 100644
index 7e50fc1..0000000
--- a/src/com/android/tradefed/targetprep/ConnectionChecker.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tradefed.targetprep;
-
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.config.Option;
-import com.android.tradefed.config.OptionClass;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.util.RunUtil;
-
-/** Target preparer that waits until an ip address is asigned to any of the specified interfaces. */
-@OptionClass(alias = "connection-checker")
-public class ConnectionChecker extends BaseTargetPreparer {
-
- @Option(name="max-wait", description="How long to wait for the device to connect, in seconds")
- private long mTimeout = 600;
-
- @Option(name="poll-interval", description="How often to poll the device, in seconds")
- private long mInterval = 30;
-
- @Override
- public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError,
- BuildError, DeviceNotAvailableException {
- long startTime = System.currentTimeMillis();
- long timeout = mTimeout * 1000;
- while (!device.checkConnectivity()) {
- if (System.currentTimeMillis() - startTime > timeout) {
- throw new TargetSetupError("Device did not connect to the network",
- device.getDeviceDescriptor());
- }
- RunUtil.getDefault().sleep(mInterval * 1000);
- }
- }
-}
diff --git a/src/com/android/tradefed/targetprep/DeviceBuildInfoBootStrapper.java b/src/com/android/tradefed/targetprep/DeviceBuildInfoBootStrapper.java
index e10ee76..fa667ef 100644
--- a/src/com/android/tradefed/targetprep/DeviceBuildInfoBootStrapper.java
+++ b/src/com/android/tradefed/targetprep/DeviceBuildInfoBootStrapper.java
@@ -15,6 +15,7 @@
*/
package com.android.tradefed.targetprep;
+import com.android.tradefed.build.BootstrapBuildProvider;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.config.Option;
import com.android.tradefed.device.DeviceNotAvailableException;
@@ -27,7 +28,8 @@
* <p>This is useful for testing devices with builds generated from an external source (e.g.
* external partner devices)
*
- * @see {@link DeviceBuildInfoInjector}, {@link BootstrapBuildProvider}
+ * @see DeviceBuildInfoInjector
+ * @see BootstrapBuildProvider
*/
public class DeviceBuildInfoBootStrapper extends BaseTargetPreparer {
diff --git a/src/com/android/tradefed/targetprep/DeviceBuildInfoInjector.java b/src/com/android/tradefed/targetprep/DeviceBuildInfoInjector.java
index 6e570e9..99a5f12 100644
--- a/src/com/android/tradefed/targetprep/DeviceBuildInfoInjector.java
+++ b/src/com/android/tradefed/targetprep/DeviceBuildInfoInjector.java
@@ -20,6 +20,7 @@
import com.android.tradefed.config.Option;
import com.android.tradefed.config.OptionClass;
import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.DeviceProperties;
import com.android.tradefed.device.ITestDevice;
/**
@@ -67,8 +68,11 @@
buildInfo.addBuildAttribute(DeviceBuildDescriptor.DEVICE_BUILD_FLAVOR,
mOverrideDeviceBuildFlavor);
} else {
- String buildFlavor = String.format("%s-%s", device.getProperty("ro.product.name"),
- device.getProperty("ro.build.type"));
+ String buildFlavor =
+ String.format(
+ "%s-%s",
+ device.getProperty(DeviceProperties.PRODUCT),
+ device.getProperty(DeviceProperties.BUILD_TYPE));
buildInfo.addBuildAttribute(DeviceBuildDescriptor.DEVICE_BUILD_FLAVOR, buildFlavor);
}
buildInfo.addBuildAttribute(DeviceBuildDescriptor.DEVICE_DESC,
diff --git a/src/com/android/tradefed/targetprep/DeviceFlashPreparer.java b/src/com/android/tradefed/targetprep/DeviceFlashPreparer.java
index 24d72d1..b77668a 100644
--- a/src/com/android/tradefed/targetprep/DeviceFlashPreparer.java
+++ b/src/com/android/tradefed/targetprep/DeviceFlashPreparer.java
@@ -470,4 +470,12 @@
void setShouldFlashRamdisk(boolean shouldFlashRamdisk) {
mShouldFlashRamdisk = shouldFlashRamdisk;
}
+
+ protected void setSkipPostFlashFlavorCheck(boolean skipPostFlashFlavorCheck) {
+ mSkipPostFlashFlavorCheck = skipPostFlashFlavorCheck;
+ }
+
+ protected void setSkipPostFlashBuildIdCheck(boolean skipPostFlashBuildIdCheck) {
+ mSkipPostFlashBuildIdCheck = skipPostFlashBuildIdCheck;
+ }
}
diff --git a/src/com/android/tradefed/targetprep/DeviceSetup.java b/src/com/android/tradefed/targetprep/DeviceSetup.java
index 9ac68ef..cdfe09a 100644
--- a/src/com/android/tradefed/targetprep/DeviceSetup.java
+++ b/src/com/android/tradefed/targetprep/DeviceSetup.java
@@ -23,6 +23,8 @@
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.StubDevice;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.util.BinaryState;
import com.android.tradefed.util.MultiMap;
@@ -889,14 +891,19 @@
}
if (mWifiSsid != null && device.connectToWifiNetwork(mWifiSsid, mWifiPsk)) {
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.WIFI_AP_NAME, mWifiSsid);
return;
}
for (Map.Entry<String, String> ssidToPsk : mWifiSsidToPsk.entrySet()) {
String psk = "".equals(ssidToPsk.getValue()) ? null : ssidToPsk.getValue();
if (device.connectToWifiNetwork(ssidToPsk.getKey(), psk)) {
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.WIFI_AP_NAME, ssidToPsk.getKey());
return;
}
}
+
// Error message does not acknowledge mWifiSsidToPsk for parity with existing monitoring.
if (mWifiSsid != null || !mWifiSsidToPsk.isEmpty()) {
throw new TargetSetupError(
diff --git a/src/com/android/tradefed/targetprep/FastbootDeviceFlasher.java b/src/com/android/tradefed/targetprep/FastbootDeviceFlasher.java
index c1a6f8f..185741c 100644
--- a/src/com/android/tradefed/targetprep/FastbootDeviceFlasher.java
+++ b/src/com/android/tradefed/targetprep/FastbootDeviceFlasher.java
@@ -416,11 +416,11 @@
/**
* Get the boot partition name for this device flasher.
- * <p/>
- * Defaults to 'hboot'. Subclasses should override if necessary.
+ *
+ * <p>Defaults to 'bootloader'. Subclasses should override if necessary.
*/
protected String getBootPartitionName() {
- return "hboot";
+ return "bootloader";
}
/**
diff --git a/src/com/android/tradefed/targetprep/FastbootUpdateBootstrapPreparer.java b/src/com/android/tradefed/targetprep/FastbootUpdateBootstrapPreparer.java
new file mode 100644
index 0000000..f6990d9
--- /dev/null
+++ b/src/com/android/tradefed/targetprep/FastbootUpdateBootstrapPreparer.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tradefed.targetprep;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.build.IDeviceBuildInfo;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.targetprep.IDeviceFlasher.UserDataFlashOption;
+import com.android.tradefed.util.BuildInfoUtil;
+
+import java.io.File;
+
+/**
+ * An {@link ITargetPreparer} that stages specified files (bootloader, radio, device image zip) into
+ * {@link IDeviceBuildInfo} to get devices flashed with {@link FastbootDeviceFlasher}, then injects
+ * post-boot device attributes into the build info for result reporting purposes.
+ *
+ * <p>This is useful for using <code>fastboot update</code> as device image update mechanism from
+ * externally sourced devices and builds, to fit into existing automation infrastructure.
+ */
+public class FastbootUpdateBootstrapPreparer extends DeviceFlashPreparer {
+
+ @Option(name = "bootloader-image", description = "bootloader image file to be used for update")
+ private File mBootloaderImage = null;
+
+ @Option(name = "baseband-image", description = "radio image file to be used for update")
+ private File mBasebandImage = null;
+
+ @Option(name = "device-image", description = "device image file to be used for update")
+ private File mDeviceImage = null;
+
+ @Option(
+ name = "bootstrap-build-info",
+ description =
+ "whether build info should be"
+ + "bootstrapped based on device attributes after flashing"
+ )
+ private boolean mBootStrapBuildInfo = true;
+
+ // parameters below are the same as DeviceBuildInfoBootStrapper
+ @Option(name = "override-device-build-id", description = "the device buid id to inject.")
+ private String mOverrideDeviceBuildId = null;
+
+ @Option(name = "override-device-build-alias", description = "the device buid alias to inject.")
+ private String mOverrideDeviceBuildAlias = null;
+
+ @Option(
+ name = "override-device-build-flavor",
+ description = "the device build flavor to inject."
+ )
+ private String mOverrideDeviceBuildFlavor = null;
+
+ @Option(
+ name = "override-device-build-branch",
+ description = "the device build branch to inject."
+ )
+ private String mOverrideDeviceBuildBranch = null;
+
+ @Override
+ public void setUp(ITestDevice device, IBuildInfo buildInfo)
+ throws TargetSetupError, BuildError, DeviceNotAvailableException {
+ if (!(buildInfo instanceof IDeviceBuildInfo)) {
+ throw new IllegalArgumentException("Provided build info must be a IDeviceBuildInfo");
+ }
+ // forcing the wipe mechanism to WIPE because the FLASH* based options are not feasible here
+ setUserDataFlashOption(UserDataFlashOption.WIPE);
+ IDeviceBuildInfo deviceBuildInfo = (IDeviceBuildInfo) buildInfo;
+ deviceBuildInfo.setBootloaderImageFile(mBootloaderImage, "0");
+ deviceBuildInfo.setBasebandImage(mBasebandImage, "0");
+ deviceBuildInfo.setDeviceImageFile(mDeviceImage, "0");
+ setSkipPostFlashBuildIdCheck(true);
+ setSkipPostFlashFlavorCheck(true);
+ // performs the actual flashing
+ super.setUp(device, buildInfo);
+
+ if (mBootStrapBuildInfo) {
+ BuildInfoUtil.bootstrapDeviceBuildAttributes(
+ buildInfo,
+ device,
+ mOverrideDeviceBuildId,
+ mOverrideDeviceBuildFlavor,
+ mOverrideDeviceBuildBranch,
+ mOverrideDeviceBuildAlias);
+ }
+ }
+
+ @Override
+ protected IDeviceFlasher createFlasher(ITestDevice device) throws DeviceNotAvailableException {
+ return new FastbootDeviceFlasher();
+ }
+}
diff --git a/src/com/android/tradefed/targetprep/FileCleaner.java b/src/com/android/tradefed/targetprep/FileCleaner.java
deleted file mode 100644
index 72eccd5..0000000
--- a/src/com/android/tradefed/targetprep/FileCleaner.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tradefed.targetprep;
-
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.config.Option;
-import com.android.tradefed.config.Option.Importance;
-import com.android.tradefed.config.OptionClass;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.util.FileUtil;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collection;
-
-/** A {@link ITargetCleaner} that removes filesystem files on teardown */
-@OptionClass(alias = "file-cleaner")
-public class FileCleaner extends BaseTargetPreparer implements ITargetCleaner {
-
- @Option(name = "apk-path", description =
- "the filesystem path of the apk to cleanup. Can be repeated.",
- importance = Importance.IF_UNSET)
- private Collection<File> mApkPaths = new ArrayList<File>();
-
- @Override
- public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError,
- BuildError, DeviceNotAvailableException {
- // ignore
-
- }
-
- @Override
- public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
- throws DeviceNotAvailableException {
- for (File file : mApkPaths) {
- FileUtil.deleteFile(file);
- }
- }
-}
diff --git a/src/com/android/tradefed/targetprep/PreloadedClassesPreparer.java b/src/com/android/tradefed/targetprep/PreloadedClassesPreparer.java
index 6f4f7ad..b14e00c 100644
--- a/src/com/android/tradefed/targetprep/PreloadedClassesPreparer.java
+++ b/src/com/android/tradefed/targetprep/PreloadedClassesPreparer.java
@@ -1,47 +1,17 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
package com.android.tradefed.targetprep;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.config.Option;
-import com.android.tradefed.config.OptionClass;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.util.CommandResult;
-import com.android.tradefed.util.CommandStatus;
-import com.android.tradefed.util.IRunUtil;
-import com.android.tradefed.util.RunUtil;
-
-import com.google.common.annotations.VisibleForTesting;
import java.io.File;
-/**
- * A {@link ITargetPreparer} that replaces the preloaded classes file on a device.
- *
- * <p>Note that this preparer requires a rooted, debug build to work.
- */
-@OptionClass(alias = "preloaded-classes-preparer")
+/** @deprecated Delete after July 29th week deployment */
+@Deprecated
public class PreloadedClassesPreparer extends BaseTargetPreparer {
- // Preload tool commands
- private static final String TOOL_CMD = "java -cp %s com.android.preload.Main --seq %s %s";
- private static final String WRITE_CMD = "write %s";
- // Defaults
- public static final long DEFAULT_TIMEOUT_MS = 5 * 60 * 1000;
+
+ private static final long DEFAULT_TIMEOUT_MS = 5 * 60 * 1000;
@Option(
name = "preload-file",
@@ -62,75 +32,9 @@
)
private long mWriteTimeout = DEFAULT_TIMEOUT_MS;
- /** {@inheritDoc} */
@Override
public void setUp(ITestDevice device, IBuildInfo buildInfo)
throws TargetSetupError, BuildError, DeviceNotAvailableException {
- if (mSkip) {
- return;
- } else if (getPreloadedClassesPath().isEmpty()) {
- CLog.w("No preloaded classes file specified. Skipping preparer.");
- return;
- }
-
- // Download preload tool, if not supplied
- if (getPreloadToolPath().isEmpty()) {
- File preload = buildInfo.getFile("preload2.jar");
- if (preload != null && preload.exists()) {
- setPreloadToolPath(preload.getAbsolutePath());
- } else {
- throw new TargetSetupError(
- "Unable to find the preload tool.", device.getDeviceDescriptor());
- }
- }
-
- // Root, disable verity, and remount
- device.enableAdbRoot();
- device.remountSystemWritable();
- // Root again after rebooting
- device.enableAdbRoot();
- // Construct the command
- String exportCmd = String.format(WRITE_CMD, getPreloadedClassesPath());
- String[] fullCmd =
- String.format(TOOL_CMD, getPreloadToolPath(), device.getSerialNumber(), exportCmd)
- .split(" ");
- CommandResult result = getRunUtil().runTimedCmd(mWriteTimeout, fullCmd);
- if (!CommandStatus.SUCCESS.equals(result.getStatus())) {
- throw new TargetSetupError(
- String.format("Error writing: %s", result.getStderr()),
- device.getDeviceDescriptor());
- }
- // Wait for the device to be reconnected
- device.waitForDeviceAvailable();
- }
-
- /**
- * Get the {@link IRunUtil} instance to use.
- *
- * <p>Exposed so unit tests can mock.
- */
- @VisibleForTesting
- protected IRunUtil getRunUtil() {
- return RunUtil.getDefault();
- }
-
- /**
- * Get the preloaded classes file.
- *
- * <p>Exposed so unit tests can mock.
- */
- @VisibleForTesting
- protected String getPreloadedClassesPath() {
- return (mNewClassesFile != null) ? mNewClassesFile.getAbsolutePath() : "";
- }
-
- /** Get the preload tool path. */
- protected String getPreloadToolPath() {
- return mPreloadToolJarPath;
- }
-
- /** Set the preload tool path. */
- protected void setPreloadToolPath(String path) {
- mPreloadToolJarPath = path;
+ // Inop
}
}
diff --git a/src/com/android/tradefed/targetprep/README.md b/src/com/android/tradefed/targetprep/README.md
new file mode 100644
index 0000000..5543432
--- /dev/null
+++ b/src/com/android/tradefed/targetprep/README.md
@@ -0,0 +1,9 @@
+# Core Trade Federation Target Preparers interface
+
+This folder contains the core interfaces that describes a target (device)
+preparer in Tradefed.
+It also contains some base implementation that are used for documentation
+and self validation use cases.
+
+More specialized implementation can be located at:
+platform/tools/tradefederation/core/test_framework/
diff --git a/src/com/android/tradefed/targetprep/TimeSetterTargetPreparer.java b/src/com/android/tradefed/targetprep/TimeSetterTargetPreparer.java
deleted file mode 100644
index bfccf8a..0000000
--- a/src/com/android/tradefed/targetprep/TimeSetterTargetPreparer.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tradefed.targetprep;
-
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.config.Option;
-import com.android.tradefed.config.OptionClass;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-
-import java.util.Date;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Target preparer to restore the correct time to the device on cleanup. This allows tests to modify
- * the time however they like since the original time will be restored, making sure the state of the
- * device isn't changed at the end.
- *
- * <p>Can also optionally set the time on setup. The time restored on cleanup will be the time set
- * before this target preparer ran.
- */
-@OptionClass(alias = "time-setter")
-public class TimeSetterTargetPreparer extends BaseTargetPreparer implements ITargetCleaner {
- @Option(
- name = "time",
- description = "Time to set (epoch time in milliseconds).",
- mandatory = true
- )
- private Long mTimeToSet;
-
- private long mStartNanoTime, mDeviceStartTimeMillis;
-
- // Exposed for testing.
- long getNanoTime() {
- return System.nanoTime();
- }
-
- private void setDeviceTime(ITestDevice device, long time) throws DeviceNotAvailableException {
- device.setDate(new Date(time));
- }
-
- @Override
- public void setUp(ITestDevice device, IBuildInfo buildInfo)
- throws TargetSetupError, BuildError, DeviceNotAvailableException {
- if (mTimeToSet == null) {
- return;
- }
- mStartNanoTime = getNanoTime();
- mDeviceStartTimeMillis = device.getDeviceDate();
- setDeviceTime(device, mTimeToSet);
- }
-
- @Override
- public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
- throws DeviceNotAvailableException {
- if (mTimeToSet == null) {
- return;
- }
- long elapsedNanos = getNanoTime() - mStartNanoTime;
- long newTime = mDeviceStartTimeMillis + TimeUnit.NANOSECONDS.toMillis(elapsedNanos);
- setDeviceTime(device, newTime);
- }
-}
diff --git a/src/com/android/tradefed/targetprep/multi/README.md b/src/com/android/tradefed/targetprep/multi/README.md
new file mode 100644
index 0000000..e1a46dc
--- /dev/null
+++ b/src/com/android/tradefed/targetprep/multi/README.md
@@ -0,0 +1,9 @@
+# Core Trade Federation Multi Target Preparers interface
+
+This folder contains the core interfaces that describes a multi-target
+preparers in Tradefed.
+It also contains some base implementation that are used for documentation
+and self validation use cases.
+
+More specialized implementation can be located at:
+platform/tools/tradefederation/core/test_framework/
diff --git a/src/com/android/tradefed/testtype/README.md b/src/com/android/tradefed/testtype/README.md
new file mode 100644
index 0000000..741e21e
--- /dev/null
+++ b/src/com/android/tradefed/testtype/README.md
@@ -0,0 +1,8 @@
+# Core Trade Federation Tests Interfaces
+
+This folder contains the core interfaces that describes a test in TradeFed.
+It also contains some base implementation that are used automatically and
+for self validation tests.
+
+More specialized implementation can be located at:
+platform/tools/tradefederation/core/test_framework/
diff --git a/src/com/android/tradefed/testtype/StubTest.java b/src/com/android/tradefed/testtype/StubTest.java
index 05cfd78..9daf578 100644
--- a/src/com/android/tradefed/testtype/StubTest.java
+++ b/src/com/android/tradefed/testtype/StubTest.java
@@ -17,6 +17,8 @@
package com.android.tradefed.testtype;
import com.android.ddmlib.Log.LogLevel;
+import com.android.tradefed.config.IConfiguration;
+import com.android.tradefed.config.IConfigurationReceiver;
import com.android.tradefed.config.Option;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.DeviceUnresponsiveException;
@@ -30,10 +32,8 @@
import java.util.LinkedHashMap;
import java.util.List;
-/**
- * No-op empty test implementation.
- */
-public class StubTest implements IShardableTest {
+/** No-op empty test implementation. */
+public class StubTest implements IShardableTest, IConfigurationReceiver {
public static final String DNAE_MESSAGE = "StubTest DeviceNotAvailableException";
@@ -73,6 +73,8 @@
)
private boolean mRunTest = false;
+ private IConfiguration mConfig;
+
/**
* {@inheritDoc}
*/
@@ -111,4 +113,13 @@
}
return null;
}
+
+ @Override
+ public void setConfiguration(IConfiguration configuration) {
+ mConfig = configuration;
+ }
+
+ public IConfiguration getConfiguration() {
+ return mConfig;
+ }
}
diff --git a/src/com/android/tradefed/testtype/retry/BaseRetryDecision.java b/src/com/android/tradefed/testtype/retry/BaseRetryDecision.java
index 862dd36..37607b6 100644
--- a/src/com/android/tradefed/testtype/retry/BaseRetryDecision.java
+++ b/src/com/android/tradefed/testtype/retry/BaseRetryDecision.java
@@ -15,15 +15,23 @@
*/
package com.android.tradefed.testtype.retry;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.device.StubDevice;
+import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.TestDescription;
import com.android.tradefed.result.TestRunResult;
+import com.android.tradefed.retry.RetryStrategy;
import com.android.tradefed.testtype.IRemoteTest;
import com.android.tradefed.testtype.ITestFilterReceiver;
+import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.stream.Collectors;
/**
* Base implementation of {@link IRetryDecision}. Base implementation only take local signals into
@@ -31,17 +39,67 @@
*/
public class BaseRetryDecision implements IRetryDecision {
- private RetryStrategy mRetryStrategy;
+ @Option(
+ name = "reboot-at-last-retry",
+ description = "Reboot the device at the last retry attempt."
+ )
+ private boolean mRebootAtLastRetry = false;
+
+ @Option(
+ name = "max-testcase-run-count",
+ description =
+ "If the IRemoteTest can have its testcases run multiple times, "
+ + "the max number of runs for each testcase."
+ )
+ private int mMaxRetryAttempts = 1;
+
+ @Option(
+ name = "retry-strategy",
+ description =
+ "The retry strategy to be used when re-running some tests with "
+ + "--max-testcase-run-count"
+ )
+ private RetryStrategy mRetryStrategy = RetryStrategy.NO_RETRY;
+
+ @Option(
+ name = "auto-retry",
+ description =
+ "Whether or not to enable the new auto-retry. This is a feature flag for testing."
+ )
+ private boolean mEnableAutoRetry = false;
+
+ private IInvocationContext mContext;
+
private IRemoteTest mCurrentlyConsideredTest;
private RetryStatsHelper mStatistics;
- /** Constructor for the retry decision, always based on the {@link RetryStrategy}. */
- public BaseRetryDecision(RetryStrategy strategy) {
- mRetryStrategy = strategy;
+ /** Constructor for the retry decision */
+ public BaseRetryDecision() {}
+
+ @Override
+ public boolean isAutoRetryEnabled() {
+ return mEnableAutoRetry;
}
@Override
- public boolean shouldRetry(IRemoteTest test, List<TestRunResult> previousResults) {
+ public RetryStrategy getRetryStrategy() {
+ return mRetryStrategy;
+ }
+
+ @Override
+ public int getMaxRetryCount() {
+ return mMaxRetryAttempts;
+ }
+
+ @Override
+ public void setInvocationContext(IInvocationContext context) {
+ mContext = context;
+ }
+
+ @Override
+ public boolean shouldRetry(
+ IRemoteTest test, int attemptJustExecuted, List<TestRunResult> previousResults)
+ throws DeviceNotAvailableException {
// Keep track of some results for the test in progress for statistics purpose.
if (test != mCurrentlyConsideredTest) {
mCurrentlyConsideredTest = test;
@@ -74,7 +132,12 @@
// TODO(b/77548917): Right now we only support ITestFilterReceiver. We should expect to
// support ITestFile*Filter*Receiver in the future.
ITestFilterReceiver filterableTest = (ITestFilterReceiver) test;
- return handleRetryFailures(filterableTest, previousResults);
+ boolean shouldRetry = handleRetryFailures(filterableTest, previousResults);
+ if (shouldRetry) {
+ // In case of retry, go through the recovery routine
+ recoverStateOfDevices(getDevices(), attemptJustExecuted);
+ }
+ return shouldRetry;
}
@Override
@@ -83,7 +146,10 @@
}
@Override
- public RetryStatistics getRetryStats() {
+ public RetryStatistics getRetryStatistics() {
+ if (mStatistics == null) {
+ return new RetryStatsHelper().calculateStatistics();
+ }
return mStatistics.calculateStatistics();
}
@@ -146,4 +212,25 @@
test.addIncludeFilter(filter);
}
}
+
+ /** Returns all the non-stub device associated with the {@link IRemoteTest}. */
+ private List<ITestDevice> getDevices() {
+ List<ITestDevice> listDevices = new ArrayList<>(mContext.getDevices());
+ // Return all the non-stub device (the one we can actually do some recovery against)
+ return listDevices
+ .stream()
+ .filter(d -> (!(d.getIDevice() instanceof StubDevice)))
+ .collect(Collectors.toList());
+ }
+
+ /** Recovery attempt on the device to get it a better state before next retry. */
+ private void recoverStateOfDevices(List<ITestDevice> devices, int lastAttempt)
+ throws DeviceNotAvailableException {
+ for (ITestDevice device : devices) {
+ if (mRebootAtLastRetry && (lastAttempt == (mMaxRetryAttempts - 2))) {
+ device.reboot();
+ continue;
+ }
+ }
+ }
}
diff --git a/src/com/android/tradefed/testtype/retry/IRetryDecision.java b/src/com/android/tradefed/testtype/retry/IRetryDecision.java
index 808520f..a8cc116 100644
--- a/src/com/android/tradefed/testtype/retry/IRetryDecision.java
+++ b/src/com/android/tradefed/testtype/retry/IRetryDecision.java
@@ -15,7 +15,10 @@
*/
package com.android.tradefed.testtype.retry;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.result.TestRunResult;
+import com.android.tradefed.retry.RetryStrategy;
import com.android.tradefed.testtype.IRemoteTest;
import java.util.List;
@@ -26,18 +29,34 @@
*/
public interface IRetryDecision {
+ /** Whether or not to enable auto-retry. */
+ public boolean isAutoRetryEnabled();
+
+ /** The {@link RetryStrategy} used during auto-retry. */
+ public RetryStrategy getRetryStrategy();
+
+ /** The maximum number of attempts during auto-retry. */
+ public int getMaxRetryCount();
+
+ /** Set the current invocation context. */
+ public void setInvocationContext(IInvocationContext context);
+
/**
* Decide whether or not retry should be attempted. Also make any necessary changes to the
* {@link IRemoteTest} to be retried (Applying filters, etc.).
*
* @param test The {@link IRemoteTest} that just ran.
+ * @param attemptJustExecuted The number of the attempt that we just ran.
* @param previousResults The list of {@link TestRunResult} of the test that just ran.
* @return True if we should retry, False otherwise.
+ * @throws DeviceNotAvailableException Can be thrown during device recovery
*/
- public boolean shouldRetry(IRemoteTest test, List<TestRunResult> previousResults);
+ public boolean shouldRetry(
+ IRemoteTest test, int attemptJustExecuted, List<TestRunResult> previousResults)
+ throws DeviceNotAvailableException;
/**
- * {@link #shouldRetry(IRemoteTest, List)} will most likely be called before the last retry
+ * {@link #shouldRetry(IRemoteTest, int, List)} will most likely be called before the last retry
* attempt, so we might be missing the very last attempt results for statistics purpose. This
* method allows those results to be provided for proper statistics calculations.
*
@@ -46,5 +65,5 @@
public void addLastAttempt(List<TestRunResult> lastResults);
/** Returns the {@link RetryStatistics} representing the retry. */
- public RetryStatistics getRetryStats();
+ public RetryStatistics getRetryStatistics();
}
diff --git a/src/com/android/tradefed/testtype/retry/ResultAggregator.java b/src/com/android/tradefed/testtype/retry/ResultAggregator.java
index b8ed0bc..5ba348b 100644
--- a/src/com/android/tradefed/testtype/retry/ResultAggregator.java
+++ b/src/com/android/tradefed/testtype/retry/ResultAggregator.java
@@ -16,6 +16,8 @@
package com.android.tradefed.testtype.retry;
import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
import com.android.tradefed.result.CollectingTestListener;
import com.android.tradefed.result.ILogSaver;
@@ -29,6 +31,10 @@
import com.android.tradefed.result.TestResult;
import com.android.tradefed.result.TestRunResult;
import com.android.tradefed.result.retry.ISupportGranularResults;
+import com.android.tradefed.retry.MergeStrategy;
+import com.android.tradefed.retry.RetryStrategy;
+
+import com.google.api.client.repackaged.com.google.common.base.Joiner;
import java.util.ArrayList;
import java.util.HashMap;
@@ -56,6 +62,10 @@
private boolean mModuleInProgress = false;
// Stores the results from non-module test runs until they are ready to be replayed.
private List<TestRunResult> mPureRunResults = new ArrayList<>();
+ //
+ private TestRunResult mDetailedRunResults = null;
+ private boolean mShouldReportFailure = true;
+ private List<String> mAllDetailedFailures = new ArrayList<>();
public ResultAggregator(List<ITestInvocationListener> listeners, RetryStrategy strategy) {
mAllForwarder = new ResultAndLogForwarder(listeners);
@@ -108,10 +118,23 @@
forwardTestRunResults(mPureRunResults, mAggregatedForwarder);
mPureRunResults.clear();
}
+ forwardDetailedFailure();
super.invocationEnded(elapsedTime);
+ // Make sure to forward the logs for the invocation.
+ forwardAggregatedInvocationLogs();
mAllForwarder.invocationEnded(elapsedTime);
}
+ /**
+ * Forward all the invocation level logs to the result reporters that don't support the granular
+ * results.
+ */
+ public final void forwardAggregatedInvocationLogs() {
+ for (Entry<String, LogFile> invocLog : getNonAssociatedLogFiles().entrySet()) {
+ mAggregatedForwarder.logAssociation(invocLog.getKey(), invocLog.getValue());
+ }
+ }
+
/** {@inheritDoc} */
@Override
public void testModuleStarted(IInvocationContext moduleContext) {
@@ -120,6 +143,11 @@
mPureRunResults.clear();
}
+ if (mDetailedRunResults != null) {
+ mShouldReportFailure = true;
+ forwardDetailedFailure();
+ }
+
mModuleInProgress = true;
super.testModuleStarted(moduleContext);
mAllForwarder.testModuleStarted(moduleContext);
@@ -140,6 +168,23 @@
forwardTestRunResults(mPureRunResults, mAggregatedForwarder);
mPureRunResults.clear();
}
+
+ if (mDetailedRunResults != null) {
+ if (mDetailedRunResults.getName().equals(name)) {
+ if (!mDetailedRunResults.isRunFailure()) {
+ if (RetryStrategy.RETRY_ANY_FAILURE.equals(mRetryStrategy)) {
+ mShouldReportFailure = false;
+ }
+ }
+ mDetailedForwarder.testRunEnded(
+ mDetailedRunResults.getElapsedTime(),
+ mDetailedRunResults.getRunProtoMetrics());
+ mDetailedRunResults = null;
+ } else {
+ mShouldReportFailure = true;
+ forwardDetailedFailure();
+ }
+ }
super.testRunStarted(name, testCount, attemptNumber, startTime);
mDetailedForwarder.testRunStarted(name, testCount, attemptNumber, startTime);
}
@@ -147,7 +192,7 @@
@Override
public void testRunFailed(String errorMessage) {
super.testRunFailed(errorMessage);
- mDetailedForwarder.testRunFailed(errorMessage);
+ // Don't forward here to the detailed forwarder in case we need to clear it.
}
@Override
@@ -198,7 +243,10 @@
@Override
public void testRunEnded(long elapsedTime, HashMap<String, Metric> runMetrics) {
super.testRunEnded(elapsedTime, runMetrics);
- mDetailedForwarder.testRunEnded(elapsedTime, runMetrics);
+ mDetailedRunResults = getCurrentRunResults();
+ if (mDetailedRunResults.isRunFailure()) {
+ mAllDetailedFailures.add(mDetailedRunResults.getRunFailureMessage());
+ }
// If we are not a module and we reach here. This allows to support non-suite scenarios
if (!mModuleInProgress) {
@@ -209,6 +257,8 @@
@Override
public void testModuleEnded() {
+ forwardDetailedFailure();
+
mModuleInProgress = false;
super.testModuleEnded();
// We still forward the testModuleEnd to the detailed reporters
@@ -307,4 +357,27 @@
// Ensure we don't keep track of the results we just forwarded
clearResultsForName(result.getName());
}
+
+ private void forwardDetailedFailure() {
+ if (mDetailedRunResults != null) {
+ if (mDetailedRunResults.isRunFailure() && mShouldReportFailure) {
+ mDetailedForwarder.testRunFailed(Joiner.on("\n\n").join(mAllDetailedFailures));
+ } else {
+ // Log the run failure that was cleared
+ String value =
+ InvocationMetricLogger.getInvocationMetrics()
+ .get(InvocationMetricKey.CLEARED_RUN_ERROR.toString());
+ if (value != null) {
+ mAllDetailedFailures.add(0, value);
+ }
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.CLEARED_RUN_ERROR,
+ Joiner.on("\n\n").join(mAllDetailedFailures));
+ }
+ mAllDetailedFailures.clear();
+ mDetailedForwarder.testRunEnded(
+ mDetailedRunResults.getElapsedTime(), mDetailedRunResults.getRunProtoMetrics());
+ mDetailedRunResults = null;
+ }
+ }
}
diff --git a/src/com/android/tradefed/testtype/retry/RetryStatistics.java b/src/com/android/tradefed/testtype/retry/RetryStatistics.java
index 4417928..f4f5e99 100644
--- a/src/com/android/tradefed/testtype/retry/RetryStatistics.java
+++ b/src/com/android/tradefed/testtype/retry/RetryStatistics.java
@@ -15,6 +15,7 @@
*/
package com.android.tradefed.testtype.retry;
+import com.android.tradefed.retry.RetryStrategy;
import com.android.tradefed.testtype.IRemoteTest;
import java.util.List;
diff --git a/src/com/android/tradefed/testtype/retry/RetryStatsHelper.java b/src/com/android/tradefed/testtype/retry/RetryStatsHelper.java
index c9748ce..ddb55dc 100644
--- a/src/com/android/tradefed/testtype/retry/RetryStatsHelper.java
+++ b/src/com/android/tradefed/testtype/retry/RetryStatsHelper.java
@@ -25,7 +25,7 @@
import java.util.Set;
/** Calculate the retry statistics and metrics based on attempts comparison. */
-public class RetryStatsHelper {
+final class RetryStatsHelper {
private List<List<TestRunResult>> mResults = new ArrayList<>();
private RetryStatistics mStats = new RetryStatistics();
diff --git a/src/com/android/tradefed/testtype/suite/BaseTestSuite.java b/src/com/android/tradefed/testtype/suite/BaseTestSuite.java
index 9c01b43..b283546 100644
--- a/src/com/android/tradefed/testtype/suite/BaseTestSuite.java
+++ b/src/com/android/tradefed/testtype/suite/BaseTestSuite.java
@@ -27,7 +27,10 @@
import com.android.tradefed.result.LogDataType;
import com.android.tradefed.testtype.IAbi;
import com.android.tradefed.testtype.IRemoteTest;
+import com.android.tradefed.testtype.suite.params.IModuleParameter;
import com.android.tradefed.testtype.suite.params.ModuleParameters;
+import com.android.tradefed.testtype.suite.params.ModuleParametersHelper;
+import com.android.tradefed.testtype.suite.params.NegativeHandler;
import com.android.tradefed.util.ArrayUtil;
import com.android.tradefed.util.FileUtil;
@@ -51,6 +54,7 @@
public static final String INCLUDE_FILTER_OPTION = "include-filter";
public static final String EXCLUDE_FILTER_OPTION = "exclude-filter";
public static final String MODULE_OPTION = "module";
+ public static final char MODULE_OPTION_SHORT_NAME = 'm';
public static final String TEST_ARG_OPTION = "test-arg";
public static final String TEST_OPTION = "test";
public static final char TEST_OPTION_SHORT_NAME = 't';
@@ -74,7 +78,7 @@
@Option(
name = MODULE_OPTION,
- shortName = 'm',
+ shortName = MODULE_OPTION_SHORT_NAME,
description = "the test module to run. Only works for configuration in the tests dir.",
importance = Importance.IF_UNSET
)
@@ -247,6 +251,16 @@
mModuleRepo =
createModuleLoader(
mIncludeFiltersParsed, mExcludeFiltersParsed, mTestArgs, mModuleArgs);
+ if (mForceParameter != null && !mEnableParameter) {
+ throw new IllegalArgumentException(
+ "'module-parameter' option was specified without "
+ + "'enable-optional-parameterization'");
+ }
+ if (mEnableOptionalParameter && !mEnableParameter) {
+ throw new IllegalArgumentException(
+ "'enable-optional-parameterization' option was specified without "
+ + "'enable-parameterized-modules'");
+ }
mModuleRepo.setParameterizedModules(mEnableParameter);
mModuleRepo.setOptionalParameterizedModules(mEnableOptionalParameter);
mModuleRepo.setModuleParameter(mForceParameter);
@@ -376,43 +390,65 @@
* @throws FileNotFoundException if any file is not found.
*/
protected void setupFilters(File testsDir) throws FileNotFoundException {
- if (mModuleName != null) {
- // If this option (-m / --module) is set only the matching unique module should run.
- Set<File> modules =
- SuiteModuleLoader.getModuleNamesMatching(
- testsDir, mSuitePrefix, String.format(".*%s.*.config", mModuleName));
- // If multiple modules match, do exact match.
- if (modules.size() > 1) {
- Set<File> newModules = new HashSet<>();
- String exactModuleName = String.format("%s.config", mModuleName);
- for (File module : modules) {
- if (module.getName().equals(exactModuleName)) {
- newModules.add(module);
- modules = newModules;
- break;
- }
+ if (mModuleName == null) {
+ if (mTestName != null) {
+ throw new IllegalArgumentException(
+ "Test name given without module name. Add --module <module-name>");
+ }
+ return;
+ }
+ // If this option (-m / --module) is set only the matching unique module should run.
+ Set<File> modules =
+ SuiteModuleLoader.getModuleNamesMatching(
+ testsDir, mSuitePrefix, String.format(".*%s.*.config", mModuleName));
+ // If multiple modules match, do exact match.
+ if (modules.size() > 1) {
+ Set<File> newModules = new HashSet<>();
+ String exactModuleName = String.format("%s.config", mModuleName);
+ for (File module : modules) {
+ if (module.getName().equals(exactModuleName)) {
+ newModules.add(module);
+ modules = newModules;
+ break;
}
}
- if (modules.size() == 0) {
- throw new IllegalArgumentException(
- String.format("No modules found matching %s", mModuleName));
- } else if (modules.size() > 1) {
- throw new IllegalArgumentException(
- String.format(
- "Multiple modules found matching %s:\n%s\nWhich one did you "
- + "mean?\n",
- mModuleName, ArrayUtil.join("\n", modules)));
- } else {
- File mod = modules.iterator().next();
- String moduleName = mod.getName().replace(".config", "");
- checkFilters(mIncludeFilters, moduleName);
- checkFilters(mExcludeFilters, moduleName);
- mIncludeFilters.add(
- new SuiteTestFilter(getRequestedAbi(), moduleName, mTestName).toString());
- }
- } else if (mTestName != null) {
+ }
+ if (modules.size() == 0) {
throw new IllegalArgumentException(
- "Test name given without module name. Add --module <module-name>");
+ String.format("No modules found matching %s", mModuleName));
+ } else if (modules.size() > 1) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Multiple modules found matching %s:\n%s\nWhich one did you "
+ + "mean?\n",
+ mModuleName, ArrayUtil.join("\n", modules)));
+ } else {
+ File mod = modules.iterator().next();
+ String moduleName = mod.getName().replace(".config", "");
+ checkFilters(mIncludeFilters, moduleName);
+ checkFilters(mExcludeFilters, moduleName);
+ mIncludeFilters.add(
+ new SuiteTestFilter(getRequestedAbi(), moduleName, mTestName).toString());
+ // Create the matching filters for the parameterized version of it if needed.
+ if (mEnableParameter) {
+ for (ModuleParameters param : ModuleParameters.values()) {
+ IModuleParameter moduleParam =
+ ModuleParametersHelper.getParameterHandler(
+ param, mEnableOptionalParameter);
+ if (moduleParam == null) {
+ continue;
+ }
+ if (moduleParam instanceof NegativeHandler) {
+ continue;
+ }
+ String paramModuleName =
+ String.format(
+ "%s[%s]", moduleName, moduleParam.getParameterIdentifier());
+ mIncludeFilters.add(
+ new SuiteTestFilter(getRequestedAbi(), paramModuleName, mTestName)
+ .toString());
+ }
+ }
}
}
diff --git a/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java b/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java
index 172ac97..777192b 100644
--- a/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java
+++ b/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java
@@ -19,25 +19,23 @@
import com.android.tradefed.config.IConfiguration;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.DeviceUnresponsiveException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.device.StubDevice;
import com.android.tradefed.device.metric.CollectorHelper;
import com.android.tradefed.device.metric.IMetricCollector;
import com.android.tradefed.device.metric.IMetricCollectorReceiver;
import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.ILogSaver;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.LogSaverResultForwarder;
import com.android.tradefed.result.TestRunResult;
+import com.android.tradefed.retry.MergeStrategy;
import com.android.tradefed.testtype.IRemoteTest;
import com.android.tradefed.testtype.ITestCollector;
import com.android.tradefed.testtype.ITestFilterReceiver;
-import com.android.tradefed.testtype.retry.BaseRetryDecision;
import com.android.tradefed.testtype.retry.IRetryDecision;
-import com.android.tradefed.testtype.retry.MergeStrategy;
import com.android.tradefed.testtype.retry.RetryStatistics;
-import com.android.tradefed.testtype.retry.RetryStrategy;
import com.android.tradefed.util.StreamUtil;
import com.google.common.annotations.VisibleForTesting;
@@ -70,6 +68,7 @@
*/
public class GranularRetriableTestWrapper implements IRemoteTest, ITestCollector {
+ private IRetryDecision mRetryDecision;
private IRemoteTest mTest;
private List<IMetricCollector> mRunMetricCollectors;
private TestFailureListener mFailureListener;
@@ -87,9 +86,6 @@
// Tracking of the metrics
private RetryStatistics mRetryStats = null;
- private RetryStrategy mRetryStrategy = RetryStrategy.NO_RETRY;
- private boolean mRebootAtLastRetry = false;
-
public GranularRetriableTestWrapper(
IRemoteTest test,
ITestInvocationListener mainListener,
@@ -103,6 +99,11 @@
mMaxRunLimit = maxRunLimit;
}
+ /** Sets the {@link IRetryDecision} to be used. */
+ public void setRetryDecision(IRetryDecision decision) {
+ mRetryDecision = decision;
+ }
+
/**
* Set the {@link ModuleDefinition} name as a {@link GranularRetriableTestWrapper} attribute.
*
@@ -161,16 +162,6 @@
mLogSaver = logSaver;
}
- /** Sets the {@link RetryStrategy} to be used when retrying. */
- public final void setRetryStrategy(RetryStrategy retryStrategy) {
- mRetryStrategy = retryStrategy;
- }
-
- /** Sets the flag to reboot devices at the last intra-module retry. */
- public final void setRebootAtLastRetry(boolean rebootAtLastRetry) {
- mRebootAtLastRetry = rebootAtLastRetry;
- }
-
/**
* Initialize a new {@link ModuleListener} for each test run.
*
@@ -225,9 +216,14 @@
return;
}
+ if (mRetryDecision == null) {
+ CLog.e("RetryDecision is null. Something is misconfigured this shouldn't happen");
+ return;
+ }
+
// Bail out early if there is no need to retry at all.
- IRetryDecision retryDecision = new BaseRetryDecision(mRetryStrategy);
- if (!retryDecision.shouldRetry(mTest, mMainGranularRunListener.getTestRunForAttempts(0))) {
+ if (!mRetryDecision.shouldRetry(
+ mTest, 0, mMainGranularRunListener.getTestRunForAttempts(0))) {
return;
}
@@ -237,35 +233,40 @@
CLog.d("Starting intra-module retry.");
for (int attemptNumber = 1; attemptNumber < mMaxRunLimit; attemptNumber++) {
boolean retry =
- retryDecision.shouldRetry(
+ mRetryDecision.shouldRetry(
mTest,
+ attemptNumber - 1,
mMainGranularRunListener.getTestRunForAttempts(attemptNumber - 1));
if (!retry) {
return;
}
- // Reboot device at the last intra-module retry if reboot-at-last-retry is set.
- if (mRebootAtLastRetry && (attemptNumber == (mMaxRunLimit-1))) {
- for (ITestDevice device : mModuleInvocationContext.getDevices()) {
- if (!(device.getIDevice() instanceof StubDevice)) {
- CLog.i("Rebooting device: %s at the last intra-module retry.",
- device.getSerialNumber());
- device.reboot();
- }
- }
- }
+ CLog.d("Intra-module retry attempt number %s", attemptNumber);
// Run the tests again
intraModuleRun(allListeners);
}
// Feed the last attempt if we reached here.
- retryDecision.addLastAttempt(
+ mRetryDecision.addLastAttempt(
mMainGranularRunListener.getTestRunForAttempts(mMaxRunLimit - 1));
} finally {
- mRetryStats = retryDecision.getRetryStats();
+ mRetryStats = mRetryDecision.getRetryStatistics();
// Track how long we spend in retry
mRetryStats.mRetryTime = System.currentTimeMillis() - startTime;
+ addRetryTime(mRetryStats.mRetryTime);
}
}
+ private void addRetryTime(long retryTimeMs) {
+ long totalRetryMs = retryTimeMs;
+ String retryTime =
+ InvocationMetricLogger.getInvocationMetrics()
+ .get(InvocationMetricKey.AUTO_RETRY_TIME.toString());
+ if (retryTime != null) {
+ totalRetryMs += Long.parseLong(retryTime) + retryTimeMs;
+ }
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.AUTO_RETRY_TIME, Long.toString(totalRetryMs));
+ }
+
/** The workflow for each individual {@link IRemoteTest} run. */
private final void intraModuleRun(ITestInvocationListener runListener)
throws DeviceNotAvailableException {
@@ -315,7 +316,7 @@
/** Get the merged TestRunResults from each {@link IRemoteTest} run. */
public final List<TestRunResult> getFinalTestRunResults() {
- MergeStrategy strategy = MergeStrategy.getMergeStrategy(mRetryStrategy);
+ MergeStrategy strategy = MergeStrategy.getMergeStrategy(mRetryDecision.getRetryStrategy());
mMainGranularRunListener.setMergeStrategy(strategy);
return mMainGranularRunListener.getMergedTestRunResults();
}
@@ -342,14 +343,6 @@
return mMainGranularRunListener.getExpectedTests();
}
- /**
- * Returns the {@link RetryStatistics} representating the retry information. Null if no retry
- * occurred.
- */
- public final RetryStatistics getRetryStatistics() {
- return mRetryStats;
- }
-
/** Returns the listener containing all the results. */
public ModuleListener getResultListener() {
return mMainGranularRunListener;
diff --git a/src/com/android/tradefed/testtype/suite/ITestSuite.java b/src/com/android/tradefed/testtype/suite/ITestSuite.java
index 370d6e5..fc71352 100644
--- a/src/com/android/tradefed/testtype/suite/ITestSuite.java
+++ b/src/com/android/tradefed/testtype/suite/ITestSuite.java
@@ -28,6 +28,7 @@
import com.android.tradefed.config.Option.Importance;
import com.android.tradefed.config.OptionCopier;
import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.DeviceProperties;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.NullDevice;
import com.android.tradefed.device.StubDevice;
@@ -36,6 +37,8 @@
import com.android.tradefed.device.metric.IMetricCollector;
import com.android.tradefed.device.metric.IMetricCollectorReceiver;
import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
import com.android.tradefed.invoker.shard.token.ITokenRequest;
import com.android.tradefed.invoker.shard.token.TokenProperty;
import com.android.tradefed.log.ITestLogger;
@@ -44,6 +47,7 @@
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.ITestLoggerReceiver;
import com.android.tradefed.result.ResultForwarder;
+import com.android.tradefed.retry.RetryStrategy;
import com.android.tradefed.suite.checker.ISystemStatusChecker;
import com.android.tradefed.suite.checker.ISystemStatusCheckerReceiver;
import com.android.tradefed.suite.checker.StatusCheckerResult;
@@ -60,7 +64,6 @@
import com.android.tradefed.testtype.IRuntimeHintProvider;
import com.android.tradefed.testtype.IShardableTest;
import com.android.tradefed.testtype.ITestCollector;
-import com.android.tradefed.testtype.retry.RetryStrategy;
import com.android.tradefed.util.AbiFormatter;
import com.android.tradefed.util.AbiUtils;
import com.android.tradefed.util.MultiMap;
@@ -164,11 +167,6 @@
@Option(name = "reboot-per-module", description = "Reboot the device before every module run.")
private boolean mRebootPerModule = false;
- @Deprecated
- @Option(name = "reboot-at-last-retry",
- description = "Reboot the device at the last intra-module retry")
- private boolean mRebootAtLastRetry = false;
-
@Option(
name = REBOOT_BEFORE_TEST,
description = "Reboot the device before the test suite starts."
@@ -461,6 +459,8 @@
}
}
long elapsedTime = System.currentTimeMillis() - startTime;
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.STAGE_TESTS_TIME, elapsedTime);
CLog.i(
String.format(
"Staging test artifacts for %d modules finished in %s.",
@@ -714,7 +714,7 @@
TestFailureListener failureListener)
throws DeviceNotAvailableException {
if (mRebootPerModule) {
- if ("user".equals(mDevice.getProperty("ro.build.type"))) {
+ if ("user".equals(mDevice.getProperty(DeviceProperties.BUILD_TYPE))) {
CLog.e(
"reboot-per-module should only be used during development, "
+ "this is a\" user\" build device");
@@ -735,17 +735,16 @@
// Pass the main invocation logSaver
module.setLogSaver(mMainConfiguration.getLogSaver());
// Pass the retry strategy to the module
- module.setRetryStrategy(
- getConfiguration().getCommandOptions().getRetryStrategy(), mMergeAttempts);
- // Pass the reboot strategy at the last intra-module retry to the module
- module.setRebootAtLastRetry(mRebootAtLastRetry);
+ module.setMergeAttemps(mMergeAttempts);
+ // Pass the retry decision to be used.
+ module.setRetryDecision(mMainConfiguration.getRetryDecision());
// Actually run the module
module.run(
listener,
moduleListeners,
failureListener,
- getConfiguration().getCommandOptions().getMaxRetryCount());
+ getConfiguration().getRetryDecision().getMaxRetryCount());
if (!mSkipAllSystemStatusCheck) {
runPostModuleCheck(module.getId(), mSystemStatusCheckers, mDevice, listener);
diff --git a/src/com/android/tradefed/testtype/suite/ModuleDefinition.java b/src/com/android/tradefed/testtype/suite/ModuleDefinition.java
index 665ec6f..7f3b5dc 100644
--- a/src/com/android/tradefed/testtype/suite/ModuleDefinition.java
+++ b/src/com/android/tradefed/testtype/suite/ModuleDefinition.java
@@ -56,8 +56,8 @@
import com.android.tradefed.testtype.IRemoteTest;
import com.android.tradefed.testtype.IRuntimeHintProvider;
import com.android.tradefed.testtype.ITestCollector;
+import com.android.tradefed.testtype.retry.IRetryDecision;
import com.android.tradefed.testtype.retry.RetryStatistics;
-import com.android.tradefed.testtype.retry.RetryStrategy;
import com.android.tradefed.testtype.suite.module.BaseModuleController;
import com.android.tradefed.testtype.suite.module.IModuleController.RunStrategy;
import com.android.tradefed.util.StreamUtil;
@@ -136,9 +136,8 @@
// Tracking of retry performance
private List<RetryStatistics> mRetryStats = new ArrayList<>();
- private RetryStrategy mRetryStrategy = RetryStrategy.NO_RETRY;
private boolean mMergeAttempts = true;
- private boolean mRebootAtLastRetry = false;
+ private IRetryDecision mRetryDecision;
// Token during sharding
private Set<TokenProperty> mRequiredTokens = new HashSet<>();
@@ -486,14 +485,16 @@
mExpectedTests += retriableTest.getExpectedTestsCount();
// Get information about retry
- RetryStatistics res = retriableTest.getRetryStatistics();
- if (res != null) {
- mRetryStats.add(res);
+ if (mRetryDecision != null) {
+ RetryStatistics res = mRetryDecision.getRetryStatistics();
+ if (res != null) {
+ mRetryStats.add(res);
+ }
}
}
// After the run, if the test failed (even after retry the final result passed) has
// failed, capture a bugreport.
- if (retriableTest.getResultListener().hasFailed()) {
+ if (retriableTest.getResultListener().hasLastAttemptFailed()) {
captureBugreport(listener, getId());
}
}
@@ -584,8 +585,7 @@
retriableTest.setModuleConfig(mModuleConfiguration);
retriableTest.setInvocationContext(mModuleInvocationContext);
retriableTest.setLogSaver(mLogSaver);
- retriableTest.setRetryStrategy(mRetryStrategy);
- retriableTest.setRebootAtLastRetry(mRebootAtLastRetry);
+ retriableTest.setRetryDecision(mRetryDecision);
return retriableTest;
}
@@ -866,15 +866,14 @@
mCollectTestsOnly = collectTestsOnly;
}
- /** Sets the {@link RetryStrategy} to be used when retrying. */
- public final void setRetryStrategy(RetryStrategy retryStrategy, boolean mergeAttempts) {
- mRetryStrategy = retryStrategy;
+ /** Sets whether or not we should merge results. */
+ public final void setMergeAttemps(boolean mergeAttempts) {
mMergeAttempts = mergeAttempts;
}
- /** Sets the flag to reboot devices at the last intra-module retry. */
- public final void setRebootAtLastRetry(boolean rebootAtLastRetry) {
- mRebootAtLastRetry = rebootAtLastRetry;
+ /** Sets the {@link IRetryDecision} to be used for intra-module retry. */
+ public final void setRetryDecision(IRetryDecision decision) {
+ mRetryDecision = decision;
}
/** Returns a list of tests that ran in this module. */
diff --git a/src/com/android/tradefed/testtype/suite/ModuleListener.java b/src/com/android/tradefed/testtype/suite/ModuleListener.java
index 86057d2..3dad444 100644
--- a/src/com/android/tradefed/testtype/suite/ModuleListener.java
+++ b/src/com/android/tradefed/testtype/suite/ModuleListener.java
@@ -40,7 +40,6 @@
private boolean mTestFailed = false;
private int mTestsRan = 1;
private ITestInvocationListener mMainListener;
- private boolean mHasFailed = false;
private boolean mCollectTestsOnly = false;
/** Track runs in progress for logging purpose */
@@ -82,7 +81,6 @@
/** {@inheritDoc} */
@Override
public void testRunFailed(String errorMessage) {
- mHasFailed = true;
CLog.d("ModuleListener.testRunFailed(%s)", errorMessage);
super.testRunFailed(errorMessage);
}
@@ -94,9 +92,9 @@
mRunInProgress = false;
}
- /** Returns whether or not the listener session has failed. */
- public boolean hasFailed() {
- return mHasFailed;
+ /** Returns whether or not the listener last retry session has failed. */
+ public boolean hasLastAttemptFailed() {
+ return getCurrentRunResults().isRunFailure();
}
/** {@inheritDoc} */
diff --git a/src/com/android/tradefed/testtype/suite/TestFailureListener.java b/src/com/android/tradefed/testtype/suite/TestFailureListener.java
index df099d3..e38b2f6 100644
--- a/src/com/android/tradefed/testtype/suite/TestFailureListener.java
+++ b/src/com/android/tradefed/testtype/suite/TestFailureListener.java
@@ -16,6 +16,7 @@
package com.android.tradefed.testtype.suite;
import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.DeviceProperties;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.log.ITestLogger;
import com.android.tradefed.log.LogUtil.CLog;
@@ -74,7 +75,7 @@
try {
// Rebooting on all failures can hide legitimate issues and platform instabilities,
// therefore only allowed on "user-debug" and "eng" builds.
- if ("user".equals(device.getProperty("ro.build.type"))) {
+ if ("user".equals(device.getProperty(DeviceProperties.BUILD_TYPE))) {
CLog.e("Reboot-on-failure should only be used during development," +
" this is a\" user\" build device");
} else {
diff --git a/src/com/android/tradefed/testtype/suite/params/SecondaryUserHandler.java b/src/com/android/tradefed/testtype/suite/params/SecondaryUserHandler.java
index cdaaca4..4b01ecc 100644
--- a/src/com/android/tradefed/testtype/suite/params/SecondaryUserHandler.java
+++ b/src/com/android/tradefed/testtype/suite/params/SecondaryUserHandler.java
@@ -15,13 +15,19 @@
*/
package com.android.tradefed.testtype.suite.params;
+import android.platform.test.annotations.SystemUserOnly;
+
import com.android.tradefed.config.IConfiguration;
import com.android.tradefed.config.IDeviceConfiguration;
import com.android.tradefed.targetprep.CreateUserPreparer;
import com.android.tradefed.targetprep.ITargetPreparer;
import com.android.tradefed.targetprep.RunCommandTargetPreparer;
+import com.android.tradefed.testtype.IRemoteTest;
+import com.android.tradefed.testtype.ITestAnnotationFilterReceiver;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/** Handler for {@link ModuleParameters#SECONDARY_USER}. */
public class SecondaryUserHandler implements IModuleParameter {
@@ -40,6 +46,21 @@
// Add a preparer to setup the location settings on the new user
preparers.add(1, createLocationPreparer());
}
+
+ // Add filter to exclude @SystemUserOnly
+ for (IRemoteTest test : moduleConfiguration.getTests()) {
+ if (test instanceof ITestAnnotationFilterReceiver) {
+ ITestAnnotationFilterReceiver filterTest = (ITestAnnotationFilterReceiver) test;
+ // Retrieve the current set of excludeAnnotations to maintain for after the
+ // clearing/reset of the annotations.
+ Set<String> excludeAnnotations = new HashSet<>(filterTest.getExcludeAnnotations());
+ // Prevent system user only tests from running
+ excludeAnnotations.add(SystemUserOnly.class.getCanonicalName());
+ // Reset the annotations of the tests
+ filterTest.clearExcludeAnnotations();
+ filterTest.addAllExcludeAnnotation(excludeAnnotations);
+ }
+ }
}
private RunCommandTargetPreparer createLocationPreparer() {
diff --git a/src/com/android/tradefed/testtype/suite/retry/RetryRescheduler.java b/src/com/android/tradefed/testtype/suite/retry/RetryRescheduler.java
index 666bd44..edf5522 100644
--- a/src/com/android/tradefed/testtype/suite/retry/RetryRescheduler.java
+++ b/src/com/android/tradefed/testtype/suite/retry/RetryRescheduler.java
@@ -76,6 +76,13 @@
+ "and \"not_executed\".")
private RetryType mRetryType = null;
+ @Option(
+ name = BaseTestSuite.MODULE_OPTION,
+ shortName = BaseTestSuite.MODULE_OPTION_SHORT_NAME,
+ description = "the test module to run. Only works for configuration in the tests dir."
+ )
+ private String mModuleName = null;
+
/**
* It's possible to add extra exclusion from the rerun. But these tests will not change their
* state.
@@ -236,6 +243,22 @@
types.add(mRetryType);
}
+ // Expand the --module option in case no abi is specified.
+ Set<String> expandedModuleOption = new HashSet<>();
+ if (mModuleName != null) {
+ SuiteTestFilter moduleFilter = SuiteTestFilter.createFrom(mModuleName);
+ expandedModuleOption.add(mModuleName);
+ if (moduleFilter.getAbi() == null) {
+ Set<String> abis = AbiUtils.getAbisSupportedByCompatibility();
+ for (String abi : abis) {
+ SuiteTestFilter namingFilter =
+ new SuiteTestFilter(
+ abi, moduleFilter.getName(), moduleFilter.getTest());
+ expandedModuleOption.add(namingFilter.toString());
+ }
+ }
+ }
+
// Expand the exclude-filter in case no abi is specified.
Set<String> extendedExcludeRetryFilters = new HashSet<>();
for (String excludeFilter : mExcludeFilters) {
@@ -257,6 +280,8 @@
for (TestRunResult moduleResult : results.getMergedTestRunResults()) {
// If the module is explicitly excluded from retries, preserve the original results.
if (!extendedExcludeRetryFilters.contains(moduleResult.getName())
+ && (expandedModuleOption.isEmpty()
+ || expandedModuleOption.contains(moduleResult.getName()))
&& RetryResultHelper.shouldRunModule(moduleResult, types)) {
if (types.contains(RetryType.NOT_EXECUTED)) {
// Clear the run failure since we are attempting to rerun all non-executed
diff --git a/src/com/android/tradefed/util/BuildTestsZipUtils.java b/src/com/android/tradefed/util/BuildTestsZipUtils.java
index cb32862..ae33b8a 100644
--- a/src/com/android/tradefed/util/BuildTestsZipUtils.java
+++ b/src/com/android/tradefed/util/BuildTestsZipUtils.java
@@ -67,8 +67,9 @@
Collections.reverse(dirs);
List<File> expandedTestDirs = new ArrayList<>();
+ File testsDir = null;
if (buildInfo != null && buildInfo instanceof IDeviceBuildInfo) {
- File testsDir = ((IDeviceBuildInfo) buildInfo).getTestsDir();
+ testsDir = ((IDeviceBuildInfo) buildInfo).getTestsDir();
if (testsDir != null && testsDir.exists()) {
expandedTestDirs.add(FileUtil.getFileForPath(testsDir, "DATA", "app"));
expandedTestDirs.add(FileUtil.getFileForPath(testsDir, "DATA", "app", apkBase));
@@ -81,6 +82,13 @@
File testcasesSubDir = FileUtil.findFile(testsDir, apkBase);
if (testcasesSubDir != null) {
expandedTestDirs.add(testcasesSubDir);
+ } else {
+ // If there doesn't exist a directory named after apkBase, it's possible that
+ // the apk is built output to a different module directory. Therefore, try to
+ // search entire testsDir to locate the apk.
+ // TODO(dshi): Find a better way to locate apk. Ideally we should start with the
+ // test module's directory to avoid false-positive result.
+ expandedTestDirs.add(testsDir);
}
}
}
@@ -133,6 +141,14 @@
// If we couldn't find a resource, we delete the tmp file
FileUtil.deleteFile(apkTempFile);
}
+
+ // Try to stage the files from remote zip files.
+ if (testsDir != null) {
+ File apkFile = buildInfo.stageRemoteFile(apkFileName, testsDir);
+ if (apkFile != null) {
+ return apkFile;
+ }
+ }
return null;
}
}
diff --git a/src/com/android/tradefed/util/RemoteZip.java b/src/com/android/tradefed/util/RemoteZip.java
new file mode 100644
index 0000000..8e3ef97
--- /dev/null
+++ b/src/com/android/tradefed/util/RemoteZip.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tradefed.util;
+
+import com.android.tradefed.build.BuildRetrievalError;
+import com.android.tradefed.build.IFileDownloader;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.util.zip.CentralDirectoryInfo;
+import com.android.tradefed.util.zip.EndCentralDirectoryInfo;
+import com.android.tradefed.util.zip.LocalFileHeader;
+import com.android.tradefed.util.zip.MergedZipEntryCollection;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.List;
+
+/** Utilities to unzip individual files inside a remote zip file. */
+public class RemoteZip {
+
+ private String mRemoteFilePath;
+ private List<CentralDirectoryInfo> mZipEntries;
+ private long mFileSize;
+ private IFileDownloader mDownloader;
+ // Last time this object is accessed. The timestamp is used to maintain the cache of RemoteZip
+ // objects.
+ private long mLastAccess;
+
+ /**
+ * Constructor
+ *
+ * @param remoteFilePath the remote path to the file to download.
+ * @param fileSize size of the remote file.
+ * @param downloader a @{link IFileDownloader} used to download a remote file.
+ */
+ public RemoteZip(String remoteFilePath, long fileSize, IFileDownloader downloader) {
+ mRemoteFilePath = remoteFilePath;
+ mFileSize = fileSize;
+ mDownloader = downloader;
+ mZipEntries = null;
+ mLastAccess = System.currentTimeMillis();
+ }
+
+ /** Get the remote file path of the remote zip artifact. */
+ public String getRemoteFilePath() {
+ return mRemoteFilePath;
+ }
+
+ /** Get the last time this object is accessed. */
+ public long getLastAccess() {
+ return mLastAccess;
+ }
+
+ /** Update the last access timestamp of the object. */
+ public void setLastAccess(long timestamp) {
+ mLastAccess = timestamp;
+ }
+
+ /**
+ * Gets the zip file entries of a remote zip file.
+ *
+ * @throws BuildRetrievalError if file could not be downloaded.
+ */
+ public List<CentralDirectoryInfo> getZipEntries() throws BuildRetrievalError, IOException {
+ if (mZipEntries != null) {
+ return mZipEntries;
+ }
+
+ File partialZipFile = FileUtil.createTempFileForRemote(mRemoteFilePath, null);
+ // Delete it so name is available
+ partialZipFile.delete();
+ try {
+ // Get the end central directory of the zip file requested.
+ // Download last 64kb (or entire file if the size is less than 64kb)
+ long size = EndCentralDirectoryInfo.MAX_LOOKBACK;
+ long startOffset = mFileSize - size;
+ if (startOffset < 0) {
+ // The default lookback size is greater than the size of the file, so read the whole
+ // file by setting size to -1.
+ startOffset = 0;
+ size = -1;
+ }
+
+ mDownloader.downloadFile(mRemoteFilePath, partialZipFile, startOffset, size);
+ EndCentralDirectoryInfo endCentralDirInfo = new EndCentralDirectoryInfo(partialZipFile);
+ partialZipFile.delete();
+
+ // Read central directory infos
+ mDownloader.downloadFile(
+ mRemoteFilePath,
+ partialZipFile,
+ endCentralDirInfo.getCentralDirOffset(),
+ endCentralDirInfo.getCentralDirSize());
+
+ mZipEntries = ZipUtil.getZipCentralDirectoryInfos(partialZipFile, endCentralDirInfo);
+ return mZipEntries;
+ } catch (IOException e) {
+ throw new BuildRetrievalError(
+ String.format("Failed to get zip entries of remote file %s", mRemoteFilePath),
+ e);
+ } finally {
+ FileUtil.deleteFile(partialZipFile);
+ }
+ }
+
+ /**
+ * Download the specified files in the remote zip file.
+ *
+ * @param destDir the directory to place the downloaded files to.
+ * @param files a list of entries to download from the remote zip file.
+ * @throws BuildRetrievalError
+ * @throws IOException
+ */
+ public void downloadFiles(File destDir, List<CentralDirectoryInfo> files)
+ throws BuildRetrievalError, IOException {
+ long totalDownloadedSize = 0;
+ long startTime = System.currentTimeMillis();
+
+ // Merge the entries into sections to minimize the download attempts.
+ List<MergedZipEntryCollection> collections =
+ MergedZipEntryCollection.createCollections(files);
+ CLog.d(
+ "Downloading %d files from remote zip file %s in %d sections.",
+ files.size(), mRemoteFilePath, collections.size());
+ for (MergedZipEntryCollection collection : collections) {
+ File partialZipFile = null;
+ try {
+ partialZipFile = FileUtil.createTempFileForRemote(mRemoteFilePath, null);
+ // Delete it so name is available
+ partialZipFile.delete();
+ // End offset is based on the maximum guess of local file header size (2KB). So it
+ // can exceed the file size.
+ long downloadedSize = collection.getEndOffset() - collection.getStartOffset();
+ if (collection.getStartOffset() + downloadedSize > mFileSize) {
+ downloadedSize = mFileSize - collection.getStartOffset();
+ }
+ mDownloader.downloadFile(
+ mRemoteFilePath,
+ partialZipFile,
+ collection.getStartOffset(),
+ downloadedSize);
+ totalDownloadedSize += downloadedSize;
+
+ // Extract each file from the partial download.
+ for (CentralDirectoryInfo entry : collection.getZipEntries()) {
+ File targetFile =
+ new File(Paths.get(destDir.toString(), entry.getFileName()).toString());
+ LocalFileHeader localFileHeader =
+ new LocalFileHeader(
+ partialZipFile,
+ (int)
+ (entry.getLocalHeaderOffset()
+ - collection.getStartOffset()));
+ ZipUtil.unzipPartialZipFile(
+ partialZipFile,
+ targetFile,
+ entry,
+ localFileHeader,
+ entry.getLocalHeaderOffset() - collection.getStartOffset());
+ }
+ } finally {
+ FileUtil.deleteFile(partialZipFile);
+ }
+ }
+ CLog.d(
+ "%d files downloaded from remote zip file in %s. Total download size: %,d bytes.",
+ files.size(),
+ TimeUtil.formatElapsedTime(System.currentTimeMillis() - startTime),
+ totalDownloadedSize);
+ }
+}
diff --git a/src/com/android/tradefed/util/SubprocessTestResultsParser.java b/src/com/android/tradefed/util/SubprocessTestResultsParser.java
index 9bd7cbc..2da9f31 100644
--- a/src/com/android/tradefed/util/SubprocessTestResultsParser.java
+++ b/src/com/android/tradefed/util/SubprocessTestResultsParser.java
@@ -17,6 +17,8 @@
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.FileInputStreamSource;
import com.android.tradefed.result.ILogSaverListener;
@@ -55,7 +57,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -101,21 +103,29 @@
*/
private class EventReceiverThread extends Thread {
private ServerSocket mSocket;
- private CountDownLatch mCountDown;
+ // initial state: 1 permit available, joins that don't wait for connection will succeed
+ private Semaphore mSemaphore = new Semaphore(1);
private boolean mShouldParse = true;
public EventReceiverThread() throws IOException {
super("EventReceiverThread");
mSocket = new ServerSocket(0);
- mCountDown = new CountDownLatch(1);
}
protected int getLocalPort() {
return mSocket.getLocalPort();
}
- protected CountDownLatch getCountDown() {
- return mCountDown;
+ /** @return True if parsing completes before timeout (optionally waiting for connection). */
+ boolean await(long millis, boolean waitForConnection) throws InterruptedException {
+ // As only 1 permit is available prior to connecting, changing the number of permits
+ // requested controls whether the receiver will wait for a connection.
+ int permits = waitForConnection ? 2 : 1;
+ if (mSemaphore.tryAcquire(permits, millis, TimeUnit.MILLISECONDS)) {
+ mSemaphore.release(permits);
+ return true;
+ }
+ return false;
}
public void cancel() throws IOException {
@@ -138,6 +148,7 @@
BufferedReader in = null;
try {
client = mSocket.accept();
+ mSemaphore.acquire(); // connected: 0 permits available, all joins will wait
in = new BufferedReader(new InputStreamReader(client.getInputStream()));
String event = null;
while ((event = in.readLine()) != null) {
@@ -152,27 +163,39 @@
CLog.e(e);
}
}
- } catch (IOException e) {
+ } catch (IOException | InterruptedException e) {
CLog.e(e);
} finally {
StreamUtil.close(in);
- mCountDown.countDown();
+ mSemaphore.release(2); // finished: 2 permits available, all joins succeed
}
CLog.d("EventReceiverThread done.");
}
}
/**
- * If the event receiver is being used, ensure that we wait for it to terminate.
+ * Wait for the event receiver to finish processing events. Will wait even if a connection
+ * wasn't established, i.e. processing hasn't begun yet.
*
* @param millis timeout in milliseconds.
* @return True if receiver thread terminate before timeout, False otherwise.
*/
public boolean joinReceiver(long millis) {
+ return joinReceiver(millis, true);
+ }
+
+ /**
+ * Wait for the event receiver to finish processing events.
+ *
+ * @param millis timeout in milliseconds.
+ * @param waitForConnection False to skip waiting if a connection was never established.
+ * @return True if receiver thread terminate before timeout, False otherwise.
+ */
+ public boolean joinReceiver(long millis, boolean waitForConnection) {
if (mEventReceiver != null) {
try {
CLog.i("Waiting for events to finish being processed.");
- if (!mEventReceiver.getCountDown().await(millis, TimeUnit.MILLISECONDS)) {
+ if (!mEventReceiver.await(millis, waitForConnection)) {
mEventReceiver.stopParsing();
CLog.e("Event receiver thread did not complete. Some events may be missing.");
return false;
@@ -505,7 +528,24 @@
// provider of the running configuration).
List<IBuildInfo> infos = mContext.getBuildInfos();
if (!infos.isEmpty()) {
- infos.get(0).addBuildAttributes(eventEnd.mBuildAttributes);
+ Map<String, String> attributes = eventEnd.mBuildAttributes;
+ for (InvocationMetricKey key : InvocationMetricKey.values()) {
+ if (!attributes.containsKey(key.toString())) {
+ continue;
+ }
+ String val = attributes.remove(key.toString());
+ if (key.shouldAdd()) {
+ try {
+ InvocationMetricLogger.addInvocationMetrics(key, Long.parseLong(val));
+ } catch (NumberFormatException e) {
+ CLog.e("Key %s should have a number value, instead was: %s", key, val);
+ CLog.e(e);
+ }
+ } else {
+ InvocationMetricLogger.addInvocationMetrics(key, val);
+ }
+ }
+ infos.get(0).addBuildAttributes(attributes);
}
}
}
diff --git a/src/com/android/tradefed/util/SystemUtil.java b/src/com/android/tradefed/util/SystemUtil.java
index 6a66c89..a38bc0b 100644
--- a/src/com/android/tradefed/util/SystemUtil.java
+++ b/src/com/android/tradefed/util/SystemUtil.java
@@ -42,6 +42,8 @@
ANDROID_HOST_OUT_TESTCASES,
}
+ public static final String REMOTE_VM_VARIABLE = "REMOTE_VM_ENV";
+
private static final String HOST_TESTCASES = "host/testcases";
private static final String TARGET_TESTCASES = "target/testcases";
@@ -163,4 +165,12 @@
return new File(path);
}
}
+
+ /** Return true if we are currently running in a remote environment. */
+ public static boolean isRemoteEnvironment() {
+ if ("1".equals(System.getenv(REMOTE_VM_VARIABLE))) {
+ return true;
+ }
+ return false;
+ }
}
diff --git a/test_framework/Android.bp b/test_framework/Android.bp
index 561d9a8..ae775da 100644
--- a/test_framework/Android.bp
+++ b/test_framework/Android.bp
@@ -18,8 +18,14 @@
srcs: [
"com/**/*.java",
],
+ static_libs: [
+ "longevity-host-lib",
+ "perfetto_config-full",
+ "test-composers",
+ ],
libs: [
"tradefed-lib-core",
+ "loganalysis",
],
}
diff --git a/test_framework/OWNERS b/test_framework/OWNERS
new file mode 100644
index 0000000..1a475dc
--- /dev/null
+++ b/test_framework/OWNERS
@@ -0,0 +1,5 @@
+# Base Owners + extra folks that can review the test_framework layer
+allenhair@google.com
+gelanchezhian@google.com
+mrosenfeld@google.com
+
diff --git a/test_framework/README.md b/test_framework/README.md
new file mode 100644
index 0000000..1a33d67
--- /dev/null
+++ b/test_framework/README.md
@@ -0,0 +1,12 @@
+# Trade Federation Test Framework
+
+This is the top-layer provided to write and run tests against.
+
+The goal of this layer is to provide a simple framework to
+write and execute tests.
+
+This directory should contain classes that are:
+* Related to tests (IRemoteTest types)
+* Related to tests setup (ITargetPreparer types)
+* Related to metrics collection during tests (IMetricCollector types)
+* Utilities specific to the tests, preparers or collectors
diff --git a/src/com/android/tradefed/device/metric/AtraceCollector.java b/test_framework/com/android/tradefed/device/metric/AtraceCollector.java
similarity index 98%
rename from src/com/android/tradefed/device/metric/AtraceCollector.java
rename to test_framework/com/android/tradefed/device/metric/AtraceCollector.java
index 628410b..c6ea63b 100644
--- a/src/com/android/tradefed/device/metric/AtraceCollector.java
+++ b/test_framework/com/android/tradefed/device/metric/AtraceCollector.java
@@ -189,7 +189,7 @@
}
}
- private void postProcess(File trace, String id) {
+ private void postProcess(File trace) {
if (mLogProcessingBinary == null
|| !mLogProcessingBinary.exists()
|| !mLogProcessingBinary.canExecute()) {
@@ -265,7 +265,7 @@
streamSource);
}
- postProcess(trace, device.getSerialNumber());
+ postProcess(trace);
trace.delete();
} else {
throw new DeviceRuntimeException("failed to pull log: " + fullLogPath());
diff --git a/src/com/android/tradefed/device/metric/AtraceRunMetricCollector.java b/test_framework/com/android/tradefed/device/metric/AtraceRunMetricCollector.java
similarity index 100%
rename from src/com/android/tradefed/device/metric/AtraceRunMetricCollector.java
rename to test_framework/com/android/tradefed/device/metric/AtraceRunMetricCollector.java
diff --git a/src/com/android/tradefed/device/metric/BuddyInfoMetricCollector.java b/test_framework/com/android/tradefed/device/metric/BuddyInfoMetricCollector.java
similarity index 100%
rename from src/com/android/tradefed/device/metric/BuddyInfoMetricCollector.java
rename to test_framework/com/android/tradefed/device/metric/BuddyInfoMetricCollector.java
diff --git a/src/com/android/tradefed/device/metric/BugreportzMetricCollector.java b/test_framework/com/android/tradefed/device/metric/BugreportzMetricCollector.java
similarity index 100%
rename from src/com/android/tradefed/device/metric/BugreportzMetricCollector.java
rename to test_framework/com/android/tradefed/device/metric/BugreportzMetricCollector.java
diff --git a/src/com/android/tradefed/device/metric/DumpHeapCollector.java b/test_framework/com/android/tradefed/device/metric/DumpHeapCollector.java
similarity index 100%
rename from src/com/android/tradefed/device/metric/DumpHeapCollector.java
rename to test_framework/com/android/tradefed/device/metric/DumpHeapCollector.java
diff --git a/src/com/android/tradefed/device/metric/GraphicsStatsMetricCollector.java b/test_framework/com/android/tradefed/device/metric/GraphicsStatsMetricCollector.java
similarity index 100%
rename from src/com/android/tradefed/device/metric/GraphicsStatsMetricCollector.java
rename to test_framework/com/android/tradefed/device/metric/GraphicsStatsMetricCollector.java
diff --git a/src/com/android/tradefed/device/metric/HostStatsdMetricCollector.java b/test_framework/com/android/tradefed/device/metric/HostStatsdMetricCollector.java
similarity index 100%
rename from src/com/android/tradefed/device/metric/HostStatsdMetricCollector.java
rename to test_framework/com/android/tradefed/device/metric/HostStatsdMetricCollector.java
diff --git a/src/com/android/tradefed/device/metric/IonHeapInfoMetricCollector.java b/test_framework/com/android/tradefed/device/metric/IonHeapInfoMetricCollector.java
similarity index 100%
rename from src/com/android/tradefed/device/metric/IonHeapInfoMetricCollector.java
rename to test_framework/com/android/tradefed/device/metric/IonHeapInfoMetricCollector.java
diff --git a/src/com/android/tradefed/device/metric/MemInfoMetricCollector.java b/test_framework/com/android/tradefed/device/metric/MemInfoMetricCollector.java
similarity index 100%
rename from src/com/android/tradefed/device/metric/MemInfoMetricCollector.java
rename to test_framework/com/android/tradefed/device/metric/MemInfoMetricCollector.java
diff --git a/src/com/android/tradefed/device/metric/PagetypeInfoMetricCollector.java b/test_framework/com/android/tradefed/device/metric/PagetypeInfoMetricCollector.java
similarity index 100%
rename from src/com/android/tradefed/device/metric/PagetypeInfoMetricCollector.java
rename to test_framework/com/android/tradefed/device/metric/PagetypeInfoMetricCollector.java
diff --git a/src/com/android/tradefed/device/metric/PerfettoPullerMetricCollector.java b/test_framework/com/android/tradefed/device/metric/PerfettoPullerMetricCollector.java
similarity index 100%
rename from src/com/android/tradefed/device/metric/PerfettoPullerMetricCollector.java
rename to test_framework/com/android/tradefed/device/metric/PerfettoPullerMetricCollector.java
diff --git a/src/com/android/tradefed/device/metric/ProcessMaxMemoryCollector.java b/test_framework/com/android/tradefed/device/metric/ProcessMaxMemoryCollector.java
similarity index 100%
rename from src/com/android/tradefed/device/metric/ProcessMaxMemoryCollector.java
rename to test_framework/com/android/tradefed/device/metric/ProcessMaxMemoryCollector.java
diff --git a/src/com/android/tradefed/device/metric/RebootReasonCollector.java b/test_framework/com/android/tradefed/device/metric/RebootReasonCollector.java
similarity index 100%
rename from src/com/android/tradefed/device/metric/RebootReasonCollector.java
rename to test_framework/com/android/tradefed/device/metric/RebootReasonCollector.java
diff --git a/src/com/android/tradefed/device/metric/RuntimeRestartCollector.java b/test_framework/com/android/tradefed/device/metric/RuntimeRestartCollector.java
similarity index 97%
rename from src/com/android/tradefed/device/metric/RuntimeRestartCollector.java
rename to test_framework/com/android/tradefed/device/metric/RuntimeRestartCollector.java
index 16e28c1..1293f71 100644
--- a/src/com/android/tradefed/device/metric/RuntimeRestartCollector.java
+++ b/test_framework/com/android/tradefed/device/metric/RuntimeRestartCollector.java
@@ -216,6 +216,13 @@
/** Helper method to add metrics from StatsdStatsReport according to timestamps. */
private void addStatsdStatsBasedMetrics(
final Map<String, Metric> metrics, List<Integer> timestampsSecs, String serial) {
+ // Always add a count of system server crashes, regardless of whether there are any.
+ // The statsd metadata-based count is used as the atom-based data can miss runtime restart
+ // instances.
+ // TODO(b/135770315): Re-assess this after the root cause for missing instances in the atom
+ // -based results is found.
+ String countMetricKey = createMetricKey(METRIC_SUFFIX_COUNT, serial);
+ metrics.put(countMetricKey, stringToMetric(String.valueOf(timestampsSecs.size())));
// If there are runtime restarts, add a comma-separated list of timestamps.
if (!timestampsSecs.isEmpty()) {
// Store both the raw timestamp and the formatted, more readable version.
@@ -245,10 +252,6 @@
/** Helper method to add metrics from the AppCrashOccurred atoms according to timestamps. */
private void addAtomBasedMetrics(
final Map<String, Metric> metrics, List<Long> timestampsNanos, String serial) {
- // Always add a count of system server crashes, regardless of whether there are any.
- // The atom-based count is used as the statsd-metadata-based one tops out at 20.
- String countMetricKey = createMetricKey(METRIC_SUFFIX_COUNT, serial);
- metrics.put(countMetricKey, stringToMetric(String.valueOf(timestampsNanos.size())));
// If there are runtime restarts, add a comma-separated list of device uptime timestamps.
if (!timestampsNanos.isEmpty()) {
// Store both the raw timestamp and the formatted, more readable version.
diff --git a/src/com/android/tradefed/device/metric/ScheduleMultipleDeviceMetricCollector.java b/test_framework/com/android/tradefed/device/metric/ScheduleMultipleDeviceMetricCollector.java
similarity index 100%
rename from src/com/android/tradefed/device/metric/ScheduleMultipleDeviceMetricCollector.java
rename to test_framework/com/android/tradefed/device/metric/ScheduleMultipleDeviceMetricCollector.java
diff --git a/src/com/android/tradefed/device/metric/ScheduledDeviceMetricCollector.java b/test_framework/com/android/tradefed/device/metric/ScheduledDeviceMetricCollector.java
similarity index 100%
rename from src/com/android/tradefed/device/metric/ScheduledDeviceMetricCollector.java
rename to test_framework/com/android/tradefed/device/metric/ScheduledDeviceMetricCollector.java
diff --git a/src/com/android/tradefed/device/metric/TemperatureCollector.java b/test_framework/com/android/tradefed/device/metric/TemperatureCollector.java
similarity index 100%
rename from src/com/android/tradefed/device/metric/TemperatureCollector.java
rename to test_framework/com/android/tradefed/device/metric/TemperatureCollector.java
diff --git a/src/com/android/tradefed/device/metric/TraceCmdCollector.java b/test_framework/com/android/tradefed/device/metric/TraceCmdCollector.java
similarity index 100%
rename from src/com/android/tradefed/device/metric/TraceCmdCollector.java
rename to test_framework/com/android/tradefed/device/metric/TraceCmdCollector.java
diff --git a/src/com/android/tradefed/device/metric/TraceMetricCollector.java b/test_framework/com/android/tradefed/device/metric/TraceMetricCollector.java
similarity index 100%
rename from src/com/android/tradefed/device/metric/TraceMetricCollector.java
rename to test_framework/com/android/tradefed/device/metric/TraceMetricCollector.java
diff --git a/src/com/android/tradefed/result/JUnit4ResultForwarder.java b/test_framework/com/android/tradefed/result/JUnit4ResultForwarder.java
similarity index 100%
rename from src/com/android/tradefed/result/JUnit4ResultForwarder.java
rename to test_framework/com/android/tradefed/result/JUnit4ResultForwarder.java
diff --git a/src/com/android/tradefed/targetprep/AdditionalFilesInstaller.java b/test_framework/com/android/tradefed/targetprep/AdditionalFilesInstaller.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/AdditionalFilesInstaller.java
rename to test_framework/com/android/tradefed/targetprep/AdditionalFilesInstaller.java
diff --git a/src/com/android/tradefed/targetprep/AllTestAppsInstallSetup.java b/test_framework/com/android/tradefed/targetprep/AllTestAppsInstallSetup.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/AllTestAppsInstallSetup.java
rename to test_framework/com/android/tradefed/targetprep/AllTestAppsInstallSetup.java
diff --git a/src/com/android/tradefed/targetprep/AoaTargetPreparer.java b/test_framework/com/android/tradefed/targetprep/AoaTargetPreparer.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/AoaTargetPreparer.java
rename to test_framework/com/android/tradefed/targetprep/AoaTargetPreparer.java
diff --git a/src/com/android/tradefed/targetprep/AppSetup.java b/test_framework/com/android/tradefed/targetprep/AppSetup.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/AppSetup.java
rename to test_framework/com/android/tradefed/targetprep/AppSetup.java
diff --git a/src/com/android/tradefed/targetprep/CdmaDeviceFlasher.java b/test_framework/com/android/tradefed/targetprep/CdmaDeviceFlasher.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/CdmaDeviceFlasher.java
rename to test_framework/com/android/tradefed/targetprep/CdmaDeviceFlasher.java
diff --git a/src/com/android/tradefed/targetprep/CpuThrottlingWaiter.java b/test_framework/com/android/tradefed/targetprep/CpuThrottlingWaiter.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/CpuThrottlingWaiter.java
rename to test_framework/com/android/tradefed/targetprep/CpuThrottlingWaiter.java
diff --git a/src/com/android/tradefed/targetprep/CrashCollector.java b/test_framework/com/android/tradefed/targetprep/CrashCollector.java
similarity index 97%
rename from src/com/android/tradefed/targetprep/CrashCollector.java
rename to test_framework/com/android/tradefed/targetprep/CrashCollector.java
index 63f80e8..b9e55a1 100644
--- a/src/com/android/tradefed/targetprep/CrashCollector.java
+++ b/test_framework/com/android/tradefed/targetprep/CrashCollector.java
@@ -21,6 +21,7 @@
import com.android.tradefed.config.OptionClass;
import com.android.tradefed.device.BackgroundDeviceAction;
import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.DeviceProperties;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.LargeOutputReceiver;
import com.android.tradefed.log.ITestLogger;
@@ -62,7 +63,7 @@
return true;
}
// first get pseudo API level to check for platform support
- String codeName = device.getProperty("ro.build.version.codename").trim();
+ String codeName = device.getProperty(DeviceProperties.BUILD_CODENAME).trim();
int apiLevel = device.getApiLevel();
if (!"REL".equals(codeName)) {
apiLevel++;
diff --git a/src/com/android/tradefed/targetprep/DeviceStorageFiller.java b/test_framework/com/android/tradefed/targetprep/DeviceStorageFiller.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/DeviceStorageFiller.java
rename to test_framework/com/android/tradefed/targetprep/DeviceStorageFiller.java
diff --git a/src/com/android/tradefed/targetprep/DeviceStringPusher.java b/test_framework/com/android/tradefed/targetprep/DeviceStringPusher.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/DeviceStringPusher.java
rename to test_framework/com/android/tradefed/targetprep/DeviceStringPusher.java
diff --git a/src/com/android/tradefed/targetprep/DeviceWiper.java b/test_framework/com/android/tradefed/targetprep/DeviceWiper.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/DeviceWiper.java
rename to test_framework/com/android/tradefed/targetprep/DeviceWiper.java
diff --git a/src/com/android/tradefed/targetprep/DisableSELinuxTargetPreparer.java b/test_framework/com/android/tradefed/targetprep/DisableSELinuxTargetPreparer.java
similarity index 95%
rename from src/com/android/tradefed/targetprep/DisableSELinuxTargetPreparer.java
rename to test_framework/com/android/tradefed/targetprep/DisableSELinuxTargetPreparer.java
index 4344fcf..7993334 100644
--- a/src/com/android/tradefed/targetprep/DisableSELinuxTargetPreparer.java
+++ b/test_framework/com/android/tradefed/targetprep/DisableSELinuxTargetPreparer.java
@@ -56,7 +56,8 @@
result = device.executeShellV2Command("setenforce 0");
if (result.getStatus() != CommandStatus.SUCCESS) {
throw new TargetSetupError(
- "Disabling SELinux failed with status: " + result.getStatus());
+ "Disabling SELinux failed with status: " + result.getStatus(),
+ device.getDeviceDescriptor());
}
if (!mWasRoot) {
device.disableAdbRoot();
@@ -73,7 +74,7 @@
device.enableAdbRoot();
}
CLog.d("Enabling SELinux.");
- CommandResult result = device.executeShellV2Command("setenforce 1");
+ device.executeShellV2Command("setenforce 1");
if (!mWasRoot) {
device.disableAdbRoot();
}
diff --git a/src/com/android/tradefed/targetprep/EraseUserDataPreparer.java b/test_framework/com/android/tradefed/targetprep/EraseUserDataPreparer.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/EraseUserDataPreparer.java
rename to test_framework/com/android/tradefed/targetprep/EraseUserDataPreparer.java
diff --git a/src/com/android/tradefed/targetprep/FastbootCommandPreparer.java b/test_framework/com/android/tradefed/targetprep/FastbootCommandPreparer.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/FastbootCommandPreparer.java
rename to test_framework/com/android/tradefed/targetprep/FastbootCommandPreparer.java
diff --git a/src/com/android/tradefed/targetprep/FolderSaver.java b/test_framework/com/android/tradefed/targetprep/FolderSaver.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/FolderSaver.java
rename to test_framework/com/android/tradefed/targetprep/FolderSaver.java
diff --git a/src/com/android/tradefed/targetprep/InstallAllTestZipAppsSetup.java b/test_framework/com/android/tradefed/targetprep/InstallAllTestZipAppsSetup.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/InstallAllTestZipAppsSetup.java
rename to test_framework/com/android/tradefed/targetprep/InstallAllTestZipAppsSetup.java
diff --git a/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparer.java b/test_framework/com/android/tradefed/targetprep/InstallApexModuleTargetPreparer.java
similarity index 95%
rename from src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparer.java
rename to test_framework/com/android/tradefed/targetprep/InstallApexModuleTargetPreparer.java
index 9be70dd..8e66961 100644
--- a/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparer.java
+++ b/test_framework/com/android/tradefed/targetprep/InstallApexModuleTargetPreparer.java
@@ -171,7 +171,7 @@
installTrain(device, buildInfo, testAppFileNames, new String[] {"--staged"});
return;
}
- installTrain(device, buildInfo, testAppFileNames, null);
+ installTrain(device, buildInfo, testAppFileNames, new String[] {});
}
/**
@@ -185,9 +185,21 @@
protected void installTrain(
ITestDevice device,
IBuildInfo buildInfo,
- Collection<String> moduleFilenames,
+ List<String> moduleFilenames,
final String[] extraArgs)
throws TargetSetupError, DeviceNotAvailableException {
+ // TODO(b/137883918):remove after new adb is released, which supports installing
+ // single apk/apex using 'install-multi-package'
+ if (moduleFilenames.size() == 1) {
+ String moduleFileName = moduleFilenames.get(0);
+ File module = getLocalPathForFilename(buildInfo, moduleFileName, device);
+ device.installPackage(module, true, extraArgs);
+ if (moduleFileName.endsWith(APK_SUFFIX)) {
+ String packageName = parsePackageName(module, device.getDeviceDescriptor());
+ mApkInstalled.add(packageName);
+ }
+ return;
+ }
List<String> apkPackageNames = new ArrayList<>();
List<String> trainInstallCmd = new ArrayList<>();
@@ -498,8 +510,7 @@
List<String> testAppFileNames, ITestDevice device, IBuildInfo buildInfo)
throws TargetSetupError, DeviceNotAvailableException {
for (String moduleFileName : testAppFileNames) {
- if (moduleFileName.endsWith(APK_SUFFIX) &&
- isPersistentApk(moduleFileName, device, buildInfo)) {
+ if (isPersistentApk(moduleFileName, device, buildInfo)) {
return true;
}
}
@@ -516,6 +527,9 @@
*/
protected boolean isPersistentApk(String filename, ITestDevice device, IBuildInfo buildInfo)
throws TargetSetupError, DeviceNotAvailableException {
+ if (!filename.endsWith(APK_SUFFIX)) {
+ return false;
+ }
File moduleFile = getLocalPathForFilename(buildInfo, filename, device);
PackageInfo pkgInfo =
device.getAppPackageInfo(parsePackageName(moduleFile, device.getDeviceDescriptor()));
diff --git a/src/com/android/tradefed/targetprep/InstallApkSetup.java b/test_framework/com/android/tradefed/targetprep/InstallApkSetup.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/InstallApkSetup.java
rename to test_framework/com/android/tradefed/targetprep/InstallApkSetup.java
diff --git a/src/com/android/tradefed/targetprep/InstallBuildEnvApkSetup.java b/test_framework/com/android/tradefed/targetprep/InstallBuildEnvApkSetup.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/InstallBuildEnvApkSetup.java
rename to test_framework/com/android/tradefed/targetprep/InstallBuildEnvApkSetup.java
diff --git a/src/com/android/tradefed/targetprep/InstrumentationPreparer.java b/test_framework/com/android/tradefed/targetprep/InstrumentationPreparer.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/InstrumentationPreparer.java
rename to test_framework/com/android/tradefed/targetprep/InstrumentationPreparer.java
diff --git a/src/com/android/tradefed/targetprep/NativeLeakCollector.java b/test_framework/com/android/tradefed/targetprep/NativeLeakCollector.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/NativeLeakCollector.java
rename to test_framework/com/android/tradefed/targetprep/NativeLeakCollector.java
diff --git a/src/com/android/tradefed/targetprep/PerfettoPreparer.java b/test_framework/com/android/tradefed/targetprep/PerfettoPreparer.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/PerfettoPreparer.java
rename to test_framework/com/android/tradefed/targetprep/PerfettoPreparer.java
diff --git a/src/com/android/tradefed/targetprep/PushFileInvoker.java b/test_framework/com/android/tradefed/targetprep/PushFileInvoker.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/PushFileInvoker.java
rename to test_framework/com/android/tradefed/targetprep/PushFileInvoker.java
diff --git a/src/com/android/tradefed/targetprep/PushFilePreparer.java b/test_framework/com/android/tradefed/targetprep/PushFilePreparer.java
similarity index 96%
rename from src/com/android/tradefed/targetprep/PushFilePreparer.java
rename to test_framework/com/android/tradefed/targetprep/PushFilePreparer.java
index e7ed4da..02f209d 100644
--- a/src/com/android/tradefed/targetprep/PushFilePreparer.java
+++ b/test_framework/com/android/tradefed/targetprep/PushFilePreparer.java
@@ -197,6 +197,7 @@
} else {
CLog.e("Did not find any module directory for '%s'", mModuleName);
}
+
} catch (IOException e) {
CLog.w(
"Something went wrong while searching for the module '%s' "
@@ -234,6 +235,14 @@
CLog.w("Failed to find test files from directory.");
src = null;
}
+
+ if (src == null && testDir != null) {
+ // TODO(b/138416078): Once build dependency can be fixed and test required
+ // APKs are all under the test module directory, we can remove this fallback
+ // approach to do individual download from remote artifact.
+ // Try to stage the files from remote zip files.
+ src = buildInfo.stageRemoteFile(fileName, testDir);
+ }
}
return src;
}
diff --git a/src/com/android/tradefed/targetprep/PythonVirtualenvPreparer.java b/test_framework/com/android/tradefed/targetprep/PythonVirtualenvPreparer.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/PythonVirtualenvPreparer.java
rename to test_framework/com/android/tradefed/targetprep/PythonVirtualenvPreparer.java
diff --git a/src/com/android/tradefed/targetprep/RebootTargetPreparer.java b/test_framework/com/android/tradefed/targetprep/RebootTargetPreparer.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/RebootTargetPreparer.java
rename to test_framework/com/android/tradefed/targetprep/RebootTargetPreparer.java
diff --git a/src/com/android/tradefed/targetprep/RemoveSystemAppPreparer.java b/test_framework/com/android/tradefed/targetprep/RemoveSystemAppPreparer.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/RemoveSystemAppPreparer.java
rename to test_framework/com/android/tradefed/targetprep/RemoveSystemAppPreparer.java
diff --git a/src/com/android/tradefed/targetprep/RestartSystemServerTargetPreparer.java b/test_framework/com/android/tradefed/targetprep/RestartSystemServerTargetPreparer.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/RestartSystemServerTargetPreparer.java
rename to test_framework/com/android/tradefed/targetprep/RestartSystemServerTargetPreparer.java
diff --git a/src/com/android/tradefed/targetprep/RootTargetPreparer.java b/test_framework/com/android/tradefed/targetprep/RootTargetPreparer.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/RootTargetPreparer.java
rename to test_framework/com/android/tradefed/targetprep/RootTargetPreparer.java
diff --git a/src/com/android/tradefed/targetprep/RunHostCommandTargetPreparer.java b/test_framework/com/android/tradefed/targetprep/RunHostCommandTargetPreparer.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/RunHostCommandTargetPreparer.java
rename to test_framework/com/android/tradefed/targetprep/RunHostCommandTargetPreparer.java
diff --git a/src/com/android/tradefed/targetprep/SemaphoreTokenTargetPreparer.java b/test_framework/com/android/tradefed/targetprep/SemaphoreTokenTargetPreparer.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/SemaphoreTokenTargetPreparer.java
rename to test_framework/com/android/tradefed/targetprep/SemaphoreTokenTargetPreparer.java
diff --git a/src/com/android/tradefed/targetprep/SideloadOtaTargetPreparer.java b/test_framework/com/android/tradefed/targetprep/SideloadOtaTargetPreparer.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/SideloadOtaTargetPreparer.java
rename to test_framework/com/android/tradefed/targetprep/SideloadOtaTargetPreparer.java
diff --git a/src/com/android/tradefed/targetprep/StopServicesSetup.java b/test_framework/com/android/tradefed/targetprep/StopServicesSetup.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/StopServicesSetup.java
rename to test_framework/com/android/tradefed/targetprep/StopServicesSetup.java
diff --git a/src/com/android/tradefed/targetprep/SwitchUserTargetPreparer.java b/test_framework/com/android/tradefed/targetprep/SwitchUserTargetPreparer.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/SwitchUserTargetPreparer.java
rename to test_framework/com/android/tradefed/targetprep/SwitchUserTargetPreparer.java
diff --git a/src/com/android/tradefed/targetprep/SystemUpdaterDeviceFlasher.java b/test_framework/com/android/tradefed/targetprep/SystemUpdaterDeviceFlasher.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/SystemUpdaterDeviceFlasher.java
rename to test_framework/com/android/tradefed/targetprep/SystemUpdaterDeviceFlasher.java
diff --git a/src/com/android/tradefed/targetprep/TearDownPassThroughPreparer.java b/test_framework/com/android/tradefed/targetprep/TearDownPassThroughPreparer.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/TearDownPassThroughPreparer.java
rename to test_framework/com/android/tradefed/targetprep/TearDownPassThroughPreparer.java
diff --git a/src/com/android/tradefed/targetprep/TemperatureThrottlingWaiter.java b/test_framework/com/android/tradefed/targetprep/TemperatureThrottlingWaiter.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/TemperatureThrottlingWaiter.java
rename to test_framework/com/android/tradefed/targetprep/TemperatureThrottlingWaiter.java
diff --git a/src/com/android/tradefed/targetprep/TestFilePushSetup.java b/test_framework/com/android/tradefed/targetprep/TestFilePushSetup.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/TestFilePushSetup.java
rename to test_framework/com/android/tradefed/targetprep/TestFilePushSetup.java
diff --git a/src/com/android/tradefed/targetprep/TestSystemAppInstallSetup.java b/test_framework/com/android/tradefed/targetprep/TestSystemAppInstallSetup.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/TestSystemAppInstallSetup.java
rename to test_framework/com/android/tradefed/targetprep/TestSystemAppInstallSetup.java
diff --git a/src/com/android/tradefed/targetprep/TimeWaster.java b/test_framework/com/android/tradefed/targetprep/TimeWaster.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/TimeWaster.java
rename to test_framework/com/android/tradefed/targetprep/TimeWaster.java
diff --git a/src/com/android/tradefed/targetprep/UserCleaner.java b/test_framework/com/android/tradefed/targetprep/UserCleaner.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/UserCleaner.java
rename to test_framework/com/android/tradefed/targetprep/UserCleaner.java
diff --git a/src/com/android/tradefed/targetprep/WaitForDeviceDatetimePreparer.java b/test_framework/com/android/tradefed/targetprep/WaitForDeviceDatetimePreparer.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/WaitForDeviceDatetimePreparer.java
rename to test_framework/com/android/tradefed/targetprep/WaitForDeviceDatetimePreparer.java
diff --git a/src/com/android/tradefed/targetprep/WifiPreparer.java b/test_framework/com/android/tradefed/targetprep/WifiPreparer.java
similarity index 93%
rename from src/com/android/tradefed/targetprep/WifiPreparer.java
rename to test_framework/com/android/tradefed/targetprep/WifiPreparer.java
index ad5dee0..6d053ab 100644
--- a/src/com/android/tradefed/targetprep/WifiPreparer.java
+++ b/test_framework/com/android/tradefed/targetprep/WifiPreparer.java
@@ -20,6 +20,8 @@
import com.android.tradefed.config.OptionClass;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
import com.android.tradefed.log.LogUtil.CLog;
/**
@@ -74,6 +76,7 @@
throw new TargetSetupError("wifi-network not specified", device.getDeviceDescriptor());
}
+ InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.WIFI_AP_NAME, mWifiNetwork);
if (!device.connectToWifiNetworkIfNeeded(mWifiNetwork, mWifiPsk)) {
throw new TargetSetupError(String.format("Failed to connect to wifi network %s on %s",
mWifiNetwork, device.getSerialNumber()), device.getDeviceDescriptor());
diff --git a/src/com/android/tradefed/targetprep/adb/AdbStopServerPreparer.java b/test_framework/com/android/tradefed/targetprep/adb/AdbStopServerPreparer.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/adb/AdbStopServerPreparer.java
rename to test_framework/com/android/tradefed/targetprep/adb/AdbStopServerPreparer.java
diff --git a/src/com/android/tradefed/targetprep/app/NoApkTestSkipper.java b/test_framework/com/android/tradefed/targetprep/app/NoApkTestSkipper.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/app/NoApkTestSkipper.java
rename to test_framework/com/android/tradefed/targetprep/app/NoApkTestSkipper.java
diff --git a/src/com/android/tradefed/targetprep/companion/CheckPairingPreparer.java b/test_framework/com/android/tradefed/targetprep/companion/CheckPairingPreparer.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/companion/CheckPairingPreparer.java
rename to test_framework/com/android/tradefed/targetprep/companion/CheckPairingPreparer.java
diff --git a/src/com/android/tradefed/targetprep/companion/CompanionAllocator.java b/test_framework/com/android/tradefed/targetprep/companion/CompanionAllocator.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/companion/CompanionAllocator.java
rename to test_framework/com/android/tradefed/targetprep/companion/CompanionAllocator.java
diff --git a/src/com/android/tradefed/targetprep/companion/CompanionAwarePreparer.java b/test_framework/com/android/tradefed/targetprep/companion/CompanionAwarePreparer.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/companion/CompanionAwarePreparer.java
rename to test_framework/com/android/tradefed/targetprep/companion/CompanionAwarePreparer.java
diff --git a/src/com/android/tradefed/targetprep/companion/CompanionDeviceTracker.java b/test_framework/com/android/tradefed/targetprep/companion/CompanionDeviceTracker.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/companion/CompanionDeviceTracker.java
rename to test_framework/com/android/tradefed/targetprep/companion/CompanionDeviceTracker.java
diff --git a/src/com/android/tradefed/targetprep/companion/CompanionRunCommandTargetPreparer.java b/test_framework/com/android/tradefed/targetprep/companion/CompanionRunCommandTargetPreparer.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/companion/CompanionRunCommandTargetPreparer.java
rename to test_framework/com/android/tradefed/targetprep/companion/CompanionRunCommandTargetPreparer.java
diff --git a/src/com/android/tradefed/targetprep/companion/CompanionTestAppInstallSetup.java b/test_framework/com/android/tradefed/targetprep/companion/CompanionTestAppInstallSetup.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/companion/CompanionTestAppInstallSetup.java
rename to test_framework/com/android/tradefed/targetprep/companion/CompanionTestAppInstallSetup.java
diff --git a/src/com/android/tradefed/targetprep/multi/DynamicSystemPreparer.java b/test_framework/com/android/tradefed/targetprep/multi/DynamicSystemPreparer.java
similarity index 97%
rename from src/com/android/tradefed/targetprep/multi/DynamicSystemPreparer.java
rename to test_framework/com/android/tradefed/targetprep/multi/DynamicSystemPreparer.java
index 174bb9c..2c3ca05 100644
--- a/src/com/android/tradefed/targetprep/multi/DynamicSystemPreparer.java
+++ b/test_framework/com/android/tradefed/targetprep/multi/DynamicSystemPreparer.java
@@ -110,14 +110,15 @@
// the waitForDeviceOnline may block and we need to correct the 'i'
// which is used to measure timeout accordingly
if (!isDSURunning(device)) {
- throw new TargetSetupError("Timeout to boot into DSU");
+ throw new TargetSetupError(
+ "Timeout to boot into DSU", device.getDeviceDescriptor());
}
CommandResult result = device.executeShellV2Command("gsi_tool enable");
if (CommandStatus.SUCCESS.equals(result.getStatus())) {
// success
return;
} else {
- throw new TargetSetupError("fail on gsi_tool enable");
+ throw new TargetSetupError("fail on gsi_tool enable", device.getDeviceDescriptor());
}
} catch (IOException e) {
CLog.e(e);
diff --git a/src/com/android/tradefed/targetprep/multi/MergeMultiBuildTargetPreparer.java b/test_framework/com/android/tradefed/targetprep/multi/MergeMultiBuildTargetPreparer.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/multi/MergeMultiBuildTargetPreparer.java
rename to test_framework/com/android/tradefed/targetprep/multi/MergeMultiBuildTargetPreparer.java
diff --git a/src/com/android/tradefed/targetprep/multi/MixImageZipPreparer.java b/test_framework/com/android/tradefed/targetprep/multi/MixImageZipPreparer.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/multi/MixImageZipPreparer.java
rename to test_framework/com/android/tradefed/targetprep/multi/MixImageZipPreparer.java
diff --git a/src/com/android/tradefed/targetprep/suite/SuiteApkInstaller.java b/test_framework/com/android/tradefed/targetprep/suite/SuiteApkInstaller.java
similarity index 89%
rename from src/com/android/tradefed/targetprep/suite/SuiteApkInstaller.java
rename to test_framework/com/android/tradefed/targetprep/suite/SuiteApkInstaller.java
index bb063c9..cdf9554 100644
--- a/src/com/android/tradefed/targetprep/suite/SuiteApkInstaller.java
+++ b/test_framework/com/android/tradefed/targetprep/suite/SuiteApkInstaller.java
@@ -78,7 +78,15 @@
IDeviceBuildInfo deviceBuildInfo = (IDeviceBuildInfo) buildInfo;
File testDir = deviceBuildInfo.getTestsDir();
if (testDir != null && testDir.isDirectory()) {
- return FileUtil.findFile(testDir, apkFileName);
+ File apkFile = FileUtil.findFile(testDir, apkFileName);
+ if (apkFile == null) {
+ // TODO(b/138416078): Once build dependency can be fixed and test required
+ // APKs are all under the test module directory, we can remove this fallback
+ // approach to do individual download from remote artifact.
+ // Try to stage the files from remote zip files.
+ apkFile = buildInfo.stageRemoteFile(apkFileName, testDir);
+ }
+ return apkFile;
}
}
return null;
diff --git a/src/com/android/tradefed/testtype/AndroidJUnitTest.java b/test_framework/com/android/tradefed/testtype/AndroidJUnitTest.java
similarity index 100%
rename from src/com/android/tradefed/testtype/AndroidJUnitTest.java
rename to test_framework/com/android/tradefed/testtype/AndroidJUnitTest.java
diff --git a/src/com/android/tradefed/testtype/CodeCoverageReportFormat.java b/test_framework/com/android/tradefed/testtype/CodeCoverageReportFormat.java
similarity index 100%
rename from src/com/android/tradefed/testtype/CodeCoverageReportFormat.java
rename to test_framework/com/android/tradefed/testtype/CodeCoverageReportFormat.java
diff --git a/src/com/android/tradefed/testtype/CodeCoverageTest.java b/test_framework/com/android/tradefed/testtype/CodeCoverageTest.java
similarity index 100%
rename from src/com/android/tradefed/testtype/CodeCoverageTest.java
rename to test_framework/com/android/tradefed/testtype/CodeCoverageTest.java
diff --git a/src/com/android/tradefed/testtype/CompanionAwareTest.java b/test_framework/com/android/tradefed/testtype/CompanionAwareTest.java
similarity index 100%
rename from src/com/android/tradefed/testtype/CompanionAwareTest.java
rename to test_framework/com/android/tradefed/testtype/CompanionAwareTest.java
diff --git a/src/com/android/tradefed/testtype/DeviceJUnit4ClassRunner.java b/test_framework/com/android/tradefed/testtype/DeviceJUnit4ClassRunner.java
similarity index 100%
rename from src/com/android/tradefed/testtype/DeviceJUnit4ClassRunner.java
rename to test_framework/com/android/tradefed/testtype/DeviceJUnit4ClassRunner.java
diff --git a/src/com/android/tradefed/testtype/DeviceSuite.java b/test_framework/com/android/tradefed/testtype/DeviceSuite.java
similarity index 100%
rename from src/com/android/tradefed/testtype/DeviceSuite.java
rename to test_framework/com/android/tradefed/testtype/DeviceSuite.java
diff --git a/src/com/android/tradefed/testtype/DeviceTestCase.java b/test_framework/com/android/tradefed/testtype/DeviceTestCase.java
similarity index 100%
rename from src/com/android/tradefed/testtype/DeviceTestCase.java
rename to test_framework/com/android/tradefed/testtype/DeviceTestCase.java
diff --git a/src/com/android/tradefed/testtype/DeviceTestResult.java b/test_framework/com/android/tradefed/testtype/DeviceTestResult.java
similarity index 100%
rename from src/com/android/tradefed/testtype/DeviceTestResult.java
rename to test_framework/com/android/tradefed/testtype/DeviceTestResult.java
diff --git a/src/com/android/tradefed/testtype/DeviceTestSuite.java b/test_framework/com/android/tradefed/testtype/DeviceTestSuite.java
similarity index 100%
rename from src/com/android/tradefed/testtype/DeviceTestSuite.java
rename to test_framework/com/android/tradefed/testtype/DeviceTestSuite.java
diff --git a/src/com/android/tradefed/testtype/GTest.java b/test_framework/com/android/tradefed/testtype/GTest.java
similarity index 98%
rename from src/com/android/tradefed/testtype/GTest.java
rename to test_framework/com/android/tradefed/testtype/GTest.java
index 2898afa..052fe23 100644
--- a/src/com/android/tradefed/testtype/GTest.java
+++ b/test_framework/com/android/tradefed/testtype/GTest.java
@@ -389,7 +389,9 @@
mDevice.executeShellCommand("stop");
}
// Insert the coverage listener if code coverage collection is enabled.
- listener = addNativeCoverageListenerIfEnabled(mDevice, listener);
+ listener =
+ addNativeCoverageListenerIfEnabled(
+ mDevice, mCoverageFlush, mCoverageProcesses, listener);
NativeCodeCoverageFlusher flusher = new NativeCodeCoverageFlusher(mDevice);
Throwable throwable = null;
@@ -406,10 +408,6 @@
throw t;
} finally {
if (!(throwable instanceof DeviceNotAvailableException)) {
- if (isCoverageEnabled() && mCoverageFlush) {
- flusher.forceCoverageFlush(mCoverageProcesses);
- }
-
if (mStopRuntime) {
mDevice.executeShellCommand("start");
mDevice.waitForDeviceAvailable();
diff --git a/src/com/android/tradefed/testtype/GTestBase.java b/test_framework/com/android/tradefed/testtype/GTestBase.java
similarity index 97%
rename from src/com/android/tradefed/testtype/GTestBase.java
rename to test_framework/com/android/tradefed/testtype/GTestBase.java
index eee17c5..defe996 100644
--- a/src/com/android/tradefed/testtype/GTestBase.java
+++ b/test_framework/com/android/tradefed/testtype/GTestBase.java
@@ -523,6 +523,10 @@
gTestCmdLine.append(String.format("LD_LIBRARY_PATH=%s ", mLdLibraryPath));
}
+ if (isCoverageEnabled()) {
+ gTestCmdLine.append("GCOV_PREFIX=/data/misc/trace/testcoverage ");
+ }
+
// su to requested user
if (mRunTestAs != null) {
gTestCmdLine.append(String.format("su %s ", mRunTestAs));
@@ -559,13 +563,19 @@
* Adds a {@link NativeCodeCoverageListener} to the chain if code coverage is enabled.
*
* @param device the device to pull the coverage results from
+ * @param coverageFlush whether to flush coverage before pulling the measurements
+ * @param coverageProcesses the processes to flush coverage from
* @param listener the original listener
* @return a chained listener if code coverage is enabled, otherwise the original listener
*/
protected ITestInvocationListener addNativeCoverageListenerIfEnabled(
- ITestDevice device, ITestInvocationListener listener) {
+ ITestDevice device,
+ boolean coverageFlush,
+ List<String> coverageProcesses,
+ ITestInvocationListener listener) {
if (mCoverage) {
- return new NativeCodeCoverageListener(device, listener);
+ return new NativeCodeCoverageListener(
+ device, coverageFlush, coverageProcesses, listener);
}
return listener;
}
diff --git a/src/com/android/tradefed/testtype/GTestListTestParser.java b/test_framework/com/android/tradefed/testtype/GTestListTestParser.java
similarity index 100%
rename from src/com/android/tradefed/testtype/GTestListTestParser.java
rename to test_framework/com/android/tradefed/testtype/GTestListTestParser.java
diff --git a/src/com/android/tradefed/testtype/GTestResultParser.java b/test_framework/com/android/tradefed/testtype/GTestResultParser.java
similarity index 100%
rename from src/com/android/tradefed/testtype/GTestResultParser.java
rename to test_framework/com/android/tradefed/testtype/GTestResultParser.java
diff --git a/src/com/android/tradefed/testtype/GTestXmlResultParser.java b/test_framework/com/android/tradefed/testtype/GTestXmlResultParser.java
similarity index 100%
rename from src/com/android/tradefed/testtype/GTestXmlResultParser.java
rename to test_framework/com/android/tradefed/testtype/GTestXmlResultParser.java
diff --git a/src/com/android/tradefed/testtype/GoogleBenchmarkResultParser.java b/test_framework/com/android/tradefed/testtype/GoogleBenchmarkResultParser.java
similarity index 100%
rename from src/com/android/tradefed/testtype/GoogleBenchmarkResultParser.java
rename to test_framework/com/android/tradefed/testtype/GoogleBenchmarkResultParser.java
diff --git a/src/com/android/tradefed/testtype/GoogleBenchmarkTest.java b/test_framework/com/android/tradefed/testtype/GoogleBenchmarkTest.java
similarity index 100%
rename from src/com/android/tradefed/testtype/GoogleBenchmarkTest.java
rename to test_framework/com/android/tradefed/testtype/GoogleBenchmarkTest.java
diff --git a/src/com/android/tradefed/testtype/HostGTest.java b/test_framework/com/android/tradefed/testtype/HostGTest.java
similarity index 99%
rename from src/com/android/tradefed/testtype/HostGTest.java
rename to test_framework/com/android/tradefed/testtype/HostGTest.java
index bfb4f54..8619236 100644
--- a/src/com/android/tradefed/testtype/HostGTest.java
+++ b/test_framework/com/android/tradefed/testtype/HostGTest.java
@@ -180,6 +180,8 @@
String.format("Command run timed out after %d ms", maxTestTimeMs));
case EXCEPTION:
throw new RuntimeException("Command run failed with exception");
+ default:
+ break;
}
} finally {
resultParser.flush();
diff --git a/src/com/android/tradefed/testtype/HostTest.java b/test_framework/com/android/tradefed/testtype/HostTest.java
similarity index 98%
rename from src/com/android/tradefed/testtype/HostTest.java
rename to test_framework/com/android/tradefed/testtype/HostTest.java
index af30957..f5d7731 100644
--- a/src/com/android/tradefed/testtype/HostTest.java
+++ b/test_framework/com/android/tradefed/testtype/HostTest.java
@@ -871,6 +871,11 @@
classes.add(cls);
classNames.add(className);
}
+ } catch (UnsupportedClassVersionError ucve) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Could not load class %s from jar %s. Reason:\n%s",
+ className, jarName, StreamUtil.getStackTrace(ucve)));
} catch (ClassNotFoundException cnfe) {
throw new IllegalArgumentException(
String.format("Cannot find test class %s", className));
diff --git a/src/com/android/tradefed/testtype/InstalledInstrumentationsTest.java b/test_framework/com/android/tradefed/testtype/InstalledInstrumentationsTest.java
similarity index 100%
rename from src/com/android/tradefed/testtype/InstalledInstrumentationsTest.java
rename to test_framework/com/android/tradefed/testtype/InstalledInstrumentationsTest.java
diff --git a/src/com/android/tradefed/testtype/InstrumentationFileTest.java b/test_framework/com/android/tradefed/testtype/InstrumentationFileTest.java
similarity index 100%
rename from src/com/android/tradefed/testtype/InstrumentationFileTest.java
rename to test_framework/com/android/tradefed/testtype/InstrumentationFileTest.java
diff --git a/src/com/android/tradefed/testtype/InstrumentationSerialTest.java b/test_framework/com/android/tradefed/testtype/InstrumentationSerialTest.java
similarity index 100%
rename from src/com/android/tradefed/testtype/InstrumentationSerialTest.java
rename to test_framework/com/android/tradefed/testtype/InstrumentationSerialTest.java
diff --git a/src/com/android/tradefed/testtype/InstrumentationTest.java b/test_framework/com/android/tradefed/testtype/InstrumentationTest.java
similarity index 100%
rename from src/com/android/tradefed/testtype/InstrumentationTest.java
rename to test_framework/com/android/tradefed/testtype/InstrumentationTest.java
diff --git a/src/com/android/tradefed/testtype/JUnitRunUtil.java b/test_framework/com/android/tradefed/testtype/JUnitRunUtil.java
similarity index 100%
rename from src/com/android/tradefed/testtype/JUnitRunUtil.java
rename to test_framework/com/android/tradefed/testtype/JUnitRunUtil.java
diff --git a/src/com/android/tradefed/testtype/JavaCodeCoverageListener.java b/test_framework/com/android/tradefed/testtype/JavaCodeCoverageListener.java
similarity index 100%
rename from src/com/android/tradefed/testtype/JavaCodeCoverageListener.java
rename to test_framework/com/android/tradefed/testtype/JavaCodeCoverageListener.java
diff --git a/src/com/android/tradefed/testtype/MetricTestCase.java b/test_framework/com/android/tradefed/testtype/MetricTestCase.java
similarity index 100%
rename from src/com/android/tradefed/testtype/MetricTestCase.java
rename to test_framework/com/android/tradefed/testtype/MetricTestCase.java
diff --git a/src/com/android/tradefed/testtype/NativeBenchmarkTest.java b/test_framework/com/android/tradefed/testtype/NativeBenchmarkTest.java
similarity index 100%
rename from src/com/android/tradefed/testtype/NativeBenchmarkTest.java
rename to test_framework/com/android/tradefed/testtype/NativeBenchmarkTest.java
diff --git a/src/com/android/tradefed/testtype/NativeBenchmarkTestParser.java b/test_framework/com/android/tradefed/testtype/NativeBenchmarkTestParser.java
similarity index 100%
rename from src/com/android/tradefed/testtype/NativeBenchmarkTestParser.java
rename to test_framework/com/android/tradefed/testtype/NativeBenchmarkTestParser.java
diff --git a/src/com/android/tradefed/testtype/NativeCodeCoverageListener.java b/test_framework/com/android/tradefed/testtype/NativeCodeCoverageListener.java
similarity index 79%
rename from src/com/android/tradefed/testtype/NativeCodeCoverageListener.java
rename to test_framework/com/android/tradefed/testtype/NativeCodeCoverageListener.java
index dca83db..713c1fc 100644
--- a/src/com/android/tradefed/testtype/NativeCodeCoverageListener.java
+++ b/test_framework/com/android/tradefed/testtype/NativeCodeCoverageListener.java
@@ -26,9 +26,11 @@
import com.android.tradefed.result.LogDataType;
import com.android.tradefed.result.ResultForwarder;
import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.NativeCodeCoverageFlusher;
import com.android.tradefed.util.ZipUtil;
import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
import java.io.File;
import java.io.IOException;
@@ -37,6 +39,7 @@
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
/**
* A {@link ResultForwarder} that will pull native coverage measurements off of the device and log
@@ -44,17 +47,35 @@
*/
public final class NativeCodeCoverageListener extends ResultForwarder {
- private static final String NATIVE_COVERAGE_DEVICE_PATH = "/data/misc/trace/proc/self/cwd/out";
+ private static final String NATIVE_COVERAGE_DEVICE_PATH = "/data/misc/trace";
private static final String COVERAGE_FILE_LIST_COMMAND =
String.format("find %s -name '*.gcda'", NATIVE_COVERAGE_DEVICE_PATH);
+ private final boolean mFlushCoverage;
+ private final List<String> mCoverageProcesses;
private final ITestDevice mDevice;
+ private final NativeCodeCoverageFlusher mFlusher;
private String mCurrentRunName;
public NativeCodeCoverageListener(ITestDevice device, ITestInvocationListener... listeners) {
super(listeners);
mDevice = device;
+ mFlushCoverage = false;
+ mCoverageProcesses = ImmutableList.of();
+ mFlusher = new NativeCodeCoverageFlusher(mDevice);
+ }
+
+ public NativeCodeCoverageListener(
+ ITestDevice device,
+ boolean flushCoverage,
+ List<String> coverageProcesses,
+ ITestInvocationListener... listeners) {
+ super(listeners);
+ mDevice = device;
+ mFlushCoverage = flushCoverage;
+ mCoverageProcesses = ImmutableList.copyOf(coverageProcesses);
+ mFlusher = new NativeCodeCoverageFlusher(mDevice);
}
@Override
@@ -72,8 +93,15 @@
try {
localDir = FileUtil.createTempDir("native_coverage");
- // Enable abd root on the device, otherwise the list command will fail.
+ // Enable abd root on the device, otherwise the following commands will fail.
verify(mDevice.enableAdbRoot(), "Failed to enable adb root.");
+
+ // Flush cross-process coverage.
+ if (mFlushCoverage) {
+ mFlusher.forceCoverageFlush(mCoverageProcesses);
+ }
+
+ // List native coverage files on the device and pull them.
String findResult = mDevice.executeShellCommand(COVERAGE_FILE_LIST_COMMAND);
Path devicePathRoot = Paths.get(NATIVE_COVERAGE_DEVICE_PATH);
diff --git a/src/com/android/tradefed/testtype/NativeStressTest.java b/test_framework/com/android/tradefed/testtype/NativeStressTest.java
similarity index 100%
rename from src/com/android/tradefed/testtype/NativeStressTest.java
rename to test_framework/com/android/tradefed/testtype/NativeStressTest.java
diff --git a/src/com/android/tradefed/testtype/NativeStressTestParser.java b/test_framework/com/android/tradefed/testtype/NativeStressTestParser.java
similarity index 100%
rename from src/com/android/tradefed/testtype/NativeStressTestParser.java
rename to test_framework/com/android/tradefed/testtype/NativeStressTestParser.java
diff --git a/src/com/android/tradefed/testtype/UiAutomatorRunner.java b/test_framework/com/android/tradefed/testtype/UiAutomatorRunner.java
similarity index 100%
rename from src/com/android/tradefed/testtype/UiAutomatorRunner.java
rename to test_framework/com/android/tradefed/testtype/UiAutomatorRunner.java
diff --git a/src/com/android/tradefed/testtype/UiAutomatorTest.java b/test_framework/com/android/tradefed/testtype/UiAutomatorTest.java
similarity index 100%
rename from src/com/android/tradefed/testtype/UiAutomatorTest.java
rename to test_framework/com/android/tradefed/testtype/UiAutomatorTest.java
diff --git a/src/com/android/tradefed/testtype/host/PrettyTestEventLogger.java b/test_framework/com/android/tradefed/testtype/host/PrettyTestEventLogger.java
similarity index 100%
rename from src/com/android/tradefed/testtype/host/PrettyTestEventLogger.java
rename to test_framework/com/android/tradefed/testtype/host/PrettyTestEventLogger.java
diff --git a/src/com/android/tradefed/testtype/junit4/BaseHostJUnit4Test.java b/test_framework/com/android/tradefed/testtype/junit4/BaseHostJUnit4Test.java
similarity index 100%
rename from src/com/android/tradefed/testtype/junit4/BaseHostJUnit4Test.java
rename to test_framework/com/android/tradefed/testtype/junit4/BaseHostJUnit4Test.java
diff --git a/src/com/android/tradefed/testtype/junit4/CarryDnaeError.java b/test_framework/com/android/tradefed/testtype/junit4/CarryDnaeError.java
similarity index 100%
rename from src/com/android/tradefed/testtype/junit4/CarryDnaeError.java
rename to test_framework/com/android/tradefed/testtype/junit4/CarryDnaeError.java
diff --git a/src/com/android/tradefed/testtype/junit4/DeviceParameterizedRunner.java b/test_framework/com/android/tradefed/testtype/junit4/DeviceParameterizedRunner.java
similarity index 100%
rename from src/com/android/tradefed/testtype/junit4/DeviceParameterizedRunner.java
rename to test_framework/com/android/tradefed/testtype/junit4/DeviceParameterizedRunner.java
diff --git a/src/com/android/tradefed/testtype/junit4/DeviceTestRunOptions.java b/test_framework/com/android/tradefed/testtype/junit4/DeviceTestRunOptions.java
similarity index 100%
rename from src/com/android/tradefed/testtype/junit4/DeviceTestRunOptions.java
rename to test_framework/com/android/tradefed/testtype/junit4/DeviceTestRunOptions.java
diff --git a/src/com/android/tradefed/testtype/junit4/LongevityHostRunner.java b/test_framework/com/android/tradefed/testtype/junit4/LongevityHostRunner.java
similarity index 100%
rename from src/com/android/tradefed/testtype/junit4/LongevityHostRunner.java
rename to test_framework/com/android/tradefed/testtype/junit4/LongevityHostRunner.java
diff --git a/src/com/android/tradefed/testtype/junit4/RunNotifierWrapper.java b/test_framework/com/android/tradefed/testtype/junit4/RunNotifierWrapper.java
similarity index 100%
rename from src/com/android/tradefed/testtype/junit4/RunNotifierWrapper.java
rename to test_framework/com/android/tradefed/testtype/junit4/RunNotifierWrapper.java
diff --git a/src/com/android/tradefed/testtype/junit4/builder/DeviceJUnit4ClassRunnerBuilder.java b/test_framework/com/android/tradefed/testtype/junit4/builder/DeviceJUnit4ClassRunnerBuilder.java
similarity index 100%
rename from src/com/android/tradefed/testtype/junit4/builder/DeviceJUnit4ClassRunnerBuilder.java
rename to test_framework/com/android/tradefed/testtype/junit4/builder/DeviceJUnit4ClassRunnerBuilder.java
diff --git a/src/com/android/tradefed/testtype/suite/AtestRunner.java b/test_framework/com/android/tradefed/testtype/suite/AtestRunner.java
similarity index 100%
rename from src/com/android/tradefed/testtype/suite/AtestRunner.java
rename to test_framework/com/android/tradefed/testtype/suite/AtestRunner.java
diff --git a/src/com/android/tradefed/util/clockwork/ClockworkUtils.java b/test_framework/com/android/tradefed/util/clockwork/ClockworkUtils.java
similarity index 100%
rename from src/com/android/tradefed/util/clockwork/ClockworkUtils.java
rename to test_framework/com/android/tradefed/util/clockwork/ClockworkUtils.java
diff --git a/src/com/android/tradefed/util/statsd/ConfigUtil.java b/test_framework/com/android/tradefed/util/statsd/ConfigUtil.java
similarity index 100%
rename from src/com/android/tradefed/util/statsd/ConfigUtil.java
rename to test_framework/com/android/tradefed/util/statsd/ConfigUtil.java
diff --git a/src/com/android/tradefed/util/statsd/MetricUtil.java b/test_framework/com/android/tradefed/util/statsd/MetricUtil.java
similarity index 100%
rename from src/com/android/tradefed/util/statsd/MetricUtil.java
rename to test_framework/com/android/tradefed/util/statsd/MetricUtil.java
diff --git a/test_result_interfaces/README.md b/test_result_interfaces/README.md
new file mode 100644
index 0000000..42fbdf1
--- /dev/null
+++ b/test_result_interfaces/README.md
@@ -0,0 +1,4 @@
+# Trade Federation Test Result Component
+
+A Tradefed component that describes how test cases results
+and logs are represented.
diff --git a/tests/.classpath b/tests/.classpath
index 79969f2..24d5cfa 100644
--- a/tests/.classpath
+++ b/tests/.classpath
@@ -23,5 +23,6 @@
<classpathentry kind="var" path="TRADEFED_ROOT/out/soong/.intermediates/prebuilts/tools/common/m2/truth-prebuilt-jar/linux_glibc_common/combined/truth-prebuilt-jar.jar"/>
<classpathentry kind="var" path="TRADEFED_ROOT/out/soong/.intermediates/external/jacoco/jacoco-cli/linux_glibc_common/combined/jacoco-cli.jar"/>
<classpathentry kind="var" path="TRADEFED_ROOT/out/soong/.intermediates/tools/tradefederation/core/tradefed-protos/linux_glibc_common/combined/tradefed-protos.jar"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/platform-annotations"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/tests/res/config/tf/unit-runner-tests.xml b/tests/res/config/tf/unit-runner-tests.xml
new file mode 100644
index 0000000..75c86cf
--- /dev/null
+++ b/tests/res/config/tf/unit-runner-tests.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Executes the TF unit tests">
+ <!-- Use HostTest runner to have JUnit3 and JUnit4 Suite support -->
+ <test class="com.android.tradefed.testtype.HostTest" >
+ <option name="class" value="com.android.loganalysis.UnitTests" />
+ <option name="class" value="com.android.tradefed.UnitTests" />
+ <option name="class" value="com.android.tradefed.prodtests.UnitTests" />
+ </test>
+</configuration>
diff --git a/tests/res/config/tf/unit-runner.xml b/tests/res/config/tf/unit-runner.xml
index 8c11a96..ddc527c 100644
--- a/tests/res/config/tf/unit-runner.xml
+++ b/tests/res/config/tf/unit-runner.xml
@@ -17,12 +17,9 @@
<option name="loop" value="false" />
<option name="null-device" value="true" />
<build_provider class="com.android.tradefed.build.StubBuildProvider" />
- <!-- Use HostTest runner to have JUnit3 and JUnit4 Suite support -->
- <test class="com.android.tradefed.testtype.HostTest" >
- <option name="class" value="com.android.loganalysis.UnitTests" />
- <option name="class" value="com.android.tradefed.UnitTests" />
- <option name="class" value="com.android.tradefed.prodtests.UnitTests" />
- </test>
+
+ <include name="tf/unit-runner-tests" />
+
<logger class="com.android.tradefed.log.FileLogger" />
<result_reporter class="com.android.tradefed.result.SubprocessResultsReporter">
<option name="output-test-log" value="true" />
diff --git a/tests/src/com/android/tradefed/UnitTests.java b/tests/src/com/android/tradefed/UnitTests.java
index 1f5647d..650ef8c 100644
--- a/tests/src/com/android/tradefed/UnitTests.java
+++ b/tests/src/com/android/tradefed/UnitTests.java
@@ -184,7 +184,7 @@
import com.android.tradefed.targetprep.AllTestAppsInstallSetupTest;
import com.android.tradefed.targetprep.AoaTargetPreparerTest;
import com.android.tradefed.targetprep.AppSetupTest;
-import com.android.tradefed.targetprep.BuildInfoAttributePreparerTest;
+import com.android.tradefed.targetprep.BaseTargetPreparerTest;
import com.android.tradefed.targetprep.CreateUserPreparerTest;
import com.android.tradefed.targetprep.DefaultTestsZipInstallerTest;
import com.android.tradefed.targetprep.DeviceFlashPreparerTest;
@@ -198,7 +198,6 @@
import com.android.tradefed.targetprep.InstallApexModuleTargetPreparerTest;
import com.android.tradefed.targetprep.InstallApkSetupTest;
import com.android.tradefed.targetprep.InstrumentationPreparerTest;
-import com.android.tradefed.targetprep.PreloadedClassesPreparerTest;
import com.android.tradefed.targetprep.PushFilePreparerTest;
import com.android.tradefed.targetprep.PythonVirtualenvPreparerTest;
import com.android.tradefed.targetprep.RebootTargetPreparerTest;
@@ -211,7 +210,6 @@
import com.android.tradefed.targetprep.SystemUpdaterDeviceFlasherTest;
import com.android.tradefed.targetprep.TestAppInstallSetupTest;
import com.android.tradefed.targetprep.TestFilePushSetupTest;
-import com.android.tradefed.targetprep.TimeSetterTargetPreparerTest;
import com.android.tradefed.targetprep.UserCleanerTest;
import com.android.tradefed.targetprep.adb.AdbStopServerPreparerTest;
import com.android.tradefed.targetprep.app.NoApkTestSkipperTest;
@@ -278,6 +276,7 @@
import com.android.tradefed.testtype.suite.module.NativeBridgeModuleControllerTest;
import com.android.tradefed.testtype.suite.params.InstantAppHandlerTest;
import com.android.tradefed.testtype.suite.params.ModuleParametersHelperTest;
+import com.android.tradefed.testtype.suite.params.SecondaryUserHandlerTest;
import com.android.tradefed.testtype.suite.retry.ResultsPlayerTest;
import com.android.tradefed.testtype.suite.retry.RetryReschedulerTest;
import com.android.tradefed.util.AaptParserTest;
@@ -344,6 +343,7 @@
import com.android.tradefed.util.net.XmlRpcHelperTest;
import com.android.tradefed.util.proto.TestRecordProtoUtilTest;
import com.android.tradefed.util.proto.TfMetricProtoUtilTest;
+import com.android.tradefed.util.RemoteZipTest;
import com.android.tradefed.util.sl4a.Sl4aClientTest;
import com.android.tradefed.util.sl4a.Sl4aEventDispatcherTest;
import com.android.tradefed.util.statsd.ConfigUtilTest;
@@ -578,7 +578,7 @@
AllTestAppsInstallSetupTest.class,
AoaTargetPreparerTest.class,
AppSetupTest.class,
- BuildInfoAttributePreparerTest.class,
+ BaseTargetPreparerTest.class,
CreateUserPreparerTest.class,
DefaultTestsZipInstallerTest.class,
DeviceFlashPreparerTest.class,
@@ -593,7 +593,6 @@
InstallApexModuleTargetPreparerTest.class,
InstallApkSetupTest.class,
InstrumentationPreparerTest.class,
- PreloadedClassesPreparerTest.class,
PushFilePreparerTest.class,
PythonVirtualenvPreparerTest.class,
RebootTargetPreparerTest.class,
@@ -605,7 +604,6 @@
SystemUpdaterDeviceFlasherTest.class,
TestAppInstallSetupTest.class,
TestFilePushSetupTest.class,
- TimeSetterTargetPreparerTest.class,
SwitchUserTargetPreparerTest.class,
UserCleanerTest.class,
@@ -715,6 +713,7 @@
// testtype/suite/params
InstantAppHandlerTest.class,
ModuleParametersHelperTest.class,
+ SecondaryUserHandlerTest.class,
// testtype/suite/retry
ResultsPlayerTest.class,
@@ -756,6 +755,7 @@
PsParserTest.class,
QuotationAwareTokenizerTest.class,
RegexTrieTest.class,
+ RemoteZipTest.class,
RunUtilTest.class,
SerializationUtilTest.class,
ShellOutputReceiverStreamTest.class,
diff --git a/tests/src/com/android/tradefed/config/ConfigurationTest.java b/tests/src/com/android/tradefed/config/ConfigurationTest.java
index 5649fb0..39a57c9 100644
--- a/tests/src/com/android/tradefed/config/ConfigurationTest.java
+++ b/tests/src/com/android/tradefed/config/ConfigurationTest.java
@@ -684,6 +684,13 @@
* shards are kicked-off in new invocations.
*/
public void testValidateOptions_localSharding_skipDownload() throws ConfigurationException {
+ mConfig =
+ new Configuration(CONFIG_NAME, CONFIG_DESCRIPTION) {
+ @Override
+ protected boolean isRemoteEnvironment() {
+ return false;
+ }
+ };
CommandOptions options = new CommandOptions();
options.setShardCount(5);
options.setShardIndex(null);
diff --git a/tests/src/com/android/tradefed/device/NativeDeviceTest.java b/tests/src/com/android/tradefed/device/NativeDeviceTest.java
index 9afd711..3c6bf79 100644
--- a/tests/src/com/android/tradefed/device/NativeDeviceTest.java
+++ b/tests/src/com/android/tradefed/device/NativeDeviceTest.java
@@ -50,6 +50,7 @@
import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.IRunUtil;
+import com.android.tradefed.util.ProcessInfo;
import com.android.tradefed.util.StreamUtil;
import org.easymock.EasyMock;
@@ -2211,7 +2212,7 @@
/** Test get ProcessInfo by process name */
@Test
- public void testGetProcessWithStartTimeByName() throws Exception {
+ public void testGetProcessByName() throws Exception {
final String fakePid = "914";
final String fakeCreationTime = "1559091922";
TestableAndroidNativeDevice spy = Mockito.spy(mTestDevice);
@@ -2228,7 +2229,7 @@
/** Test get ProcessInfo by process name return null for invalid process */
@Test
- public void testGetProcessWithStartTimeByNameInvalidProcess() throws Exception {
+ public void testGetProcessByNameInvalidProcess() throws Exception {
TestableAndroidNativeDevice spy = Mockito.spy(mTestDevice);
doReturn("").when(spy).executeShellCommand("pidof system_server");
EasyMock.replay(mMockIDevice);
@@ -2236,6 +2237,19 @@
EasyMock.verify(mMockIDevice);
}
+ /** Test get ProcessInfo by process name return null for invalid process */
+ @Test
+ public void testGetProcessByNameInvalidStartTime() throws Exception {
+ TestableAndroidNativeDevice spy = Mockito.spy(mTestDevice);
+ doReturn("120").when(spy).executeShellCommand("pidof system_server");
+ doReturn("stat: '/proc/120': No such file or directory")
+ .when(spy)
+ .executeShellCommand("stat -c%Z /proc/120");
+ EasyMock.replay(mMockIDevice);
+ assertNull(spy.getProcessByName("system_server"));
+ EasyMock.verify(mMockIDevice);
+ }
+
/** Test get boot history */
@Test
public void testGetBootHistory() throws Exception {
@@ -2246,7 +2260,7 @@
+ " reboot,,1556237796\n"
+ " reboot,,1556237725\n")
.when(spy)
- .getProperty(DeviceProperties.BOOT_REASON_HISTORY);
+ .executeShellCommand("getprop " + DeviceProperties.BOOT_REASON_HISTORY);
Map<Long, String> history = new LinkedHashMap<Long, String>();
history.put(1556587278L, "kernel_panic");
history.put(1556238008L, "reboot");
@@ -2261,7 +2275,9 @@
@Test
public void testGetBootHistoryEmpty() throws Exception {
TestableAndroidNativeDevice spy = Mockito.spy(mTestDevice);
- doReturn("").when(spy).getProperty(DeviceProperties.BOOT_REASON_HISTORY);
+ doReturn("")
+ .when(spy)
+ .executeShellCommand("getprop " + DeviceProperties.BOOT_REASON_HISTORY);
EasyMock.replay(mMockIDevice);
assertTrue(spy.getBootHistory().isEmpty());
EasyMock.verify(mMockIDevice);
@@ -2271,7 +2287,9 @@
@Test
public void testGetBootHistoryInvalid() throws Exception {
TestableAndroidNativeDevice spy = Mockito.spy(mTestDevice);
- doReturn("invalid output").when(spy).getProperty(DeviceProperties.BOOT_REASON_HISTORY);
+ doReturn("invalid output")
+ .when(spy)
+ .executeShellCommand("getprop " + DeviceProperties.BOOT_REASON_HISTORY);
EasyMock.replay(mMockIDevice);
assertTrue(spy.getBootHistory().isEmpty());
EasyMock.verify(mMockIDevice);
@@ -2287,11 +2305,221 @@
+ " reboot,,1556237796\n"
+ " reboot,,1556237725\n")
.when(spy)
- .getProperty(DeviceProperties.BOOT_REASON_HISTORY);
+ .executeShellCommand("getprop " + DeviceProperties.BOOT_REASON_HISTORY);
Map<Long, String> history = new LinkedHashMap<Long, String>();
history.put(1556587278L, "kernel_panic");
EasyMock.replay(mMockIDevice);
- assertEquals(history, spy.getBootHistorySince(1556238008L));
+ assertEquals(history, spy.getBootHistorySince(1556238008L, TimeUnit.SECONDS));
+ EasyMock.verify(mMockIDevice);
+ }
+
+ /** Test get boot history since */
+ @Test
+ public void testGetBootHistorySinceInMillisecond() throws Exception {
+ TestableAndroidNativeDevice spy = Mockito.spy(mTestDevice);
+ doReturn(
+ "kernel_panic,1556587278\n"
+ + " reboot,,1556238008\n"
+ + " reboot,,1556237796\n"
+ + " reboot,,1556237725\n")
+ .when(spy)
+ .executeShellCommand("getprop " + DeviceProperties.BOOT_REASON_HISTORY);
+ Map<Long, String> history = new LinkedHashMap<Long, String>();
+ history.put(1556587278L, "kernel_panic");
+ EasyMock.replay(mMockIDevice);
+ assertEquals(history, spy.getBootHistorySince(1556238008000L, TimeUnit.MILLISECONDS));
+ EasyMock.verify(mMockIDevice);
+ }
+
+ /** Test deviceSoftRestartedSince */
+ @Test
+ public void testDeviceSoftRestartedSince() throws Exception {
+ TestableAndroidNativeDevice spy = Mockito.spy(mTestDevice);
+ doReturn("914").when(spy).executeShellCommand("pidof system_server");
+ doReturn("1559091922").when(spy).executeShellCommand("stat -c%Z /proc/914");
+ doReturn("system").when(spy).executeShellCommand("stat -c%U /proc/914");
+ doReturn(
+ "kernel_panic,1556587278\n"
+ + " reboot,,1556238008\n"
+ + " reboot,,1556237796\n"
+ + " reboot,,1556237725\n")
+ .when(spy)
+ .executeShellCommand("getprop " + DeviceProperties.BOOT_REASON_HISTORY);
+ EasyMock.replay(mMockIDevice);
+ assertFalse(spy.deviceSoftRestartedSince(1559091923L, TimeUnit.SECONDS));
+ assertFalse(spy.deviceSoftRestartedSince(1559091923000L, TimeUnit.MILLISECONDS));
+ assertFalse(spy.deviceSoftRestartedSince(1559091922L, TimeUnit.SECONDS));
+ assertFalse(spy.deviceSoftRestartedSince(1559091922000L, TimeUnit.MILLISECONDS));
+ assertTrue(spy.deviceSoftRestartedSince(1559091921L, TimeUnit.SECONDS));
+ assertTrue(spy.deviceSoftRestartedSince(1559091921000L, TimeUnit.MILLISECONDS));
+ EasyMock.verify(mMockIDevice);
+ }
+
+ /** Test deviceSoftRestartedSince return true with system_server stopped */
+ @Test
+ public void testDeviceSoftRestartedSinceWithSystemServerStopped() throws Exception {
+ TestableAndroidNativeDevice spy = Mockito.spy(mTestDevice);
+ doReturn("").when(spy).executeShellCommand("pidof system_server");
+ assertTrue(spy.deviceSoftRestartedSince(1559091922L, TimeUnit.SECONDS));
+ }
+
+ /** Test deviceSoftRestartedSince throw RuntimeException with abnormal reboot */
+ @Test
+ public void testDeviceSoftRestartedSinceWithAbnormalReboot() throws Exception {
+ TestableAndroidNativeDevice spy = Mockito.spy(mTestDevice);
+ doReturn("914").when(spy).executeShellCommand("pidof system_server");
+ doReturn("1559091999").when(spy).executeShellCommand("stat -c%Z /proc/914");
+ doReturn("system").when(spy).executeShellCommand("stat -c%U /proc/914");
+ doReturn(
+ "kernel_panic,1559091933\n"
+ + " reboot,,1556238008\n"
+ + " reboot,,1556237796\n"
+ + " reboot,,1556237725\n")
+ .when(spy)
+ .executeShellCommand("getprop " + DeviceProperties.BOOT_REASON_HISTORY);
+ EasyMock.replay(mMockIDevice);
+ try {
+ spy.deviceSoftRestartedSince(1559091922L, TimeUnit.SECONDS);
+ } catch (RuntimeException e) {
+ //expected
+ return;
+ }
+ fail("RuntimeException is expected");
+ }
+
+ /** Test deviceSoftRestartedSince return false with normal reboot */
+ @Test
+ public void testDeviceSoftRestartedSinceNotAfterNormalReboot() throws Exception {
+ TestableAndroidNativeDevice spy = Mockito.spy(mTestDevice);
+ doReturn("914").when(spy).executeShellCommand("pidof system_server");
+ doReturn("1559091939").when(spy).executeShellCommand("stat -c%Z /proc/914");
+ doReturn("system").when(spy).executeShellCommand("stat -c%U /proc/914");
+ doReturn(
+ "reboot,1559091933\n"
+ + " reboot,,1556238008\n"
+ + " reboot,,1556237796\n"
+ + " reboot,,1556237725\n")
+ .when(spy)
+ .executeShellCommand("getprop " + DeviceProperties.BOOT_REASON_HISTORY);
+ EasyMock.replay(mMockIDevice);
+ assertFalse(spy.deviceSoftRestartedSince(1559091921L, TimeUnit.SECONDS));
+ EasyMock.verify(mMockIDevice);
+ }
+
+ /** Test deviceSoftRestartedSince return false with normal reboot */
+ @Test
+ public void testDeviceSoftRestartedSinceAfterNormalReboot() throws Exception {
+ TestableAndroidNativeDevice spy = Mockito.spy(mTestDevice);
+ doReturn("914").when(spy).executeShellCommand("pidof system_server");
+ doReturn("1559091992").when(spy).executeShellCommand("stat -c%Z /proc/914");
+ doReturn("system").when(spy).executeShellCommand("stat -c%U /proc/914");
+ doReturn(
+ "reboot,1559091933\n"
+ + " reboot,,1556238008\n"
+ + " reboot,,1556237796\n"
+ + " reboot,,1556237725\n")
+ .when(spy)
+ .executeShellCommand("getprop " + DeviceProperties.BOOT_REASON_HISTORY);
+ EasyMock.replay(mMockIDevice);
+ assertTrue(spy.deviceSoftRestartedSince(1559091921L, TimeUnit.SECONDS));
+ EasyMock.verify(mMockIDevice);
+ }
+
+ /** Test deviceSoftRestarted given the previous system_server {@link ProcessInfo} */
+ @Test
+ public void testDeviceSoftRestarted() throws Exception {
+ TestableAndroidNativeDevice spy = Mockito.spy(mTestDevice);
+ ProcessInfo prev1 = new ProcessInfo("system", 123, "system_server", 1559000000L);
+ ProcessInfo prev2 = new ProcessInfo("system", 914, "system_server", 1559091922L);
+ doReturn("914").when(spy).executeShellCommand("pidof system_server");
+ doReturn("1559091922").when(spy).executeShellCommand("stat -c%Z /proc/914");
+ doReturn("system").when(spy).executeShellCommand("stat -c%U /proc/914");
+ doReturn(
+ "kernel_panic,1556587278\n"
+ + " reboot,,1556238008\n"
+ + " reboot,,1556237796\n"
+ + " reboot,,1556237725\n")
+ .when(spy)
+ .executeShellCommand("getprop " + DeviceProperties.BOOT_REASON_HISTORY);
+ EasyMock.replay(mMockIDevice);
+ assertTrue(spy.deviceSoftRestarted(prev1));
+ assertFalse(spy.deviceSoftRestarted(prev2));
+ EasyMock.verify(mMockIDevice);
+ }
+
+ /** Test deviceSoftRestarted return true with system_server stopped */
+ @Test
+ public void testDeviceSoftRestartedWithSystemServerStopped() throws Exception {
+ TestableAndroidNativeDevice spy = Mockito.spy(mTestDevice);
+ doReturn("").when(spy).executeShellCommand("pidof system_server");
+ assertTrue(
+ spy.deviceSoftRestarted(
+ new ProcessInfo("system", 123, "system_server", 1559000000L)));
+ }
+
+ /** Test deviceSoftRestarted throw RuntimeException with abnormal reboot */
+ @Test
+ public void testDeviceSoftRestartedWithAbnormalReboot() throws Exception {
+ TestableAndroidNativeDevice spy = Mockito.spy(mTestDevice);
+ doReturn("914").when(spy).executeShellCommand("pidof system_server");
+ doReturn("1559091999").when(spy).executeShellCommand("stat -c%Z /proc/914");
+ doReturn("system").when(spy).executeShellCommand("stat -c%U /proc/914");
+ doReturn(
+ "kernel_panic,1559091933\n"
+ + " reboot,,1556238008\n"
+ + " reboot,,1556237796\n"
+ + " reboot,,1556237725\n")
+ .when(spy)
+ .executeShellCommand("getprop " + DeviceProperties.BOOT_REASON_HISTORY);
+ EasyMock.replay(mMockIDevice);
+ try {
+ spy.deviceSoftRestarted(new ProcessInfo("system", 123, "system_server", 1559000000L));
+ } catch (RuntimeException e) {
+ //expected
+ return;
+ }
+ fail("Abnormal reboot is detected, RuntimeException is expected");
+ }
+
+ /** Test ddeviceSoftRestarted return false with normal reboot */
+ @Test
+ public void testDeviceSoftRestartedNotAfterNormalReboot() throws Exception {
+ TestableAndroidNativeDevice spy = Mockito.spy(mTestDevice);
+ doReturn("914").doReturn("914").when(spy).executeShellCommand("pidof system_server");
+ doReturn("1559091935").when(spy).executeShellCommand("stat -c%Z /proc/914");
+ doReturn("system").when(spy).executeShellCommand("stat -c%U /proc/914");
+ doReturn(
+ "reboot,,1559091933\n"
+ + " reboot,,1556238008\n"
+ + " reboot,,1556237796\n"
+ + " reboot,,1556237725\n")
+ .when(spy)
+ .executeShellCommand("getprop " + DeviceProperties.BOOT_REASON_HISTORY);
+ EasyMock.replay(mMockIDevice);
+ assertFalse(
+ spy.deviceSoftRestarted(
+ new ProcessInfo("system", 123, "system_server", 1559000000L)));
+ EasyMock.verify(mMockIDevice);
+ }
+
+ /** Test deviceSoftRestarted return true if system_server restarted after normal reboot */
+ @Test
+ public void testDeviceSoftRestartedAfterNormalReboot() throws Exception {
+ TestableAndroidNativeDevice spy = Mockito.spy(mTestDevice);
+ doReturn("914").doReturn("914").when(spy).executeShellCommand("pidof system_server");
+ doReturn("1559091995").when(spy).executeShellCommand("stat -c%Z /proc/914");
+ doReturn("system").when(spy).executeShellCommand("stat -c%U /proc/914");
+ doReturn(
+ "reboot,,1559091933\n"
+ + " reboot,,1556238008\n"
+ + " reboot,,1556237796\n"
+ + " reboot,,1556237725\n")
+ .when(spy)
+ .executeShellCommand("getprop " + DeviceProperties.BOOT_REASON_HISTORY);
+ EasyMock.replay(mMockIDevice);
+ assertTrue(
+ spy.deviceSoftRestarted(
+ new ProcessInfo("system", 123, "system_server", 1559000000L)));
EasyMock.verify(mMockIDevice);
}
diff --git a/tests/src/com/android/tradefed/device/TestDeviceFuncTest.java b/tests/src/com/android/tradefed/device/TestDeviceFuncTest.java
index 1f0de39..0cf8ae7 100644
--- a/tests/src/com/android/tradefed/device/TestDeviceFuncTest.java
+++ b/tests/src/com/android/tradefed/device/TestDeviceFuncTest.java
@@ -36,6 +36,7 @@
import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.KeyguardControllerState;
+import com.android.tradefed.util.ProcessInfo;
import com.android.tradefed.util.RunUtil;
import com.android.tradefed.util.StreamUtil;
@@ -52,6 +53,7 @@
import java.io.IOException;
import java.net.URLConnection;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
import javax.imageio.ImageIO;
@@ -607,6 +609,60 @@
}
}
+ private ProcessInfo waitForSystemServerProcess() throws DeviceNotAvailableException {
+ ProcessInfo systemServer = null;
+ for (int i = 0; i < 5; i++) {
+ systemServer = mTestDevice.getProcessByName("system_server");
+ if (systemServer != null) {
+ return systemServer;
+ }
+ RunUtil.getDefault().sleep(1000);
+ }
+ Log.i(LOG_TAG, "The system_server process fails to come up");
+ return null;
+ }
+
+ /** Test device soft-restart detection API. */
+ @Test
+ public void testDeviceSoftRestart() throws DeviceNotAvailableException {
+ Log.i(LOG_TAG, "testDeviceSoftRestartSince");
+
+ // Get system_server process info
+ ProcessInfo prev = mTestDevice.getProcessByName("system_server");
+ long deviceTimeMs = mTestDevice.getDeviceDate();
+ if (prev == null) {
+ Log.i(LOG_TAG, "System_server process does not exist. Abort testDeviceSoftRestart.");
+ return;
+ }
+ assertFalse(mTestDevice.deviceSoftRestartedSince(prev.getStartTime(), TimeUnit.SECONDS));
+ assertFalse(mTestDevice.deviceSoftRestarted(prev));
+ if (!mTestDevice.isAdbRoot()) {
+ mTestDevice.enableAdbRoot();
+ }
+ mTestDevice.executeShellCommand(String.format("kill %s", prev.getPid()));
+ RunUtil.getDefault().sleep(1000);
+ assertTrue(mTestDevice.deviceSoftRestartedSince(deviceTimeMs, TimeUnit.MILLISECONDS));
+ assertTrue(mTestDevice.deviceSoftRestarted(prev));
+ prev = waitForSystemServerProcess();
+ deviceTimeMs = mTestDevice.getDeviceDate();
+ mTestDevice.reboot();
+ if (!mTestDevice.isAdbRoot()) {
+ mTestDevice.enableAdbRoot();
+ }
+ assertFalse(mTestDevice.deviceSoftRestartedSince(deviceTimeMs, TimeUnit.MILLISECONDS));
+ assertFalse(mTestDevice.deviceSoftRestarted(prev));
+ // Restart system_server 10 seconds after reboot
+ RunUtil.getDefault().sleep(10000);
+ mTestDevice.executeShellCommand(
+ String.format("kill %s", mTestDevice.getProcessByName("system_server").getPid()));
+ RunUtil.getDefault().sleep(1000);
+ assertTrue(mTestDevice.deviceSoftRestartedSince(deviceTimeMs, TimeUnit.MILLISECONDS));
+ assertTrue(mTestDevice.deviceSoftRestarted(prev));
+ waitForSystemServerProcess();
+ assertTrue(mTestDevice.deviceSoftRestartedSince(deviceTimeMs, TimeUnit.MILLISECONDS));
+ assertTrue(mTestDevice.deviceSoftRestarted(prev));
+ }
+
/**
* Verify that {@link TestDevice#clearErrorDialogs()} can successfully clear an error dialog
* from screen.
diff --git a/tests/src/com/android/tradefed/device/TestDeviceTest.java b/tests/src/com/android/tradefed/device/TestDeviceTest.java
index c3c1b00..d870399 100644
--- a/tests/src/com/android/tradefed/device/TestDeviceTest.java
+++ b/tests/src/com/android/tradefed/device/TestDeviceTest.java
@@ -1308,8 +1308,8 @@
*/
public void testRuntimePermissionSupportedLmpRelease() throws Exception {
injectSystemProperty("ro.build.version.sdk", "21");
- injectSystemProperty(TestDevice.BUILD_CODENAME_PROP, "REL");
- injectSystemProperty(TestDevice.BUILD_ID_PROP, "1642709");
+ injectSystemProperty(DeviceProperties.BUILD_CODENAME, "REL");
+ injectSystemProperty(DeviceProperties.BUILD_ID, "1642709");
replayMocks();
assertFalse(mTestDevice.isRuntimePermissionSupported());
}
@@ -1321,8 +1321,8 @@
*/
public void testRuntimePermissionSupportedLmpMr1Dev() throws Exception {
injectSystemProperty("ro.build.version.sdk", "22");
- injectSystemProperty(TestDevice.BUILD_CODENAME_PROP, "REL");
- injectSystemProperty(TestDevice.BUILD_ID_PROP, "1844090");
+ injectSystemProperty(DeviceProperties.BUILD_CODENAME, "REL");
+ injectSystemProperty(DeviceProperties.BUILD_ID, "1844090");
replayMocks();
assertFalse(mTestDevice.isRuntimePermissionSupported());
}
@@ -1334,8 +1334,8 @@
*/
public void testRuntimePermissionSupportedNonMncLocal() throws Exception {
injectSystemProperty("ro.build.version.sdk", "21");
- injectSystemProperty(TestDevice.BUILD_CODENAME_PROP, "LMP");
- injectSystemProperty(TestDevice.BUILD_ID_PROP, "eng.foo.20150414.190304");
+ injectSystemProperty(DeviceProperties.BUILD_CODENAME, "LMP");
+ injectSystemProperty(DeviceProperties.BUILD_ID, "eng.foo.20150414.190304");
replayMocks();
assertFalse(mTestDevice.isRuntimePermissionSupported());
}
@@ -2550,7 +2550,7 @@
*/
public void testMaxNumberOfRunningUsersSupported() throws Exception {
injectSystemProperty("ro.build.version.sdk", "28");
- injectSystemProperty(TestDevice.BUILD_CODENAME_PROP, "REL");
+ injectSystemProperty(DeviceProperties.BUILD_CODENAME, "REL");
final String getMaxRunningUsersCommand = "pm get-max-running-users";
injectShellResponse(getMaxRunningUsersCommand, "Maximum supported running users: 4");
replayMocks();
@@ -2560,7 +2560,7 @@
/** Test that invalid output is handled by {@link TestDevice#getMaxNumberOfUsersSupported()}. */
public void testMaxNumberOfRunningUsersSupported_invalid() throws Exception {
injectSystemProperty("ro.build.version.sdk", "28");
- injectSystemProperty(TestDevice.BUILD_CODENAME_PROP, "REL");
+ injectSystemProperty(DeviceProperties.BUILD_CODENAME, "REL");
final String getMaxRunningUsersCommand = "pm get-max-running-users";
injectShellResponse(getMaxRunningUsersCommand, "not the output we expect");
replayMocks();
@@ -2847,7 +2847,7 @@
* @throws Exception
*/
public void testGetBuildSigningKeys_test_keys() throws Exception {
- injectSystemProperty(TestDevice.BUILD_TAGS, "test-keys");
+ injectSystemProperty(DeviceProperties.BUILD_TAGS, "test-keys");
replayMocks();
assertEquals("test-keys", mTestDevice.getBuildSigningKeys());
}
@@ -2858,7 +2858,7 @@
* @throws Exception
*/
public void testGetBuildSigningKeys_test_keys_commas() throws Exception {
- injectSystemProperty(TestDevice.BUILD_TAGS, "test-keys,foo,bar,yadda");
+ injectSystemProperty(DeviceProperties.BUILD_TAGS, "test-keys,foo,bar,yadda");
replayMocks();
assertEquals("test-keys", mTestDevice.getBuildSigningKeys());
}
@@ -2868,7 +2868,7 @@
* @throws Exception
*/
public void testGetBuildSigningKeys_not_matched() throws Exception {
- injectSystemProperty(TestDevice.BUILD_TAGS, "huh,foo,bar,yadda");
+ injectSystemProperty(DeviceProperties.BUILD_TAGS, "huh,foo,bar,yadda");
replayMocks();
assertNull(mTestDevice.getBuildSigningKeys());
}
diff --git a/tests/src/com/android/tradefed/device/cloud/GceManagerTest.java b/tests/src/com/android/tradefed/device/cloud/GceManagerTest.java
index 269e4ce..a38ced6 100644
--- a/tests/src/com/android/tradefed/device/cloud/GceManagerTest.java
+++ b/tests/src/com/android/tradefed/device/cloud/GceManagerTest.java
@@ -18,6 +18,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.doReturn;
@@ -36,6 +37,7 @@
import com.google.common.net.HostAndPort;
+import org.easymock.Capture;
import org.easymock.EasyMock;
import org.junit.After;
import org.junit.Assert;
@@ -570,7 +572,7 @@
EasyMock.eq("--instance_names"),
EasyMock.eq("instance1"),
EasyMock.eq("--config_file"),
- EasyMock.eq(mGceManager.getAvdConfigFile().getAbsolutePath()),
+ EasyMock.contains(mGceManager.getAvdConfigFile().getAbsolutePath()),
EasyMock.eq("--report_file"),
EasyMock.anyObject()))
.andReturn(cmd);
@@ -580,6 +582,34 @@
EasyMock.verify(mMockRunUtil);
}
+ @Test
+ public void testShutdownGce_noWait() throws Exception {
+ OptionSetter setter = new OptionSetter(mOptions);
+ setter.setOptionValue("wait-gce-teardown", "false");
+ mGceManager =
+ new GceManager(
+ mMockDeviceDesc, mOptions, mMockBuildInfo, null, "instance1", "host1") {
+ @Override
+ IRunUtil getRunUtil() {
+ return mMockRunUtil;
+ }
+ };
+ mGceManager.startGce();
+ CommandResult cmd = new CommandResult();
+ cmd.setStatus(CommandStatus.SUCCESS);
+ cmd.setStdout("output");
+ Capture<List<String>> capture = new Capture<>();
+ EasyMock.expect(mMockRunUtil.runCmdInBackground(EasyMock.<List<String>>capture(capture)))
+ .andReturn(Mockito.mock(Process.class));
+
+ EasyMock.replay(mMockRunUtil);
+ mGceManager.shutdownGce();
+ EasyMock.verify(mMockRunUtil);
+
+ List<String> args = capture.getValue();
+ assertTrue(args.get(5).contains(mAvdBinary.getName()));
+ }
+
/**
* Test for {@link GceManager#shutdownGce() }.
*
@@ -609,7 +639,7 @@
EasyMock.eq("--instance_names"),
EasyMock.eq("instance1"),
EasyMock.eq("--config_file"),
- EasyMock.eq(mGceManager.getAvdConfigFile().getAbsolutePath()),
+ EasyMock.contains(mGceManager.getAvdConfigFile().getAbsolutePath()),
EasyMock.eq("--service_account_json_private_key_path"),
EasyMock.eq("/path/to/key.json"),
EasyMock.eq("--report_file"),
diff --git a/tests/src/com/android/tradefed/device/cloud/ManagedRemoteDeviceTest.java b/tests/src/com/android/tradefed/device/cloud/ManagedRemoteDeviceTest.java
index 7bf87e6..38d7254 100644
--- a/tests/src/com/android/tradefed/device/cloud/ManagedRemoteDeviceTest.java
+++ b/tests/src/com/android/tradefed/device/cloud/ManagedRemoteDeviceTest.java
@@ -15,14 +15,16 @@
*/
package com.android.tradefed.device.cloud;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertNotEquals;
import com.android.ddmlib.IDevice;
import com.android.tradefed.config.GlobalConfiguration;
import com.android.tradefed.device.IDeviceMonitor;
import com.android.tradefed.device.IDeviceStateMonitor;
import com.android.tradefed.device.TestDeviceOptions;
+import com.android.tradefed.log.ITestLogger;
import org.junit.Before;
import org.junit.BeforeClass;
@@ -38,6 +40,7 @@
private IDevice mIDevice;
private IDeviceStateMonitor mStateMonitor;
private IDeviceMonitor mDeviceMonitor;
+ private ITestLogger mMockLogger;
@BeforeClass
public static void setUpClass() throws Exception {
@@ -53,7 +56,9 @@
mIDevice = Mockito.mock(IDevice.class);
mStateMonitor = Mockito.mock(IDeviceStateMonitor.class);
mDeviceMonitor = Mockito.mock(IDeviceMonitor.class);
+ mMockLogger = Mockito.mock(ITestLogger.class);
mDevice = new ManagedRemoteDevice(mIDevice, mStateMonitor, mDeviceMonitor);
+ mDevice.setTestLogger(mMockLogger);
}
@Test
@@ -63,6 +68,11 @@
TestDeviceOptions get = mDevice.getOptions();
assertFalse(get.equals(originalOptions));
TestDeviceOptions get2 = mDevice.getOptions();
- assertTrue(get2.equals(get));
+ // Same during the same session
+ assertEquals(get2, get);
+
+ mDevice.postInvocationTearDown(null);
+ TestDeviceOptions get3 = mDevice.getOptions();
+ assertNotEquals(get2, get3);
}
}
diff --git a/tests/src/com/android/tradefed/device/metric/DebugHostLogOnFailureCollectorTest.java b/tests/src/com/android/tradefed/device/metric/DebugHostLogOnFailureCollectorTest.java
index 7933634..a19c8c9 100644
--- a/tests/src/com/android/tradefed/device/metric/DebugHostLogOnFailureCollectorTest.java
+++ b/tests/src/com/android/tradefed/device/metric/DebugHostLogOnFailureCollectorTest.java
@@ -17,6 +17,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.never;
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.invoker.InvocationContext;
@@ -126,4 +127,38 @@
Mockito.<HashMap<String, Metric>>any());
Mockito.verify(mMockListener).testRunEnded(0L, new HashMap<String, Metric>());
}
+
+ /** Test when we fail to obtain the host_log from the start of the collector. */
+ @Test
+ public void testCollect_null() throws Exception {
+ TestDescription test = new TestDescription("class", "test");
+ // Buffer at testRunStarted, then the one we want to log
+ Mockito.when(mMockLogger.getLog()).thenReturn(null);
+ mTestListener = mCollector.init(mContext, mMockListener);
+ mTestListener.testRunStarted("runName", 1);
+ mTestListener.testStarted(test);
+ mTestListener.testFailed(test, "I failed");
+ mTestListener.testEnded(test, new HashMap<String, Metric>());
+ mTestListener.testRunEnded(0L, new HashMap<String, Metric>());
+
+ // Ensure the callback went through
+ assertTrue(mCollector.mOnTestStartCalled);
+ assertTrue(mCollector.mOnTestFailCalled);
+
+ Mockito.verify(mMockListener).testRunStarted("runName", 1);
+ Mockito.verify(mMockListener).testStarted(Mockito.eq(test), Mockito.anyLong());
+ Mockito.verify(mMockListener).testFailed(Mockito.eq(test), Mockito.any());
+ // No file is logged
+ Mockito.verify(mMockListener, never())
+ .testLog(
+ Mockito.eq("class#test-debug-hostlog-on-failure"),
+ Mockito.eq(LogDataType.TEXT),
+ Mockito.any());
+ Mockito.verify(mMockListener)
+ .testEnded(
+ Mockito.eq(test),
+ Mockito.anyLong(),
+ Mockito.<HashMap<String, Metric>>any());
+ Mockito.verify(mMockListener).testRunEnded(0L, new HashMap<String, Metric>());
+ }
}
diff --git a/tests/src/com/android/tradefed/device/metric/LogcatOnFailureCollectorTest.java b/tests/src/com/android/tradefed/device/metric/LogcatOnFailureCollectorTest.java
index d213104..61945dc 100644
--- a/tests/src/com/android/tradefed/device/metric/LogcatOnFailureCollectorTest.java
+++ b/tests/src/com/android/tradefed/device/metric/LogcatOnFailureCollectorTest.java
@@ -82,7 +82,7 @@
}
@Before
- public void setUp() {
+ public void setUp() throws Exception {
mMockDevice = EasyMock.createMock(ITestDevice.class);
mNullMockDevice = EasyMock.createMock(ITestDevice.class);
mMockListener = EasyMock.createMock(ITestInvocationListener.class);
@@ -101,6 +101,7 @@
@Test
public void testCollect() throws Exception {
+ EasyMock.expect(mMockDevice.getApiLevel()).andReturn(20);
mMockReceiver.start();
mMockReceiver.clear();
mMockReceiver.stop();
@@ -137,6 +138,41 @@
assertTrue(mCollector.mOnTestFailCalled);
}
+ /**
+ * If the API level support of the device is lower than a threshold we fall back to a different
+ * collection for the logcat.
+ */
+ @Test
+ public void testCollect_legacy() throws Exception {
+ EasyMock.expect(mMockDevice.getApiLevel()).andReturn(18);
+ mMockListener.testRunStarted("runName", 1);
+ TestDescription test = new TestDescription("class", "test");
+ mMockListener.testStarted(EasyMock.eq(test), EasyMock.anyLong());
+ mMockListener.testFailed(EasyMock.eq(test), EasyMock.anyObject());
+ mMockListener.testEnded(
+ EasyMock.eq(test),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mMockListener.testRunEnded(0L, new HashMap<String, Metric>());
+ mMockDevice.executeShellCommand(EasyMock.eq("logcat -t 5000"), EasyMock.anyObject());
+ mMockListener.testLog(
+ EasyMock.eq("class#test-serial-logcat-on-failure"),
+ EasyMock.eq(LogDataType.LOGCAT),
+ EasyMock.anyObject());
+
+ EasyMock.replay(mMockListener, mMockDevice, mMockReceiver, mNullMockDevice);
+ mTestListener = mCollector.init(mContext, mMockListener);
+ mTestListener.testRunStarted("runName", 1);
+ mTestListener.testStarted(test);
+ mTestListener.testFailed(test, "I failed");
+ mTestListener.testEnded(test, new HashMap<String, Metric>());
+ mTestListener.testRunEnded(0L, new HashMap<String, Metric>());
+ EasyMock.verify(mMockListener, mMockDevice, mMockReceiver, mNullMockDevice);
+ // Ensure the callback went through
+ assertTrue(mCollector.mOnTestStartCalled);
+ assertTrue(mCollector.mOnTestFailCalled);
+ }
+
@Test
public void testCollect_noRuns() throws Exception {
// If there was no runs, nothing should be done.
@@ -149,6 +185,7 @@
@Test
public void testCollect_multiRun() throws Exception {
+ EasyMock.expect(mMockDevice.getApiLevel()).andStubReturn(20);
mMockReceiver.start();
EasyMock.expectLastCall().times(2);
mMockReceiver.clear();
diff --git a/tests/src/com/android/tradefed/device/metric/RuntimeRestartCollectorTest.java b/tests/src/com/android/tradefed/device/metric/RuntimeRestartCollectorTest.java
index c626eca..0d48c7c 100644
--- a/tests/src/com/android/tradefed/device/metric/RuntimeRestartCollectorTest.java
+++ b/tests/src/com/android/tradefed/device/metric/RuntimeRestartCollectorTest.java
@@ -438,11 +438,12 @@
}
/**
- * Test that the collector reports counts based on the {@link AppCrashOccurred} results when it
- * disagrees with info from statsd metadata.
+ * Test that the collector reports counts based on the {@link StatsdStatsReport} results when it
+ * disagrees with info from the {@link AppCrashOccurred} atom.
*/
@Test
- public void testAddingMetrics_withRuntimeRestart_useAtomResultsForCount() throws Exception {
+ public void testAddingMetrics_withRuntimeRestart_useStatsdMetadataResultsForCount()
+ throws Exception {
ITestDevice testDevice = mockTestDevice(DEVICE_SERIAL_1);
doReturn(Arrays.asList(testDevice)).when(mContext).getDevices();
// Two data points from the AppCrashOccurred data.
@@ -471,7 +472,7 @@
// Count should be two as in the stubbed EventMetricDataResults, even though statsd metadata
// only reported one timestamp.
int count = getCount(runMetrics);
- Assert.assertEquals(2, count);
+ Assert.assertEquals(1, count);
}
/**
diff --git a/tests/src/com/android/tradefed/invoker/InvocationExecutionTest.java b/tests/src/com/android/tradefed/invoker/InvocationExecutionTest.java
index 8b8c6ac..1dfe325 100644
--- a/tests/src/com/android/tradefed/invoker/InvocationExecutionTest.java
+++ b/tests/src/com/android/tradefed/invoker/InvocationExecutionTest.java
@@ -75,6 +75,7 @@
private IConfiguration mConfig;
private ITestInvocationListener mMockListener;
private ITestDevice mMockDevice;
+ private ITestLogger mMockLogger;
@Before
public void setUp() {
@@ -82,6 +83,7 @@
mContext = new InvocationContext();
mConfig = new Configuration("test", "test");
mMockListener = mock(ITestInvocationListener.class);
+ mMockLogger = mock(ITestLogger.class);
mMockDevice = EasyMock.createMock(ITestDevice.class);
// Reset the counters
TestBaseMetricCollector.sTotalInit = 0;
@@ -253,7 +255,7 @@
mContext.addAllocatedDevice("default", mock(ITestDevice.class));
mExec.doSetup(mContext, mConfig, mMockListener);
- mExec.doTeardown(mContext, mConfig, null, null);
+ mExec.doTeardown(mContext, mConfig, mMockLogger, null);
// Pre multi preparers are always called before.
InOrder inOrder = Mockito.inOrder(stub1, stub2, stub3, stub4, cleaner);
@@ -300,7 +302,7 @@
mContext.addAllocatedDevice("default", mock(ITestDevice.class));
// Ensure that the original error is the one passed around.
Throwable exception = new Throwable("Original error");
- mExec.doTeardown(mContext, mConfig, null, exception);
+ mExec.doTeardown(mContext, mConfig, mMockLogger, exception);
InOrder inOrder = Mockito.inOrder(stub1, stub2, stub3, stub4, cleaner);
diff --git a/tests/src/com/android/tradefed/invoker/RemoteInvocationExecutionTest.java b/tests/src/com/android/tradefed/invoker/RemoteInvocationExecutionTest.java
index bb688b0..25de280 100644
--- a/tests/src/com/android/tradefed/invoker/RemoteInvocationExecutionTest.java
+++ b/tests/src/com/android/tradefed/invoker/RemoteInvocationExecutionTest.java
@@ -104,6 +104,9 @@
((DeviceSelectionOptions)
reparse.getDeviceConfig().get(0).getDeviceRequirements())
.getDeviceTypeRequested());
+ assertEquals(
+ "",
+ reparse.getDeviceConfig().get(0).getDeviceOptions().getRemoteTf().getPath());
} finally {
FileUtil.deleteFile(res);
}
diff --git a/tests/src/com/android/tradefed/invoker/SandboxedInvocationExecutionTest.java b/tests/src/com/android/tradefed/invoker/SandboxedInvocationExecutionTest.java
index 8623025..3f5536e 100644
--- a/tests/src/com/android/tradefed/invoker/SandboxedInvocationExecutionTest.java
+++ b/tests/src/com/android/tradefed/invoker/SandboxedInvocationExecutionTest.java
@@ -372,8 +372,6 @@
// No tests to run but we still call start/end
Mockito.verify(mMockListener).invocationStarted(mContext);
Mockito.verify(mMockListener).invocationFailed(exception);
- Mockito.verify(mMockListener)
- .testLog(eq(TestInvocation.TRADEFED_LOG_NAME), eq(LogDataType.TEXT), any());
Mockito.verify(mMockListener).invocationEnded(0L);
// Invocation did not start for real so context is not locked.
mContext.addInvocationAttribute("test", "test");
diff --git a/tests/src/com/android/tradefed/invoker/ShardListenerTest.java b/tests/src/com/android/tradefed/invoker/ShardListenerTest.java
index a8127b1..c7c26d9 100644
--- a/tests/src/com/android/tradefed/invoker/ShardListenerTest.java
+++ b/tests/src/com/android/tradefed/invoker/ShardListenerTest.java
@@ -61,7 +61,8 @@
@Test
public void testBufferAndReplay() {
mMockListener.invocationStarted(mContext);
- mMockListener.testRunStarted("run1", 1);
+ mMockListener.testRunStarted(
+ EasyMock.eq("run1"), EasyMock.eq(1), EasyMock.eq(0), EasyMock.anyLong());
TestDescription tid = new TestDescription("class1", "name1");
mMockListener.testStarted(tid, 0l);
mMockListener.testEnded(tid, 0l, new HashMap<String, Metric>());
@@ -82,7 +83,8 @@
@Test
public void testPlayRuns() {
mMockListener.invocationStarted(mContext);
- mMockListener.testRunStarted("run1", 1);
+ mMockListener.testRunStarted(
+ EasyMock.eq("run1"), EasyMock.eq(1), EasyMock.eq(0), EasyMock.anyLong());
TestDescription tid = new TestDescription("class1", "name1");
mMockListener.testStarted(tid, 0l);
mMockListener.testEnded(tid, 0l, new HashMap<String, Metric>());
@@ -121,19 +123,22 @@
IInvocationContext module2 = new InvocationContext();
mMockListener.invocationStarted(mContext);
mMockListener.testModuleStarted(module1);
- mMockListener.testRunStarted("run1", 1);
+ mMockListener.testRunStarted(
+ EasyMock.eq("run1"), EasyMock.eq(1), EasyMock.eq(0), EasyMock.anyLong());
TestDescription tid = new TestDescription("class1", "name1");
mMockListener.testStarted(tid, 0l);
mMockListener.testEnded(tid, 0l, new HashMap<String, Metric>());
mMockListener.testRunEnded(0l, new HashMap<String, Metric>());
- mMockListener.testRunStarted("run2", 1);
+ mMockListener.testRunStarted(
+ EasyMock.eq("run2"), EasyMock.eq(1), EasyMock.eq(0), EasyMock.anyLong());
mMockListener.testStarted(tid, 0l);
mMockListener.testEnded(tid, 0l, new HashMap<String, Metric>());
mMockListener.testRunEnded(0l, new HashMap<String, Metric>());
mMockListener.testModuleEnded();
// expectation on second module
mMockListener.testModuleStarted(module2);
- mMockListener.testRunStarted("run3", 1);
+ mMockListener.testRunStarted(
+ EasyMock.eq("run3"), EasyMock.eq(1), EasyMock.eq(0), EasyMock.anyLong());
mMockListener.testStarted(tid, 0l);
mMockListener.testEnded(tid, 0l, new HashMap<String, Metric>());
mMockListener.testRunEnded(0l, new HashMap<String, Metric>());
@@ -165,6 +170,60 @@
EasyMock.verify(mMockListener, mMockDevice);
}
+ @Test
+ public void testBufferAndReplay_withModule_attempts() {
+ IInvocationContext module1 = new InvocationContext();
+ IInvocationContext module2 = new InvocationContext();
+ mMockListener.invocationStarted(mContext);
+ mMockListener.testModuleStarted(module1);
+ mMockListener.testRunStarted(
+ EasyMock.eq("run1"), EasyMock.eq(1), EasyMock.eq(0), EasyMock.anyLong());
+ TestDescription tid = new TestDescription("class1", "name1");
+ mMockListener.testStarted(tid, 0l);
+ mMockListener.testEnded(tid, 0l, new HashMap<String, Metric>());
+ mMockListener.testRunEnded(0l, new HashMap<String, Metric>());
+ mMockListener.testRunStarted(
+ EasyMock.eq("run1"), EasyMock.eq(1), EasyMock.eq(1), EasyMock.anyLong());
+ mMockListener.testStarted(tid, 0l);
+ mMockListener.testEnded(tid, 0l, new HashMap<String, Metric>());
+ mMockListener.testRunEnded(0l, new HashMap<String, Metric>());
+ mMockListener.testModuleEnded();
+ // expectation on second module
+ mMockListener.testModuleStarted(module2);
+ mMockListener.testRunStarted(
+ EasyMock.eq("run2"), EasyMock.eq(1), EasyMock.eq(0), EasyMock.anyLong());
+ mMockListener.testStarted(tid, 0l);
+ mMockListener.testEnded(tid, 0l, new HashMap<String, Metric>());
+ mMockListener.testRunEnded(0l, new HashMap<String, Metric>());
+ mMockListener.testModuleEnded();
+ mMockListener.invocationEnded(0l);
+
+ EasyMock.replay(mMockListener, mMockDevice);
+ mShardListener.setSupportGranularResults(true);
+ mShardListener.invocationStarted(mContext);
+ // 1st module
+ mShardListener.testModuleStarted(module1);
+ mShardListener.testRunStarted("run1", 1, 0);
+ mShardListener.testStarted(tid, 0l);
+ mShardListener.testEnded(tid, 0l, new HashMap<String, Metric>());
+ mShardListener.testRunEnded(0l, new HashMap<String, Metric>());
+ mShardListener.testRunStarted("run1", 1, 1);
+ mShardListener.testStarted(tid, 0l);
+ mShardListener.testEnded(tid, 0l, new HashMap<String, Metric>());
+ mShardListener.testRunEnded(0l, new HashMap<String, Metric>());
+ mShardListener.testModuleEnded();
+ // 2nd module
+ mShardListener.testModuleStarted(module2);
+ mShardListener.testRunStarted("run2", 1, 0);
+ mShardListener.testStarted(tid, 0l);
+ mShardListener.testEnded(tid, 0l, new HashMap<String, Metric>());
+ mShardListener.testRunEnded(0l, new HashMap<String, Metric>());
+ mShardListener.testModuleEnded();
+
+ mShardListener.invocationEnded(0l);
+ EasyMock.verify(mMockListener, mMockDevice);
+ }
+
/** Test the full ordering structure during a sharded pattern. */
@Test
public void testLogOrderingForSharding() throws Exception {
@@ -207,7 +266,8 @@
EasyMock.anyObject(),
EasyMock.eq(testFile));
- mockListener.testRunStarted("run1", 1);
+ mockListener.testRunStarted(
+ EasyMock.eq("run1"), EasyMock.eq(1), EasyMock.eq(0), EasyMock.anyLong());
TestDescription tid = new TestDescription("class1", "name1");
mockListener.testStarted(tid, 0l);
// Log association played in order for the test.
diff --git a/tests/src/com/android/tradefed/invoker/TestInvocationMultiTest.java b/tests/src/com/android/tradefed/invoker/TestInvocationMultiTest.java
index c9a22e5..9d59eb8 100644
--- a/tests/src/com/android/tradefed/invoker/TestInvocationMultiTest.java
+++ b/tests/src/com/android/tradefed/invoker/TestInvocationMultiTest.java
@@ -24,6 +24,7 @@
import com.android.tradefed.command.CommandOptions;
import com.android.tradefed.command.CommandRunner.ExitCode;
import com.android.tradefed.config.ConfigurationDescriptor;
+import com.android.tradefed.config.ConfigurationException;
import com.android.tradefed.config.DeviceConfigurationHolder;
import com.android.tradefed.config.IConfiguration;
import com.android.tradefed.device.ITestDevice;
@@ -39,6 +40,7 @@
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.LogDataType;
import com.android.tradefed.result.LogFile;
+import com.android.tradefed.testtype.retry.BaseRetryDecision;
import org.easymock.Capture;
import org.easymock.EasyMock;
@@ -77,6 +79,7 @@
mMockConfig = EasyMock.createMock(IConfiguration.class);
EasyMock.expect(mMockConfig.getPostProcessors()).andReturn(mPostProcessors);
+ EasyMock.expect(mMockConfig.getRetryDecision()).andReturn(new BaseRetryDecision());
mMockRescheduler = EasyMock.createMock(IRescheduler.class);
mMockTestListener = EasyMock.createMock(ITestInvocationListener.class);
mMockLogSaver = EasyMock.createMock(ILogSaver.class);
@@ -231,6 +234,89 @@
stubBuild.cleanUp();
}
+ /**
+ * Test when the {@link IConfiguration#resolveDynamicOptions()} fails, ensure we report all the
+ * logs and error.
+ */
+ @Test
+ public void testResolveDynamicFails() throws Throwable {
+ mDevice1 = EasyMock.createMock(ITestDevice.class);
+ EasyMock.expect(mDevice1.getIDevice()).andStubReturn(new StubDevice("serial1"));
+ mDevice2 = EasyMock.createMock(ITestDevice.class);
+ EasyMock.expect(mDevice2.getIDevice()).andStubReturn(new StubDevice("serial1"));
+ mContext.addAllocatedDevice("device1", mDevice1);
+ mContext.addAllocatedDevice("device2", mDevice2);
+
+ List<ITestInvocationListener> configListener = new ArrayList<>();
+ configListener.add(mMockTestListener);
+ EasyMock.expect(mMockConfig.getTestInvocationListeners())
+ .andReturn(configListener)
+ .times(2);
+ EasyMock.expect(mMockConfig.getLogSaver()).andReturn(mMockLogSaver);
+ EasyMock.expect(mMockConfig.getLogOutput()).andStubReturn(mMockLogger);
+ EasyMock.expect(mMockConfig.getConfigurationDescription()).andReturn(mConfigDesc);
+ mMockLogger.init();
+ EasyMock.expect(mMockLogger.getLog())
+ .andReturn(new ByteArrayInputStreamSource("fake".getBytes()));
+ mMockLogger.closeLog();
+ EasyMock.expectLastCall().times(2);
+
+ mMockLogRegistry.registerLogger(mMockLogger);
+ mMockLogRegistry.dumpToGlobalLog(mMockLogger);
+ mMockLogRegistry.unregisterLogger();
+ EasyMock.expectLastCall().times(2);
+
+ EasyMock.expect(mMockConfig.getCommandLine()).andStubReturn("empty");
+ EasyMock.expect(mMockConfig.getCommandOptions()).andStubReturn(new CommandOptions());
+ EasyMock.expect(mMockConfig.getTests()).andStubReturn(new ArrayList<>());
+
+ ConfigurationException configException = new ConfigurationException("failed to resolve");
+ mMockConfig.resolveDynamicOptions();
+ EasyMock.expectLastCall().andThrow(configException);
+
+ mMockConfig.cleanDynamicOptionFiles();
+
+ mMockTestListener.invocationStarted(mContext);
+ EasyMock.expect(mMockTestListener.getSummary()).andReturn(null);
+ mMockLogSaver.invocationStarted(mContext);
+ mMockTestListener.invocationFailed(EasyMock.eq(configException));
+ mMockTestListener.testLog(EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject());
+ EasyMock.expect(
+ mMockLogSaver.saveLogData(
+ EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject()))
+ .andReturn(new LogFile("", "", LogDataType.TEXT));
+ EasyMock.expect(
+ mMockLogSaver.saveLogData(
+ EasyMock.eq(TestInvocation.TRADEFED_END_HOST_LOG),
+ EasyMock.anyObject(),
+ EasyMock.anyObject()))
+ .andReturn(new LogFile("", "", LogDataType.TEXT));
+ mMockTestListener.invocationEnded(EasyMock.anyLong());
+ EasyMock.expect(mMockTestListener.getSummary()).andReturn(null);
+ mMockLogSaver.invocationEnded(EasyMock.anyLong());
+
+ EasyMock.replay(
+ mMockConfig,
+ mMockRescheduler,
+ mMockTestListener,
+ mMockLogSaver,
+ mMockLogger,
+ mMockLogRegistry,
+ mDevice1,
+ mDevice2);
+ mInvocation.invoke(
+ mContext, mMockConfig, mMockRescheduler, new ITestInvocationListener[] {});
+ EasyMock.verify(
+ mMockConfig,
+ mMockRescheduler,
+ mMockTestListener,
+ mMockLogSaver,
+ mMockLogger,
+ mMockLogRegistry,
+ mDevice1,
+ mDevice2);
+ }
+
@Test
public void testRunBuildProvider_oneThrow() throws Throwable {
makeTwoDeviceContext();
diff --git a/tests/src/com/android/tradefed/invoker/TestInvocationTest.java b/tests/src/com/android/tradefed/invoker/TestInvocationTest.java
index 38bca14..9aad2e1 100644
--- a/tests/src/com/android/tradefed/invoker/TestInvocationTest.java
+++ b/tests/src/com/android/tradefed/invoker/TestInvocationTest.java
@@ -59,6 +59,7 @@
import com.android.tradefed.invoker.shard.ShardHelper;
import com.android.tradefed.log.ILeveledLogOutput;
import com.android.tradefed.log.ILogRegistry;
+import com.android.tradefed.log.ITestLogger;
import com.android.tradefed.metrics.proto.MetricMeasurement.Measurements;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric.Builder;
@@ -85,6 +86,7 @@
import com.android.tradefed.testtype.IRetriableTest;
import com.android.tradefed.testtype.IShardableTest;
import com.android.tradefed.testtype.StubTest;
+import com.android.tradefed.testtype.retry.IRetryDecision;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.SystemUtil.EnvVariable;
import com.android.tradefed.util.keystore.StubKeyStoreFactory;
@@ -165,7 +167,6 @@
@Before
public void setUp() throws Exception {
-
mStubConfiguration = new Configuration("foo", "bar");
mStubMultiConfiguration = new Configuration("foo", "bar");
@@ -283,6 +284,11 @@
protected String getAdbVersion() {
return null;
}
+
+ @Override
+ void logHostAdb(ITestLogger logger) {
+ // inop for the common test case.
+ }
};
}
@@ -429,6 +435,45 @@
stubBuild.cleanUp();
}
+ /**
+ * Test when the reporting of host_log is returning null, in this case we don't log anything.
+ */
+ @Test
+ public void testInvoke_noBuild_noHostLog() throws Throwable {
+ EasyMock.expect(mMockBuildProvider.getBuild()).andReturn(null);
+ setupInvoke();
+ setupMockFailureListeners(
+ new BuildRetrievalError("No build found to test."),
+ true, /* don't expect host log */
+ false);
+
+ EasyMock.reset(mMockLogger, mMockLogRegistry);
+ mMockLogRegistry.registerLogger(mMockLogger);
+ mMockLogger.init();
+ mMockLogger.closeLog();
+ EasyMock.expectLastCall().times(2);
+
+ IRemoteTest test = EasyMock.createMock(IRemoteTest.class);
+ mStubConfiguration.setTest(test);
+ // Host log fails to report
+ EasyMock.expect(mMockLogger.getLog()).andReturn(null);
+ EasyMock.expect(mMockDevice.getLogcat()).andReturn(EMPTY_STREAM_SOURCE).times(2);
+ mMockDevice.clearLogcat();
+ EasyMock.expectLastCall().times(2);
+ Capture<IBuildInfo> captured = new Capture<>();
+ mMockBuildProvider.cleanUp(EasyMock.capture(captured));
+ mMockLogRegistry.unregisterLogger();
+ EasyMock.expectLastCall().times(2);
+ mMockLogRegistry.dumpToGlobalLog(mMockLogger);
+ replayMocks(test, mockRescheduler);
+ mTestInvocation.invoke(mStubInvocationMetadata, mStubConfiguration, mockRescheduler);
+ verifyMocks(test);
+
+ IBuildInfo stubBuild = captured.getValue();
+ assertEquals(BuildInfo.UNKNOWN_BUILD_ID, stubBuild.getBuildId());
+ stubBuild.cleanUp();
+ }
+
/** Test the invoke scenario where there is no build to test for a {@link IRetriableTest}. */
@Test
public void testInvoke_noBuildRetry() throws Throwable {
@@ -1161,7 +1206,11 @@
* calls.
*/
private void setupMockListeners(
- InvocationStatus status, Throwable throwable, boolean stubFailures) throws IOException {
+ InvocationStatus status,
+ Throwable throwable,
+ boolean stubFailures,
+ boolean reportHostLog)
+ throws IOException {
// invocationStarted
mMockLogSaver.invocationStarted(mStubInvocationMetadata);
mMockTestListener.invocationStarted(mStubInvocationMetadata);
@@ -1265,20 +1314,26 @@
EasyMock.expect(
mMockLogSaver.saveLogData(
- EasyMock.eq(TestInvocation.TRADEFED_LOG_NAME),
- EasyMock.eq(LogDataType.TEXT),
- (InputStream) EasyMock.anyObject()))
- .andReturn(new LogFile(PATH, URL, LogDataType.TEXT));
- mMockTestListener.testLog(EasyMock.eq(TestInvocation.TRADEFED_LOG_NAME),
- EasyMock.eq(LogDataType.TEXT), (InputStreamSource)EasyMock.anyObject());
- mMockSummaryListener.testLog(EasyMock.eq(TestInvocation.TRADEFED_LOG_NAME),
- EasyMock.eq(LogDataType.TEXT), (InputStreamSource)EasyMock.anyObject());
- EasyMock.expect(
- mMockLogSaver.saveLogData(
EasyMock.eq(TestInvocation.TRADEFED_END_HOST_LOG),
EasyMock.eq(LogDataType.TEXT),
(InputStream) EasyMock.anyObject()))
.andReturn(new LogFile(PATH, URL, LogDataType.TEXT));
+ if (reportHostLog) {
+ EasyMock.expect(
+ mMockLogSaver.saveLogData(
+ EasyMock.eq(TestInvocation.TRADEFED_LOG_NAME),
+ EasyMock.eq(LogDataType.TEXT),
+ (InputStream) EasyMock.anyObject()))
+ .andReturn(new LogFile(PATH, URL, LogDataType.TEXT));
+ mMockTestListener.testLog(
+ EasyMock.eq(TestInvocation.TRADEFED_LOG_NAME),
+ EasyMock.eq(LogDataType.TEXT),
+ (InputStreamSource) EasyMock.anyObject());
+ mMockSummaryListener.testLog(
+ EasyMock.eq(TestInvocation.TRADEFED_LOG_NAME),
+ EasyMock.eq(LogDataType.TEXT),
+ (InputStreamSource) EasyMock.anyObject());
+ }
// invocationEnded, getSummary (mMockTestListener)
mMockTestListener.invocationEnded(EasyMock.anyLong());
@@ -1346,6 +1401,72 @@
verifyMocks(test, mockRescheduler, shard1, shard2, mGlobalConfiguration);
}
+ /** Test that the before sharding log is properly carried even with auto-retry. */
+ @Test
+ public void testInvoke_shardableTest_autoRetry() throws Throwable {
+ List<ITestInvocationListener> listenerList =
+ mStubConfiguration.getTestInvocationListeners();
+ ILogSaverListener logSaverListener = EasyMock.createMock(ILogSaverListener.class);
+ listenerList.add(logSaverListener);
+ mStubConfiguration.setTestInvocationListeners(listenerList);
+
+ logSaverListener.setLogSaver(mMockLogSaver);
+ logSaverListener.invocationStarted(mStubInvocationMetadata);
+
+ String command = "empty --test-tag t";
+ String[] commandLine = {"empty", "--test-tag", "t"};
+ int shardCount = 2;
+ IShardableTest test = EasyMock.createMock(IShardableTest.class);
+ List<IRemoteTest> shards = new ArrayList<>();
+ IRemoteTest shard1 = EasyMock.createMock(IRemoteTest.class);
+ IRemoteTest shard2 = EasyMock.createMock(IRemoteTest.class);
+ shards.add(shard1);
+ shards.add(shard2);
+ EasyMock.expect(test.split()).andReturn(shards);
+ mStubConfiguration.setTest(test);
+ mStubConfiguration.setCommandLine(commandLine);
+
+ IRetryDecision decision = mStubConfiguration.getRetryDecision();
+ OptionSetter decisionSetter = new OptionSetter(decision);
+ decisionSetter.setOptionValue("auto-retry", "true");
+ decisionSetter.setOptionValue("max-testcase-run-count", "2");
+
+ mMockBuildProvider.cleanUp(mMockBuildInfo);
+ // The keystore is cloned for each shard.
+ EasyMock.expect(mGlobalConfiguration.getKeyStoreFactory())
+ .andReturn(new StubKeyStoreFactory())
+ .times(2);
+ setupInvoke();
+ EasyMock.reset(mMockLogger, mMockLogRegistry);
+ mMockLogRegistry.registerLogger(mMockLogger);
+ mMockLogger.init();
+ mMockLogger.closeLog();
+ mMockLogRegistry.unregisterLogger();
+ mMockLogSaver.invocationStarted(mStubInvocationMetadata);
+ mMockLogSaver.invocationEnded(0L);
+ setupNShardInvocation(shardCount, command);
+ // Ensure that the host_log gets logged after sharding.
+ EasyMock.expect(mMockLogger.getLog()).andReturn(EMPTY_STREAM_SOURCE);
+ String logName = "host_log_before_sharding";
+ LogFile loggedFile = new LogFile(PATH, URL, LogDataType.TEXT);
+ EasyMock.expect(
+ mMockLogSaver.saveLogData(
+ EasyMock.eq(logName),
+ EasyMock.eq(LogDataType.TEXT),
+ EasyMock.anyObject()))
+ .andReturn(loggedFile);
+ logSaverListener.logAssociation(logName, loggedFile);
+ mMockLogRegistry.unregisterLogger();
+ EasyMock.expectLastCall();
+ mMockLogger.closeLog();
+ EasyMock.expectLastCall();
+
+ mMockLogRegistry.dumpToGlobalLog(mMockLogger);
+ replayMocks(test, mockRescheduler, shard1, shard2, mGlobalConfiguration, logSaverListener);
+ mTestInvocation.invoke(mStubInvocationMetadata, mStubConfiguration, mockRescheduler);
+ verifyMocks(test, mockRescheduler, shard1, shard2, mGlobalConfiguration, logSaverListener);
+ }
+
/**
* Test that {@link TestInvocation#logDeviceBatteryLevel(IInvocationContext, String)} is not
* adding battery information for placeholder device.
@@ -1495,16 +1616,21 @@
}
private void setupMockSuccessListeners() throws IOException {
- setupMockListeners(InvocationStatus.SUCCESS, null, false);
+ setupMockListeners(InvocationStatus.SUCCESS, null, false, true);
}
private void setupMockFailureListeners(Throwable throwable) throws IOException {
- setupMockListeners(InvocationStatus.FAILED, throwable, false);
+ setupMockListeners(InvocationStatus.FAILED, throwable, false, true);
}
private void setupMockFailureListenersAny(Throwable throwable, boolean stubFailures)
throws IOException {
- setupMockListeners(InvocationStatus.FAILED, throwable, stubFailures);
+ setupMockListeners(InvocationStatus.FAILED, throwable, stubFailures, true);
+ }
+
+ private void setupMockFailureListeners(
+ Throwable throwable, boolean stubFailures, boolean reportHostLog) throws IOException {
+ setupMockListeners(InvocationStatus.FAILED, throwable, stubFailures, reportHostLog);
}
private void verifySummaryListener() {
@@ -1578,6 +1704,11 @@
protected String getAdbVersion() {
return null;
}
+
+ @Override
+ void logHostAdb(ITestLogger logger) {
+ // inop for the common test case.
+ }
};
}
@@ -1658,6 +1789,11 @@
protected String getAdbVersion() {
return null;
}
+
+ @Override
+ void logHostAdb(ITestLogger logger) {
+ // inop for the common test case.
+ }
};
}
@@ -1750,6 +1886,11 @@
protected String getAdbVersion() {
return null;
}
+
+ @Override
+ void logHostAdb(ITestLogger logger) {
+ // inop for the common test case.
+ }
};
}
diff --git a/tests/src/com/android/tradefed/invoker/sandbox/ParentSandboxInvocationExecutionTest.java b/tests/src/com/android/tradefed/invoker/sandbox/ParentSandboxInvocationExecutionTest.java
index adc27ad..6ec710d 100644
--- a/tests/src/com/android/tradefed/invoker/sandbox/ParentSandboxInvocationExecutionTest.java
+++ b/tests/src/com/android/tradefed/invoker/sandbox/ParentSandboxInvocationExecutionTest.java
@@ -38,6 +38,7 @@
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.invoker.InvocationContext;
import com.android.tradefed.invoker.TestInvocation.Stage;
+import com.android.tradefed.log.ITestLogger;
import com.android.tradefed.sandbox.SandboxOptions;
import com.android.tradefed.targetprep.ITargetCleaner;
import com.android.tradefed.targetprep.TargetSetupError;
@@ -59,12 +60,14 @@
private SandboxOptions mOptions;
private ITargetCleaner mMockPreparer;
private ITestDevice mMockDevice;
+ private ITestLogger mMockLogger;
@Before
public void setUp() {
mMockFactory = Mockito.mock(IConfigurationFactory.class);
mMockPreparer = Mockito.mock(ITargetCleaner.class);
mMockDevice = Mockito.mock(ITestDevice.class);
+ mMockLogger = Mockito.mock(ITestLogger.class);
mParentSandbox =
new ParentSandboxInvocationExecution() {
@@ -108,7 +111,7 @@
.createConfigurationFromArgs(new String[] {"parent-config"});
mParentSandbox.doSetup(mContext, mConfig, null);
- mParentSandbox.doTeardown(mContext, mConfig, null, null);
+ mParentSandbox.doTeardown(mContext, mConfig, mMockLogger, null);
mParentSandbox.doCleanUp(mContext, mConfig, null);
verify(mMockFactory, times(1)).createConfigurationFromArgs(Mockito.any());
@@ -132,7 +135,7 @@
doReturn(new StubDevice("stub")).when(mMockDevice).getIDevice();
mParentSandbox.doSetup(mContext, mConfig, null);
- mParentSandbox.doTeardown(mContext, mConfig, null, null);
+ mParentSandbox.doTeardown(mContext, mConfig, mMockLogger, null);
mParentSandbox.doCleanUp(mContext, mConfig, null);
mParentSandbox.reportLogs(
mMockDevice, configParent.getTestInvocationListeners().get(0), Stage.ERROR);
diff --git a/tests/src/com/android/tradefed/invoker/shard/TestsPoolPollerTest.java b/tests/src/com/android/tradefed/invoker/shard/TestsPoolPollerTest.java
index ca2cfdf..907b33a 100644
--- a/tests/src/com/android/tradefed/invoker/shard/TestsPoolPollerTest.java
+++ b/tests/src/com/android/tradefed/invoker/shard/TestsPoolPollerTest.java
@@ -22,6 +22,8 @@
import static org.junit.Assert.fail;
import com.android.ddmlib.Log.LogLevel;
+import com.android.tradefed.config.Configuration;
+import com.android.tradefed.config.IConfiguration;
import com.android.tradefed.config.OptionSetter;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.DeviceUnresponsiveException;
@@ -59,6 +61,7 @@
private ITestDevice mDevice;
private List<IMetricCollector> mMetricCollectors;
private ILogRegistry mMockRegistry;
+ private IConfiguration mConfiguration;
@Before
public void setUp() {
@@ -66,6 +69,7 @@
mDevice = Mockito.mock(ITestDevice.class);
mMockRegistry = Mockito.mock(ILogRegistry.class);
Mockito.doReturn("serial").when(mDevice).getSerialNumber();
+ mConfiguration = new Configuration("test", "test");
mMetricCollectors = new ArrayList<>();
}
@@ -104,9 +108,13 @@
*/
@Test
public void testPollingRun() throws Exception {
+ StubTest first = new StubTest();
+ OptionSetter setterFirst = new OptionSetter(first);
+ setterFirst.setOptionValue("run-a-test", "true");
int numTests = 5;
List<IRemoteTest> testsList = new ArrayList<>();
- for (int i = 0; i < numTests; i++) {
+ testsList.add(first);
+ for (int i = 0; i < numTests - 1; i++) {
IRemoteTest test = new StubTest();
OptionSetter setter = new OptionSetter(test);
setter.setOptionValue("run-a-test", "true");
@@ -114,6 +122,7 @@
}
CountDownLatch tracker = new CountDownLatch(1);
TestsPoolPoller poller = new TestsPoolPoller(testsList, tracker);
+ poller.setConfiguration(mConfiguration);
poller.setMetricCollectors(mMetricCollectors);
poller.run(mListener);
Mockito.verify(mListener, Mockito.times(numTests))
@@ -121,6 +130,9 @@
Mockito.verify(mListener, Mockito.times(numTests))
.testRunEnded(Mockito.anyLong(), Mockito.<HashMap<String, Metric>>any());
assertEquals(0, tracker.getCount());
+
+ // Ensure that the configuration set is the one that we passed.
+ assertEquals(mConfiguration, first.getConfiguration());
}
/**
diff --git a/tests/src/com/android/tradefed/result/TestResultTest.java b/tests/src/com/android/tradefed/result/TestResultTest.java
index cb971e2..92b46ef 100644
--- a/tests/src/com/android/tradefed/result/TestResultTest.java
+++ b/tests/src/com/android/tradefed/result/TestResultTest.java
@@ -15,10 +15,10 @@
*/
package com.android.tradefed.result;
-import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.*;
import com.android.ddmlib.testrunner.TestResult.TestStatus;
-import com.android.tradefed.testtype.retry.MergeStrategy;
+import com.android.tradefed.retry.MergeStrategy;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -52,6 +52,7 @@
TestResult.merge(testResults, MergeStrategy.ONE_TESTCASE_PASS_IS_PASS);
// Merge Strategy leave it a PASSED
assertEquals(TestStatus.PASSED, finalRes.getStatus());
+ assertTrue(finalRes.getProtoMetrics().containsKey(TestResult.IS_FLAKY));
assertEquals(2, finalRes.getStartTime());
assertEquals(7, finalRes.getEndTime());
assertEquals("failed", finalRes.getStackTrace());
diff --git a/tests/src/com/android/tradefed/sandbox/TradefedSandboxTest.java b/tests/src/com/android/tradefed/sandbox/TradefedSandboxTest.java
index 1ea83af..e1eff2c 100644
--- a/tests/src/com/android/tradefed/sandbox/TradefedSandboxTest.java
+++ b/tests/src/com/android/tradefed/sandbox/TradefedSandboxTest.java
@@ -185,6 +185,48 @@
assertEquals("Error when dumping the config. stderr: Ouch I failed.", res.getMessage());
}
+ /** Test that the fallback dump config also attempt to parse the config. */
+ @Test
+ public void testPrepareEnvironment_dumpConfigFail_fallback_fail() throws Exception {
+ mMockRunUtil.unsetEnvVariable(GlobalConfiguration.GLOBAL_CONFIG_VARIABLE);
+ EasyMock.expectLastCall().times(2);
+ mMockRunUtil.unsetEnvVariable(GlobalConfiguration.GLOBAL_CONFIG_SERVER_CONFIG_VARIABLE);
+ EasyMock.expectLastCall().times(2);
+ mMockRunUtil.setEnvVariable(
+ EasyMock.eq(GlobalConfiguration.GLOBAL_CONFIG_VARIABLE), EasyMock.anyObject());
+ mMockRunUtil.setEnvVariablePriority(EnvPriority.SET);
+ mMockListener.testLog(
+ EasyMock.eq("sandbox-global-config"),
+ EasyMock.eq(LogDataType.XML),
+ EasyMock.anyObject());
+ CommandResult result = new CommandResult();
+ result.setStatus(CommandStatus.FAILED);
+ result.setStderr("Could not find configuration 'empty'");
+ EasyMock.expect(
+ mMockRunUtil.runTimedCmd(
+ EasyMock.anyLong(),
+ EasyMock.eq("java"),
+ EasyMock.eq("-cp"),
+ EasyMock.anyObject(),
+ EasyMock.eq(SandboxConfigDump.class.getCanonicalName()),
+ EasyMock.eq("RUN_CONFIG"),
+ EasyMock.anyObject(),
+ EasyMock.eq("empty"),
+ EasyMock.eq("--arg"),
+ EasyMock.eq("1"),
+ EasyMock.eq("--use-proto-reporter")))
+ .andReturn(result);
+ setPrepareConfigurationExpectations();
+ EasyMock.replay(mMockConfig, mMockListener, mMockRunUtil);
+ Exception res = mSandbox.prepareEnvironment(mMockContext, mMockConfig, mMockListener);
+ EasyMock.verify(mMockConfig, mMockListener, mMockRunUtil);
+ assertNotNull(res);
+ assertTrue(res instanceof ConfigurationException);
+ assertEquals(
+ "Error when dumping the config. stderr: Could not find configuration 'empty'",
+ res.getMessage());
+ }
+
/**
* Test a case where the {@link
* com.android.tradefed.sandbox.TradefedSandbox#prepareEnvironment(IInvocationContext,
diff --git a/tests/src/com/android/tradefed/suite/checker/SystemServerStatusCheckerTest.java b/tests/src/com/android/tradefed/suite/checker/SystemServerStatusCheckerTest.java
index dee9e5d..b8eb13e 100644
--- a/tests/src/com/android/tradefed/suite/checker/SystemServerStatusCheckerTest.java
+++ b/tests/src/com/android/tradefed/suite/checker/SystemServerStatusCheckerTest.java
@@ -28,8 +28,6 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import java.util.HashMap;
-import java.util.Map;
/** Unit tests for {@link SystemServerStatusChecker} */
@RunWith(JUnit4.class)
@@ -55,37 +53,20 @@
@Test
public void testSystemServerProcessNotRestarted() throws Exception {
EasyMock.expect(mMockDevice.getProcessByName(EasyMock.eq("system_server")))
- .andReturn(new ProcessInfo("system", 914, "system_server", 1559091922L))
- .times(2);
+ .andReturn(new ProcessInfo("system", 914, "system_server", 1559091922L));
+ EasyMock.expect(mMockDevice.deviceSoftRestarted(EasyMock.anyObject())).andReturn(false);
EasyMock.replay(mMockDevice);
assertEquals(CheckStatus.SUCCESS, mChecker.preExecutionCheck(mMockDevice).getStatus());
assertEquals(CheckStatus.SUCCESS, mChecker.postExecutionCheck(mMockDevice).getStatus());
EasyMock.verify(mMockDevice);
}
- /** Test that system checker fail if system_server crashed and didn't come back. */
- @Test
- public void testSystemServerProcessCrashed() throws Exception {
- EasyMock.expect(mMockDevice.getProcessByName(EasyMock.eq("system_server")))
- .andReturn(new ProcessInfo("system", 914, "system_server", 1559091922L));
- EasyMock.expect(mMockDevice.getProcessByName(EasyMock.eq("system_server"))).andReturn(null);
- EasyMock.replay(mMockDevice);
- assertEquals(CheckStatus.SUCCESS, mChecker.preExecutionCheck(mMockDevice).getStatus());
- StatusCheckerResult result = mChecker.postExecutionCheck(mMockDevice);
- assertEquals(CheckStatus.FAILED, result.getStatus());
- assertTrue(result.isBugreportNeeded());
- EasyMock.verify(mMockDevice);
- }
-
/** Test that system checker fail if system_server restarted without device reboot. */
@Test
public void testSystemServerProcessRestartedWithoutDeviceReboot() throws Exception {
EasyMock.expect(mMockDevice.getProcessByName(EasyMock.eq("system_server")))
.andReturn(new ProcessInfo("system", 914, "system_server", 1559091922L));
- EasyMock.expect(mMockDevice.getProcessByName(EasyMock.eq("system_server")))
- .andReturn(new ProcessInfo("system", 1024, "system_server", 1559096000L));
- EasyMock.expect(mMockDevice.getBootHistorySince(EasyMock.eq(1559091922L)))
- .andReturn(new HashMap<Long, String>());
+ EasyMock.expect(mMockDevice.deviceSoftRestarted(EasyMock.anyObject())).andReturn(true);
EasyMock.replay(mMockDevice);
assertEquals(CheckStatus.SUCCESS, mChecker.preExecutionCheck(mMockDevice).getStatus());
StatusCheckerResult result = mChecker.postExecutionCheck(mMockDevice);
@@ -96,16 +77,11 @@
/** Test that system checker fail if system_server restarted with device reboot. */
@Test
- public void testSystemServerProcessRestartedWithUnintentionalDeviceReboot() throws Exception {
- Map<Long, String> history = new HashMap<Long, String>();
- history.put(1559095000L, "kernel_panic");
+ public void testSystemServerProcessRestartedWithAbnormalDeviceReboot() throws Exception {
EasyMock.expect(mMockDevice.getProcessByName(EasyMock.eq("system_server")))
.andReturn(new ProcessInfo("system", 914, "system_server", 1559091922L));
- EasyMock.expect(mMockDevice.getProcessByName(EasyMock.eq("system_server")))
- .andReturn(new ProcessInfo("system", 1024, "system_server", 1559096000L));
- EasyMock.expect(mMockDevice.getBootHistorySince(EasyMock.eq(1559091922L)))
- .andReturn(history);
- EasyMock.expect(mMockDevice.getLastExpectedRebootTimeMillis()).andReturn(200L);
+ EasyMock.expect(mMockDevice.deviceSoftRestarted(EasyMock.anyObject()))
+ .andThrow(new RuntimeException("abnormal reboot"));
EasyMock.replay(mMockDevice);
assertEquals(CheckStatus.SUCCESS, mChecker.preExecutionCheck(mMockDevice).getStatus());
StatusCheckerResult result = mChecker.postExecutionCheck(mMockDevice);
@@ -115,34 +91,13 @@
}
/**
- * Test that if the pid changed but there was a Tradefed reboot, we still not fail the checker.
- */
- @Test
- public void testSystemServerProcessRestartedWithIntentionalDeviceReboot() throws Exception {
- Map<Long, String> history = new HashMap<Long, String>();
- history.put(1559095000L, "reboot");
- EasyMock.expect(mMockDevice.getProcessByName(EasyMock.eq("system_server")))
- .andReturn(new ProcessInfo("system", 914, "system_server", 1559091922L));
- EasyMock.expect(mMockDevice.getProcessByName(EasyMock.eq("system_server")))
- .andReturn(new ProcessInfo("system", 1024, "system_server", 1559096000L));
- EasyMock.expect(mMockDevice.getBootHistorySince(EasyMock.eq(1559091922L)))
- .andReturn(history);
- // TF reboot was triggered by host
- EasyMock.expect(mMockDevice.getLastExpectedRebootTimeMillis()).andReturn(600L);
- EasyMock.replay(mMockDevice);
- assertEquals(CheckStatus.SUCCESS, mChecker.preExecutionCheck(mMockDevice).getStatus());
- StatusCheckerResult result = mChecker.postExecutionCheck(mMockDevice);
- assertEquals(CheckStatus.SUCCESS, result.getStatus());
- EasyMock.verify(mMockDevice);
- }
-
- /**
* Test that if fail to get system_server process at preExecutionCheck, we skip the
* system_server check in postExecution.
*/
@Test
public void testFailToGetSystemServerProcess() throws Exception {
EasyMock.expect(mMockDevice.getProcessByName(EasyMock.eq("system_server"))).andReturn(null);
+ mMockDevice.reboot();
EasyMock.replay(mMockDevice);
assertEquals(CheckStatus.FAILED, mChecker.preExecutionCheck(mMockDevice).getStatus());
assertEquals(CheckStatus.SUCCESS, mChecker.postExecutionCheck(mMockDevice).getStatus());
diff --git a/tests/src/com/android/tradefed/targetprep/BaseTargetPreparerTest.java b/tests/src/com/android/tradefed/targetprep/BaseTargetPreparerTest.java
new file mode 100644
index 0000000..d64e547
--- /dev/null
+++ b/tests/src/com/android/tradefed/targetprep/BaseTargetPreparerTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tradefed.targetprep;
+
+import static org.junit.Assert.*;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Configuration;
+import com.android.tradefed.config.IConfiguration;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.util.FileUtil;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/** Unit tests for {@link BaseTargetPreparer}. */
+@RunWith(JUnit4.class)
+public class BaseTargetPreparerTest {
+
+ private static class DisabledBaseTargetPreparer extends BaseTargetPreparer {
+
+ DisabledBaseTargetPreparer() {
+ setDisable(true);
+ }
+
+ @Override
+ public void setUp(ITestDevice device, IBuildInfo buildInfo)
+ throws TargetSetupError, BuildError, DeviceNotAvailableException {
+ // Ignore
+ }
+ }
+
+ @Test
+ public void testDisabledByDefault() throws Exception {
+ DisabledBaseTargetPreparer preparer = new DisabledBaseTargetPreparer();
+ assertTrue(preparer.isDisabled());
+ IConfiguration config = new Configuration("test", "test");
+ config.setTargetPreparer(preparer);
+ File configFile = FileUtil.createTempFile("base-target-prep-config", ".xml");
+ try (PrintWriter pw = new PrintWriter(configFile)) {
+ config.dumpXml(pw, new ArrayList<>(), false, false);
+ String value = FileUtil.readStringFromFile(configFile);
+ assertTrue(value.contains("<option name=\"disable\" value=\"true\" />"));
+ // Disable-tear-down was not modified so it should not appear.
+ assertFalse(value.contains("disable-tear-down"));
+ } finally {
+ FileUtil.deleteFile(configFile);
+ }
+ }
+}
diff --git a/tests/src/com/android/tradefed/targetprep/BuildInfoAttributePreparerTest.java b/tests/src/com/android/tradefed/targetprep/BuildInfoAttributePreparerTest.java
deleted file mode 100644
index efccdcb..0000000
--- a/tests/src/com/android/tradefed/targetprep/BuildInfoAttributePreparerTest.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tradefed.targetprep;
-
-import com.android.tradefed.build.BuildInfo;
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.config.OptionSetter;
-
-import junit.framework.TestCase;
-
-import java.util.Map;
-
-/**
- * Unit tests for {@link BuildInfoAttributePreparer}
- */
-public class BuildInfoAttributePreparerTest extends TestCase {
- private BuildInfoAttributePreparer mPrep = null;
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void setUp() throws Exception {
- super.setUp();
- mPrep = new BuildInfoAttributePreparer();
- }
-
- public void testSimple() throws Exception {
- final IBuildInfo build = new BuildInfo();
-
- OptionSetter opt = new OptionSetter(mPrep);
- opt.setOptionValue("build-attribute", "key", "value");
- mPrep.setUp(null, build);
-
- Map<String, String> map = build.getBuildAttributes();
- assertTrue(map.containsKey("key"));
- assertEquals("value", map.get("key"));
- }
-}
diff --git a/tests/src/com/android/tradefed/targetprep/DisableSELinuxTargetPreparerTest.java b/tests/src/com/android/tradefed/targetprep/DisableSELinuxTargetPreparerTest.java
index 42f5c4a..2d6210e 100644
--- a/tests/src/com/android/tradefed/targetprep/DisableSELinuxTargetPreparerTest.java
+++ b/tests/src/com/android/tradefed/targetprep/DisableSELinuxTargetPreparerTest.java
@@ -122,6 +122,7 @@
CommandResult result = new CommandResult();
result.setStdout(ENFORCED);
result.setStatus(CommandStatus.FAILED);
+ EasyMock.expect(mMockDevice.getDeviceDescriptor()).andReturn(null);
EasyMock.expect(mMockDevice.executeShellV2Command(GETENFORCE)).andReturn(result).once();
EasyMock.expect(mMockDevice.isAdbRoot()).andReturn(false).once();
EasyMock.expect(mMockDevice.enableAdbRoot()).andReturn(true).once();
diff --git a/tests/src/com/android/tradefed/targetprep/FastbootDeviceFlasherTest.java b/tests/src/com/android/tradefed/targetprep/FastbootDeviceFlasherTest.java
index 215f623..ce7936e 100644
--- a/tests/src/com/android/tradefed/targetprep/FastbootDeviceFlasherTest.java
+++ b/tests/src/com/android/tradefed/targetprep/FastbootDeviceFlasherTest.java
@@ -529,8 +529,11 @@
EasyMock.expect(mockBuild.getBootloaderImageFile()).andReturn(bootloaderFake);
CommandResult res = new CommandResult(CommandStatus.SUCCESS);
res.setStderr("flashing");
- EasyMock.expect(mMockDevice.executeFastbootCommand(EasyMock.eq("flash"),
- EasyMock.eq("hboot"), EasyMock.eq(bootloaderFake.getAbsolutePath())))
+ EasyMock.expect(
+ mMockDevice.executeFastbootCommand(
+ EasyMock.eq("flash"),
+ EasyMock.eq("bootloader"),
+ EasyMock.eq(bootloaderFake.getAbsolutePath())))
.andReturn(res);
mMockDevice.rebootIntoBootloader();
EasyMock.expectLastCall();
diff --git a/tests/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparerTest.java b/tests/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparerTest.java
index 4cde810..59c8926 100644
--- a/tests/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparerTest.java
+++ b/tests/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparerTest.java
@@ -353,11 +353,15 @@
EasyMock.expect(mMockDevice.executeShellV2Command("ls " + STAGING_DATA_DIR)).andReturn(res);
mMockDevice.reboot();
EasyMock.expectLastCall();
- List<String> trainInstallCmd = new ArrayList<>();
- trainInstallCmd.add("install-multi-package");
- trainInstallCmd.add(mFakeApk.getAbsolutePath());
- EasyMock.expect(mMockDevice.executeAdbCommand(trainInstallCmd.toArray(new String[0])))
- .andReturn("Success")
+ //TODO:add back once new adb is deployed to the lab
+ // List<String> trainInstallCmd = new ArrayList<>();
+ // trainInstallCmd.add("install-multi-package");
+ // trainInstallCmd.add(mFakeApk.getAbsolutePath());
+ // EasyMock.expect(mMockDevice.executeAdbCommand(trainInstallCmd.toArray(new String[0])))
+ // .andReturn("Success")
+ // .once();
+ EasyMock.expect(mMockDevice.installPackage((File) EasyMock.anyObject(), EasyMock.eq(true)))
+ .andReturn(null)
.once();
EasyMock.expect(mMockDevice.uninstallPackage(APK_PACKAGE_NAME)).andReturn(null).once();
@@ -723,11 +727,15 @@
}
private void mockSuccessfulInstallPackageAndReboot(File f) throws Exception {
- List<String> trainInstallCmd = new ArrayList<>();
- trainInstallCmd.add("install-multi-package");
- trainInstallCmd.add(f.getAbsolutePath());
- EasyMock.expect(mMockDevice.executeAdbCommand(trainInstallCmd.toArray(new String[0])))
- .andReturn("Success")
+ //TODO:add back once new adb is deployed to the lab
+ // List<String> trainInstallCmd = new ArrayList<>();
+ // trainInstallCmd.add("install-multi-package");
+ // trainInstallCmd.add(f.getAbsolutePath());
+ // EasyMock.expect(mMockDevice.executeAdbCommand(trainInstallCmd.toArray(new String[0])))
+ // .andReturn("Success")
+ // .once();
+ EasyMock.expect(mMockDevice.installPackage((File) EasyMock.anyObject(), EasyMock.eq(true)))
+ .andReturn(null)
.once();
mMockDevice.reboot();
EasyMock.expectLastCall().once();
diff --git a/tests/src/com/android/tradefed/targetprep/PreloadedClassesPreparerTest.java b/tests/src/com/android/tradefed/targetprep/PreloadedClassesPreparerTest.java
deleted file mode 100644
index e0e1830..0000000
--- a/tests/src/com/android/tradefed/targetprep/PreloadedClassesPreparerTest.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tradefed.targetprep;
-
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.when;
-
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.util.CommandResult;
-import com.android.tradefed.util.CommandStatus;
-import com.android.tradefed.util.IRunUtil;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mockito;
-
-import java.io.File;
-
-/** Unit tests for {@link PushFilePreparer} */
-@RunWith(JUnit4.class)
-public class PreloadedClassesPreparerTest {
- private static final String FAKE_FILE_PATH = "/file/path";
- private static final String FAKE_TOOL_PATH = "/tool/path";
- private static final String PRELOAD_TOOL_NAME = "preload2.jar";
- private static final String WRITE_COMMAND =
- "java -cp %s com.android.preload.Main --seq SERIAL write %s";
-
- private IBuildInfo mMockBuildInfo;
- private ITestDevice mMockDevice;
- private IRunUtil mMockRunUtil;
-
- private PreloadedClassesPreparer mRealPreparer;
- private PreloadedClassesPreparer mSpyPreparer;
-
- @Before
- public void setUp() throws Exception {
- // Setup mocks and spies
- mMockDevice = Mockito.mock(ITestDevice.class);
- mMockBuildInfo = Mockito.mock(IBuildInfo.class);
- mMockRunUtil = Mockito.mock(IRunUtil.class);
- mRealPreparer = new PreloadedClassesPreparer();
- mSpyPreparer = Mockito.spy(mRealPreparer);
- // Setup mock returns
- when(mMockDevice.getDeviceDescriptor()).thenReturn(null);
- when(mMockDevice.getSerialNumber()).thenReturn("SERIAL");
- when(mSpyPreparer.getRunUtil()).thenReturn(mMockRunUtil);
- }
-
- // Using the build info to get the preload tool is specific to remote runs.
- @Test
- public void testSetUp_RemoteSuccess() throws Exception {
- // Create a fully mocked success case
- File tool = Mockito.mock(File.class);
- when(tool.exists()).thenReturn(true);
- when(tool.getAbsolutePath()).thenReturn(FAKE_TOOL_PATH);
- when(mMockBuildInfo.getFile(PRELOAD_TOOL_NAME)).thenReturn(tool);
- when(mSpyPreparer.getPreloadedClassesPath()).thenReturn(FAKE_FILE_PATH);
- CommandResult result = new CommandResult();
- result.setStatus(CommandStatus.SUCCESS);
- // Expected output command based on the above.
- String[] command = String.format(WRITE_COMMAND, FAKE_TOOL_PATH, FAKE_FILE_PATH).split(" ");
- when(mMockRunUtil.runTimedCmd(PreloadedClassesPreparer.DEFAULT_TIMEOUT_MS, command))
- .thenReturn(result);
- // Run and don't encounter issues
- mSpyPreparer.setUp(mMockDevice, mMockBuildInfo);
- }
-
- // Using the build info to get the preload tool is specific to remote runs.
- @Test
- public void testSetUp_RemoteNoTool() throws Exception {
- // Set mocks to fail returning the tool
- when(mSpyPreparer.getPreloadedClassesPath()).thenReturn(FAKE_FILE_PATH);
- when(mMockBuildInfo.getFile(PRELOAD_TOOL_NAME)).thenReturn(null);
- try {
- mSpyPreparer.setUp(mMockDevice, mMockBuildInfo);
- fail("Did not fail when there was no tool available.");
- } catch (TargetSetupError e) {
- // Good, this should throw
- }
- }
-
- @Test
- public void testSetUp_LocalSuccess() throws Exception {
- when(mSpyPreparer.getPreloadToolPath()).thenReturn(FAKE_TOOL_PATH);
- when(mSpyPreparer.getPreloadedClassesPath()).thenReturn(FAKE_FILE_PATH);
- CommandResult result = new CommandResult();
- result.setStatus(CommandStatus.SUCCESS);
- // Expected output command based on the above.
- String[] command = String.format(WRITE_COMMAND, FAKE_TOOL_PATH, FAKE_FILE_PATH).split(" ");
- when(mMockRunUtil.runTimedCmd(PreloadedClassesPreparer.DEFAULT_TIMEOUT_MS, command))
- .thenReturn(result);
- // Run and don't encounter issues
- mSpyPreparer.setUp(mMockDevice, mMockBuildInfo);
- }
-
- @Test
- public void testSetUp_NoFile() throws Exception {
- // If not skipped, expect this to error out.
- mSpyPreparer.setUp(mMockDevice, mMockBuildInfo);
- }
-
- @Test
- public void testSetUp_WriteFailure() throws Exception {
- when(mSpyPreparer.getPreloadToolPath()).thenReturn(FAKE_TOOL_PATH);
- when(mSpyPreparer.getPreloadedClassesPath()).thenReturn(FAKE_FILE_PATH);
- CommandResult result = new CommandResult();
- result.setStatus(CommandStatus.FAILED);
- // Expected output command based on the above.
- String[] command = String.format(WRITE_COMMAND, FAKE_TOOL_PATH, FAKE_FILE_PATH).split(" ");
- when(mMockRunUtil.runTimedCmd(PreloadedClassesPreparer.DEFAULT_TIMEOUT_MS, command))
- .thenReturn(result);
- // Run and encounter a write issue
- try {
- mSpyPreparer.setUp(mMockDevice, mMockBuildInfo);
- fail("Did not fail when writing with the tool failed.");
- } catch (TargetSetupError e) {
- // Good, this should throw.
- }
- }
-}
diff --git a/tests/src/com/android/tradefed/targetprep/PushFilePreparerTest.java b/tests/src/com/android/tradefed/targetprep/PushFilePreparerTest.java
index 3817c98..c8ca2e6 100644
--- a/tests/src/com/android/tradefed/targetprep/PushFilePreparerTest.java
+++ b/tests/src/com/android/tradefed/targetprep/PushFilePreparerTest.java
@@ -314,6 +314,40 @@
}
/**
+ * Test {@link PushFilePreparer#resolveRelativeFilePath(IBuildInfo, String)} can locate a source
+ * file existed in a remote zip of a device build.
+ */
+ @Test
+ public void testResolveRelativeFilePath_withDeviceBuildInfo_remoteZip() throws Exception {
+ IDeviceBuildInfo buildInfo = EasyMock.createStrictMock(IDeviceBuildInfo.class);
+ String fileName = "source_file";
+
+ File testsDir = null;
+ try {
+ testsDir = FileUtil.createTempDir("tests_dir");
+ File hostTestCasesDir = FileUtil.getFileForPath(testsDir, HOST_TESTCASES);
+ FileUtil.mkdirsRWX(hostTestCasesDir);
+ File sourceFile = FileUtil.createTempFile(fileName, null, hostTestCasesDir);
+
+ // Change the file name so direct file search will return null.
+ fileName = sourceFile.getName() + "-2";
+ EasyMock.expect(buildInfo.getFile(fileName)).andReturn(null);
+ EasyMock.expect(buildInfo.getTestsDir()).andReturn(testsDir);
+ EasyMock.expect(buildInfo.getFile(BuildInfoFileKey.TARGET_LINKED_DIR)).andReturn(null);
+ EasyMock.expect(buildInfo.stageRemoteFile(EasyMock.eq(fileName), EasyMock.eq(testsDir)))
+ .andReturn(sourceFile);
+ EasyMock.replay(buildInfo);
+
+ assertEquals(
+ sourceFile.getAbsolutePath(),
+ mPreparer.resolveRelativeFilePath(buildInfo, fileName).getAbsolutePath());
+ EasyMock.verify(buildInfo);
+ } finally {
+ FileUtil.recursiveDelete(testsDir);
+ }
+ }
+
+ /**
* If a folder is found match it first and push it while filtering the abi that are not
* considered.
*/
diff --git a/tests/src/com/android/tradefed/targetprep/TimeSetterTargetPreparerTest.java b/tests/src/com/android/tradefed/targetprep/TimeSetterTargetPreparerTest.java
deleted file mode 100644
index 6a043c8..0000000
--- a/tests/src/com/android/tradefed/targetprep/TimeSetterTargetPreparerTest.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tradefed.targetprep;
-
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.config.OptionSetter;
-import com.android.tradefed.device.ITestDevice;
-
-import org.easymock.EasyMock;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.Date;
-import java.util.concurrent.TimeUnit;
-
-/** Unit Tests for {@link TimeSetterTargetPreparer}. */
-@RunWith(JUnit4.class)
-public class TimeSetterTargetPreparerTest {
- private TimeSetterTargetPreparer mTimeSetterTargetPreparer;
- private ITestDevice mMockDevice;
- private IBuildInfo mMockBuildInfo;
- private long mMockNanoTime;
-
- @Before
- public void setUp() {
- mMockDevice = EasyMock.createMock(ITestDevice.class);
- mMockBuildInfo = EasyMock.createMock(IBuildInfo.class);
- mTimeSetterTargetPreparer =
- new TimeSetterTargetPreparer() {
- @Override
- long getNanoTime() {
- return mMockNanoTime;
- }
- };
- }
-
- @Test
- public void testSaveTime() throws Exception {
- OptionSetter optionSetter = new OptionSetter(mTimeSetterTargetPreparer);
- optionSetter.setOptionValue("time", "555");
-
- EasyMock.expect(mMockDevice.getDeviceDate()).andReturn(123L).once();
- mMockDevice.setDate(new Date(555L));
- EasyMock.expectLastCall().once();
- mMockDevice.setDate(new Date(128L));
- EasyMock.expectLastCall().once();
-
- EasyMock.replay(mMockDevice, mMockBuildInfo);
-
- mMockNanoTime = TimeUnit.MILLISECONDS.toNanos(2);
- mTimeSetterTargetPreparer.setUp(mMockDevice, mMockBuildInfo);
- mMockNanoTime = TimeUnit.MILLISECONDS.toNanos(7);
- mTimeSetterTargetPreparer.tearDown(mMockDevice, mMockBuildInfo, null);
- EasyMock.verify(mMockBuildInfo, mMockDevice);
- }
-}
diff --git a/tests/src/com/android/tradefed/targetprep/suite/SuiteApkInstallerTest.java b/tests/src/com/android/tradefed/targetprep/suite/SuiteApkInstallerTest.java
index 5ba4a4d..83ca46e 100644
--- a/tests/src/com/android/tradefed/targetprep/suite/SuiteApkInstallerTest.java
+++ b/tests/src/com/android/tradefed/targetprep/suite/SuiteApkInstallerTest.java
@@ -260,6 +260,42 @@
}
}
+ /**
+ * Test that {@link SuiteApkInstaller#getLocalPathForFilename(IBuildInfo, String, ITestDevice)}
+ * returns the apk file retrieved from remote artifacts.
+ */
+ @Test
+ public void testGetLocalPathForFileName_remoteZip() throws Exception {
+ mPreparer =
+ new SuiteApkInstaller() {
+ @Override
+ protected File getRootDir(IBuildInfo buildInfo) throws FileNotFoundException {
+ return null;
+ }
+ };
+ IDeviceBuildInfo deviceBuildInfo = EasyMock.createMock(IDeviceBuildInfo.class);
+ File tmpDir = null;
+ try {
+ tmpDir = FileUtil.createTempDir("test");
+ Mockito.doReturn(null).when(mMockBuildInfo).getFile("foo.apk");
+ EasyMock.expect(deviceBuildInfo.getTestsDir()).andReturn(tmpDir);
+ // Change the name so direct file search will return null.
+ File tmpApk = FileUtil.createTempFile("suite-apk-installer-2", ".apk", tmpDir);
+ EasyMock.expect(
+ deviceBuildInfo.stageRemoteFile(
+ EasyMock.eq("suite-apk-installer.apk"), EasyMock.eq(tmpDir)))
+ .andReturn(tmpApk);
+ EasyMock.replay(deviceBuildInfo);
+ File apk =
+ mPreparer.getLocalPathForFilename(
+ deviceBuildInfo, "suite-apk-installer.apk", mMockDevice);
+ assertEquals(tmpApk.getAbsolutePath(), apk.getAbsolutePath());
+ EasyMock.verify(deviceBuildInfo);
+ } finally {
+ FileUtil.recursiveDelete(tmpDir);
+ }
+ }
+
/** If the file is found in the build shared resources directory, use it. */
@Test
public void testGetLocalPathForFileName_inSharedDir() throws Exception {
diff --git a/tests/src/com/android/tradefed/testtype/GTestBaseTest.java b/tests/src/com/android/tradefed/testtype/GTestBaseTest.java
index 27a816f..82cce6f 100644
--- a/tests/src/com/android/tradefed/testtype/GTestBaseTest.java
+++ b/tests/src/com/android/tradefed/testtype/GTestBaseTest.java
@@ -27,6 +27,8 @@
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.result.ITestInvocationListener;
+import com.google.common.collect.ImmutableList;
+
import org.easymock.EasyMock;
import org.junit.Before;
import org.junit.Test;
@@ -198,7 +200,8 @@
mSetter.setOptionValue("coverage", "true");
ITestInvocationListener listener =
- gTestBase.addNativeCoverageListenerIfEnabled(mMockTestDevice, mMockListener);
+ gTestBase.addNativeCoverageListenerIfEnabled(
+ mMockTestDevice, false, ImmutableList.of(), mMockListener);
assertThat(listener).isInstanceOf(NativeCodeCoverageListener.class);
}
@@ -211,7 +214,8 @@
mSetter.setOptionValue("coverage", "false");
ITestInvocationListener listener =
- gTestBase.addNativeCoverageListenerIfEnabled(mMockTestDevice, mMockListener);
+ gTestBase.addNativeCoverageListenerIfEnabled(
+ mMockTestDevice, false, ImmutableList.of(), mMockListener);
assertThat(listener).isSameAs(mMockListener);
}
diff --git a/tests/src/com/android/tradefed/testtype/GTestTest.java b/tests/src/com/android/tradefed/testtype/GTestTest.java
index 56a2b6f..97e71d6 100644
--- a/tests/src/com/android/tradefed/testtype/GTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/GTestTest.java
@@ -512,9 +512,6 @@
(TimeUnit) EasyMock.anyObject(),
EasyMock.anyInt());
- EasyMock.expect(mMockITestDevice.isAdbRoot()).andReturn(true);
- EasyMock.expect(mMockITestDevice.executeShellCommand("kill -37 -1")).andReturn("");
-
replayMocks();
mGTest.run(mMockInvocationListener);
@@ -574,11 +571,6 @@
(TimeUnit) EasyMock.anyObject(),
EasyMock.anyInt());
- EasyMock.expect(mMockITestDevice.isAdbRoot()).andReturn(true);
- EasyMock.expect(mMockITestDevice.getProcessPid(processNames.get(0))).andReturn("1");
- EasyMock.expect(mMockITestDevice.getProcessPid(processNames.get(1))).andReturn("1000");
- EasyMock.expect(mMockITestDevice.executeShellCommand("kill -37 1 1000")).andReturn("");
-
replayMocks();
mGTest.run(mMockInvocationListener);
diff --git a/tests/src/com/android/tradefed/testtype/NativeCodeCoverageListenerTest.java b/tests/src/com/android/tradefed/testtype/NativeCodeCoverageListenerTest.java
index 98d88eb..78718d9 100644
--- a/tests/src/com/android/tradefed/testtype/NativeCodeCoverageListenerTest.java
+++ b/tests/src/com/android/tradefed/testtype/NativeCodeCoverageListenerTest.java
@@ -23,6 +23,7 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
@@ -32,6 +33,7 @@
import com.android.tradefed.util.proto.TfMetricProtoUtil;
import com.google.common.base.VerifyException;
+import com.google.common.collect.ImmutableList;
import com.google.protobuf.ByteString;
import org.junit.Before;
@@ -84,19 +86,18 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
-
- mCodeCoverageListener = new NativeCodeCoverageListener(mMockDevice, mFakeListener);
}
@Test
public void test_logsCoverageZip() throws DeviceNotAvailableException, IOException {
+ mCodeCoverageListener = new NativeCodeCoverageListener(mMockDevice, mFakeListener);
+
// Setup mocks to write the coverage measurement to the file.
doReturn(true).when(mMockDevice).enableAdbRoot();
doReturn(
new StringJoiner("\n")
- .add("/data/misc/trace/proc/self/cwd/out/path/to/coverage.gcda")
- .add(
- "/data/misc/trace/proc/self/cwd/out/path/to/.hidden/coverage2.gcda")
+ .add("/data/misc/trace/path/to/coverage.gcda")
+ .add("/data/misc/trace/path/to/.hidden/coverage2.gcda")
.toString())
.when(mMockDevice)
.executeShellCommand(anyString());
@@ -139,6 +140,8 @@
@Test
public void testNoCoverageFiles_logsEmptyZip() throws DeviceNotAvailableException, IOException {
+ mCodeCoverageListener = new NativeCodeCoverageListener(mMockDevice, mFakeListener);
+
doReturn(true).when(mMockDevice).enableAdbRoot();
doReturn("").when(mMockDevice).executeShellCommand(anyString());
@@ -160,10 +163,54 @@
}
@Test
+ public void testCoverageFlushAllProcesses_flushAllCommandCalled()
+ throws DeviceNotAvailableException, IOException {
+ mCodeCoverageListener =
+ new NativeCodeCoverageListener(
+ mMockDevice, true, ImmutableList.of(), mFakeListener);
+
+ doReturn(true).when(mMockDevice).enableAdbRoot();
+ doReturn(true).when(mMockDevice).isAdbRoot();
+ doReturn("").when(mMockDevice).executeShellCommand(anyString());
+
+ // Simulate a test run.
+ mCodeCoverageListener.testRunStarted(RUN_NAME, TEST_COUNT);
+ Map<String, String> metric = new HashMap<>();
+ mCodeCoverageListener.testRunEnded(ELAPSED_TIME, TfMetricProtoUtil.upgradeConvert(metric));
+
+ // Verify the flush-all-coverage command was called.
+ verify(mMockDevice).executeShellCommand("kill -37 -1");
+ }
+
+ @Test
+ public void testCoverageFlushSpecificProcesses_flushCommandCalled()
+ throws DeviceNotAvailableException, IOException {
+ mCodeCoverageListener =
+ new NativeCodeCoverageListener(
+ mMockDevice, true, ImmutableList.of("mediaserver", "adbd"), mFakeListener);
+
+ doReturn(true).when(mMockDevice).enableAdbRoot();
+ doReturn(true).when(mMockDevice).isAdbRoot();
+ doReturn("123").when(mMockDevice).getProcessPid("mediaserver");
+ doReturn("56789").when(mMockDevice).getProcessPid("adbd");
+ doReturn("").when(mMockDevice).executeShellCommand(anyString());
+
+ // Simulate a test run.
+ mCodeCoverageListener.testRunStarted(RUN_NAME, TEST_COUNT);
+ Map<String, String> metric = new HashMap<>();
+ mCodeCoverageListener.testRunEnded(ELAPSED_TIME, TfMetricProtoUtil.upgradeConvert(metric));
+
+ // Verify the flush-coverage command was called with the specific pids.
+ verify(mMockDevice).executeShellCommand("kill -37 123 56789");
+ }
+
+ @Test
public void testFailure_unableToPullFile() throws DeviceNotAvailableException {
+ mCodeCoverageListener = new NativeCodeCoverageListener(mMockDevice, mFakeListener);
+
// Setup mocks.
doReturn(true).when(mMockDevice).enableAdbRoot();
- doReturn("/data/misc/trace/proc/self/cwd/out/some/path/to/coverage.gcda\n")
+ doReturn("/data/misc/trace/some/path/to/coverage.gcda\n")
.when(mMockDevice)
.executeShellCommand(anyString());
doReturn(false).when(mMockDevice).pullFile(anyString(), any());
diff --git a/tests/src/com/android/tradefed/testtype/retry/ResultAggregatorTest.java b/tests/src/com/android/tradefed/testtype/retry/ResultAggregatorTest.java
index f71c4e4..115770b 100644
--- a/tests/src/com/android/tradefed/testtype/retry/ResultAggregatorTest.java
+++ b/tests/src/com/android/tradefed/testtype/retry/ResultAggregatorTest.java
@@ -23,8 +23,11 @@
import com.android.tradefed.result.ILogSaver;
import com.android.tradefed.result.ILogSaverListener;
import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.LogDataType;
+import com.android.tradefed.result.LogFile;
import com.android.tradefed.result.TestDescription;
import com.android.tradefed.result.retry.ISupportGranularResults;
+import com.android.tradefed.retry.RetryStrategy;
import org.easymock.EasyMock;
import org.junit.Before;
@@ -60,6 +63,195 @@
@Test
public void testForwarding() {
+ LogFile beforeEnd = new LogFile("path", "url", LogDataType.TEXT);
+ TestDescription test1 = new TestDescription("classname", "test1");
+ TestDescription test2 = new TestDescription("classname", "test2");
+ ILogSaver logger = EasyMock.createMock(ILogSaver.class);
+
+ EasyMock.expect(mDetailedListener.supportGranularResults()).andStubReturn(true);
+
+ // Invocation level
+ mAggListener.setLogSaver(logger);
+ mAggListener.invocationStarted(mInvocationContext);
+ EasyMock.expect(mAggListener.getSummary()).andStubReturn(null);
+ mDetailedListener.setLogSaver(logger);
+ mDetailedListener.invocationStarted(mInvocationContext);
+ EasyMock.expect(mDetailedListener.getSummary()).andStubReturn(null);
+
+ mAggListener.testModuleStarted(mModuleContext);
+ mDetailedListener.testModuleStarted(mModuleContext);
+
+ // Detailed receives the breakdown
+ mDetailedListener.testRunStarted(
+ EasyMock.eq("run1"), EasyMock.eq(2), EasyMock.eq(0), EasyMock.anyLong());
+ mDetailedListener.testStarted(EasyMock.eq(test1), EasyMock.anyLong());
+ mDetailedListener.testEnded(
+ EasyMock.eq(test1),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mDetailedListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong());
+ mDetailedListener.testFailed(test2, "I failed. retry me.");
+ mDetailedListener.testEnded(
+ EasyMock.eq(test2),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mDetailedListener.testRunEnded(450L, new HashMap<String, Metric>());
+ mDetailedListener.testRunStarted(
+ EasyMock.eq("run1"), EasyMock.eq(2), EasyMock.eq(1), EasyMock.anyLong());
+ mDetailedListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong());
+ mDetailedListener.testEnded(
+ EasyMock.eq(test2),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mDetailedListener.testRunEnded(450L, new HashMap<String, Metric>());
+
+ // Aggregated listeners receives the aggregated results
+ mAggListener.testRunStarted(
+ EasyMock.eq("run1"), EasyMock.eq(2), EasyMock.eq(0), EasyMock.anyLong());
+ mAggListener.testStarted(EasyMock.eq(test1), EasyMock.anyLong());
+ mAggListener.testEnded(
+ EasyMock.eq(test1),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mAggListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong());
+ mAggListener.testEnded(
+ EasyMock.eq(test2),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mAggListener.testRunEnded(450L, new HashMap<String, Metric>());
+
+ mAggListener.testModuleEnded();
+ mDetailedListener.testModuleEnded();
+ mAggListener.logAssociation("before-end", beforeEnd);
+ mAggListener.invocationEnded(500L);
+ mDetailedListener.logAssociation("before-end", beforeEnd);
+ mDetailedListener.invocationEnded(500L);
+
+ EasyMock.replay(mAggListener, mDetailedListener);
+ mAggregator =
+ new ResultAggregator(
+ Arrays.asList(mAggListener, mDetailedListener),
+ RetryStrategy.RETRY_ANY_FAILURE);
+ mAggregator.setLogSaver(logger);
+ mAggregator.invocationStarted(mInvocationContext);
+ mAggregator.testModuleStarted(mModuleContext);
+ // Attempt 1
+ mAggregator.testRunStarted("run1", 2, 0);
+ mAggregator.testStarted(test1);
+ mAggregator.testEnded(test1, new HashMap<String, Metric>());
+ mAggregator.testStarted(test2);
+ mAggregator.testFailed(test2, "I failed. retry me.");
+ mAggregator.testEnded(test2, new HashMap<String, Metric>());
+ mAggregator.testRunFailed("run fail");
+ mAggregator.testRunEnded(450L, new HashMap<String, Metric>());
+ // Attempt 2
+ mAggregator.testRunStarted("run1", 2, 1);
+ mAggregator.testStarted(test2);
+ mAggregator.testEnded(test2, new HashMap<String, Metric>());
+ mAggregator.testRunEnded(450L, new HashMap<String, Metric>());
+
+ mAggregator.testModuleEnded();
+ mAggregator.logAssociation("before-end", beforeEnd);
+ mAggregator.invocationEnded(500L);
+ EasyMock.verify(mAggListener, mDetailedListener);
+ }
+
+ @Test
+ public void testForwarding_runFailure() {
+ TestDescription test1 = new TestDescription("classname", "test1");
+ TestDescription test2 = new TestDescription("classname", "test2");
+ ILogSaver logger = EasyMock.createMock(ILogSaver.class);
+
+ EasyMock.expect(mDetailedListener.supportGranularResults()).andStubReturn(true);
+
+ // Invocation level
+ mAggListener.setLogSaver(logger);
+ mAggListener.invocationStarted(mInvocationContext);
+ EasyMock.expect(mAggListener.getSummary()).andStubReturn(null);
+ mDetailedListener.setLogSaver(logger);
+ mDetailedListener.invocationStarted(mInvocationContext);
+ EasyMock.expect(mDetailedListener.getSummary()).andStubReturn(null);
+
+ mAggListener.testModuleStarted(mModuleContext);
+ mDetailedListener.testModuleStarted(mModuleContext);
+
+ // Detailed receives the breakdown
+ mDetailedListener.testRunStarted(
+ EasyMock.eq("run1"), EasyMock.eq(2), EasyMock.eq(0), EasyMock.anyLong());
+ mDetailedListener.testStarted(EasyMock.eq(test1), EasyMock.anyLong());
+ mDetailedListener.testEnded(
+ EasyMock.eq(test1),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mDetailedListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong());
+ mDetailedListener.testFailed(test2, "I failed. retry me.");
+ mDetailedListener.testEnded(
+ EasyMock.eq(test2),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mDetailedListener.testRunFailed("run fail\n\nrun fail 2");
+ mDetailedListener.testRunEnded(450L, new HashMap<String, Metric>());
+ mDetailedListener.testRunStarted(
+ EasyMock.eq("run1"), EasyMock.eq(2), EasyMock.eq(1), EasyMock.anyLong());
+ mDetailedListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong());
+ mDetailedListener.testEnded(
+ EasyMock.eq(test2),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mDetailedListener.testRunEnded(450L, new HashMap<String, Metric>());
+
+ // Aggregated listeners receives the aggregated results
+ mAggListener.testRunStarted(
+ EasyMock.eq("run1"), EasyMock.eq(2), EasyMock.eq(0), EasyMock.anyLong());
+ mAggListener.testStarted(EasyMock.eq(test1), EasyMock.anyLong());
+ mAggListener.testEnded(
+ EasyMock.eq(test1),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mAggListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong());
+ mAggListener.testEnded(
+ EasyMock.eq(test2),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mAggListener.testRunFailed(EasyMock.eq("run fail\n\nrun fail 2"));
+ mAggListener.testRunEnded(450L, new HashMap<String, Metric>());
+
+ mAggListener.testModuleEnded();
+ mDetailedListener.testModuleEnded();
+ mAggListener.invocationEnded(500L);
+ mDetailedListener.invocationEnded(500L);
+
+ EasyMock.replay(mAggListener, mDetailedListener);
+ mAggregator =
+ new ResultAggregator(
+ Arrays.asList(mAggListener, mDetailedListener),
+ RetryStrategy.RETRY_ANY_FAILURE);
+ mAggregator.setLogSaver(logger);
+ mAggregator.invocationStarted(mInvocationContext);
+ mAggregator.testModuleStarted(mModuleContext);
+ // Attempt 1
+ mAggregator.testRunStarted("run1", 2, 0);
+ mAggregator.testStarted(test1);
+ mAggregator.testEnded(test1, new HashMap<String, Metric>());
+ mAggregator.testStarted(test2);
+ mAggregator.testFailed(test2, "I failed. retry me.");
+ mAggregator.testEnded(test2, new HashMap<String, Metric>());
+ mAggregator.testRunFailed("run fail");
+ mAggregator.testRunEnded(450L, new HashMap<String, Metric>());
+ // Attempt 2
+ mAggregator.testRunStarted("run1", 2, 1);
+ mAggregator.testStarted(test2);
+ mAggregator.testEnded(test2, new HashMap<String, Metric>());
+ mAggregator.testRunFailed("run fail 2");
+ mAggregator.testRunEnded(450L, new HashMap<String, Metric>());
+
+ mAggregator.testModuleEnded();
+ mAggregator.invocationEnded(500L);
+ EasyMock.verify(mAggListener, mDetailedListener);
+ }
+
+ @Test
+ public void testForwarding_runFailure_noRerun() {
TestDescription test1 = new TestDescription("classname", "test1");
TestDescription test2 = new TestDescription("classname", "test2");
ILogSaver logger = EasyMock.createMock(ILogSaver.class);
@@ -93,14 +285,6 @@
EasyMock.<HashMap<String, Metric>>anyObject());
mDetailedListener.testRunFailed("run fail");
mDetailedListener.testRunEnded(450L, new HashMap<String, Metric>());
- mDetailedListener.testRunStarted(
- EasyMock.eq("run1"), EasyMock.eq(2), EasyMock.eq(1), EasyMock.anyLong());
- mDetailedListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong());
- mDetailedListener.testEnded(
- EasyMock.eq(test2),
- EasyMock.anyLong(),
- EasyMock.<HashMap<String, Metric>>anyObject());
- mDetailedListener.testRunEnded(450L, new HashMap<String, Metric>());
// Aggregated listeners receives the aggregated results
mAggListener.testRunStarted(
@@ -111,10 +295,12 @@
EasyMock.anyLong(),
EasyMock.<HashMap<String, Metric>>anyObject());
mAggListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong());
+ mAggListener.testFailed(test2, "I failed. retry me.");
mAggListener.testEnded(
EasyMock.eq(test2),
EasyMock.anyLong(),
EasyMock.<HashMap<String, Metric>>anyObject());
+ mAggListener.testRunFailed(EasyMock.eq("run fail"));
mAggListener.testRunEnded(450L, new HashMap<String, Metric>());
mAggListener.testModuleEnded();
@@ -139,12 +325,6 @@
mAggregator.testEnded(test2, new HashMap<String, Metric>());
mAggregator.testRunFailed("run fail");
mAggregator.testRunEnded(450L, new HashMap<String, Metric>());
- // Attempt 2
- mAggregator.testRunStarted("run1", 2, 1);
- mAggregator.testStarted(test2);
- mAggregator.testEnded(test2, new HashMap<String, Metric>());
- mAggregator.testRunEnded(450L, new HashMap<String, Metric>());
-
mAggregator.testModuleEnded();
mAggregator.invocationEnded(500L);
EasyMock.verify(mAggListener, mDetailedListener);
@@ -222,6 +402,7 @@
mAggregator.testStarted(test2);
mAggregator.testFailed(test2, "I failed. retry me.");
mAggregator.testEnded(test2, new HashMap<String, Metric>());
+ mAggregator.testRunFailed("I failed");
mAggregator.testRunEnded(450L, new HashMap<String, Metric>());
// Attempt 2
mAggregator.testRunStarted("run1", 2, 1);
@@ -233,6 +414,201 @@
EasyMock.verify(mAggListener, mDetailedListener);
}
+ @Test
+ public void testForwarding_singleRun_noModules_runFailures() {
+ TestDescription test1 = new TestDescription("classname", "test1");
+ TestDescription test2 = new TestDescription("classname", "test2");
+ ILogSaver logger = EasyMock.createMock(ILogSaver.class);
+
+ EasyMock.expect(mDetailedListener.supportGranularResults()).andStubReturn(true);
+
+ // Invocation level
+ mAggListener.setLogSaver(logger);
+ mAggListener.invocationStarted(mInvocationContext);
+ EasyMock.expect(mAggListener.getSummary()).andStubReturn(null);
+ mDetailedListener.setLogSaver(logger);
+ mDetailedListener.invocationStarted(mInvocationContext);
+ EasyMock.expect(mDetailedListener.getSummary()).andStubReturn(null);
+
+ // Detailed receives the breakdown
+ mDetailedListener.testRunStarted(
+ EasyMock.eq("run1"), EasyMock.eq(2), EasyMock.eq(0), EasyMock.anyLong());
+ mDetailedListener.testStarted(EasyMock.eq(test1), EasyMock.anyLong());
+ mDetailedListener.testEnded(
+ EasyMock.eq(test1),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mDetailedListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong());
+ mDetailedListener.testFailed(test2, "I failed. retry me.");
+ mDetailedListener.testEnded(
+ EasyMock.eq(test2),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mDetailedListener.testRunEnded(450L, new HashMap<String, Metric>());
+ mDetailedListener.testRunStarted(
+ EasyMock.eq("run1"), EasyMock.eq(2), EasyMock.eq(1), EasyMock.anyLong());
+ mDetailedListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong());
+ mDetailedListener.testEnded(
+ EasyMock.eq(test2),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mDetailedListener.testRunFailed(EasyMock.eq("I failed\n\nI failed 2"));
+ mDetailedListener.testRunEnded(450L, new HashMap<String, Metric>());
+
+ // Aggregated listeners receives the aggregated results
+ mAggListener.testRunStarted(
+ EasyMock.eq("run1"), EasyMock.eq(2), EasyMock.eq(0), EasyMock.anyLong());
+ mAggListener.testStarted(EasyMock.eq(test1), EasyMock.anyLong());
+ mAggListener.testEnded(
+ EasyMock.eq(test1),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mAggListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong());
+ mAggListener.testEnded(
+ EasyMock.eq(test2),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mAggListener.testRunFailed(EasyMock.eq("I failed\n\nI failed 2"));
+ mAggListener.testRunEnded(900L, new HashMap<String, Metric>());
+
+ mAggListener.invocationEnded(500L);
+ mDetailedListener.invocationEnded(500L);
+
+ EasyMock.replay(mAggListener, mDetailedListener);
+ mAggregator =
+ new ResultAggregator(
+ Arrays.asList(mAggListener, mDetailedListener),
+ RetryStrategy.RETRY_ANY_FAILURE);
+ mAggregator.setLogSaver(logger);
+ mAggregator.invocationStarted(mInvocationContext);
+ // Attempt 1
+ mAggregator.testRunStarted("run1", 2, 0);
+ mAggregator.testStarted(test1);
+ mAggregator.testEnded(test1, new HashMap<String, Metric>());
+ mAggregator.testStarted(test2);
+ mAggregator.testFailed(test2, "I failed. retry me.");
+ mAggregator.testEnded(test2, new HashMap<String, Metric>());
+ mAggregator.testRunFailed("I failed");
+ mAggregator.testRunEnded(450L, new HashMap<String, Metric>());
+ // Attempt 2
+ mAggregator.testRunStarted("run1", 2, 1);
+ mAggregator.testStarted(test2);
+ mAggregator.testEnded(test2, new HashMap<String, Metric>());
+ mAggregator.testRunFailed("I failed 2");
+ mAggregator.testRunEnded(450L, new HashMap<String, Metric>());
+
+ mAggregator.invocationEnded(500L);
+ EasyMock.verify(mAggListener, mDetailedListener);
+ }
+
+ @Test
+ public void testForwarding_noModules_runFailures() {
+ TestDescription test1 = new TestDescription("classname", "test1");
+ TestDescription test2 = new TestDescription("classname", "test2");
+ ILogSaver logger = EasyMock.createMock(ILogSaver.class);
+
+ EasyMock.expect(mDetailedListener.supportGranularResults()).andStubReturn(true);
+
+ // Invocation level
+ mAggListener.setLogSaver(logger);
+ mAggListener.invocationStarted(mInvocationContext);
+ EasyMock.expect(mAggListener.getSummary()).andStubReturn(null);
+ mDetailedListener.setLogSaver(logger);
+ mDetailedListener.invocationStarted(mInvocationContext);
+ EasyMock.expect(mDetailedListener.getSummary()).andStubReturn(null);
+
+ // Detailed receives the breakdown
+ mDetailedListener.testRunStarted(
+ EasyMock.eq("run1"), EasyMock.eq(2), EasyMock.eq(0), EasyMock.anyLong());
+ mDetailedListener.testStarted(EasyMock.eq(test1), EasyMock.anyLong());
+ mDetailedListener.testEnded(
+ EasyMock.eq(test1),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mDetailedListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong());
+ mDetailedListener.testFailed(test2, "I failed. retry me.");
+ mDetailedListener.testEnded(
+ EasyMock.eq(test2),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mDetailedListener.testRunEnded(450L, new HashMap<String, Metric>());
+ mDetailedListener.testRunStarted(
+ EasyMock.eq("run1"), EasyMock.eq(2), EasyMock.eq(1), EasyMock.anyLong());
+ mDetailedListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong());
+ mDetailedListener.testEnded(
+ EasyMock.eq(test2),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mDetailedListener.testRunFailed(EasyMock.eq("I failed\n\nI failed 2"));
+ mDetailedListener.testRunEnded(450L, new HashMap<String, Metric>());
+ mDetailedListener.testRunStarted(
+ EasyMock.eq("run2"), EasyMock.eq(1), EasyMock.eq(0), EasyMock.anyLong());
+ mDetailedListener.testStarted(EasyMock.eq(test1), EasyMock.anyLong());
+ mDetailedListener.testEnded(
+ EasyMock.eq(test1),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mDetailedListener.testRunEnded(450L, new HashMap<String, Metric>());
+
+ // Aggregated listeners receives the aggregated results
+ mAggListener.testRunStarted(
+ EasyMock.eq("run1"), EasyMock.eq(2), EasyMock.eq(0), EasyMock.anyLong());
+ mAggListener.testStarted(EasyMock.eq(test1), EasyMock.anyLong());
+ mAggListener.testEnded(
+ EasyMock.eq(test1),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mAggListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong());
+ mAggListener.testEnded(
+ EasyMock.eq(test2),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mAggListener.testRunFailed(EasyMock.eq("I failed\n\nI failed 2"));
+ mAggListener.testRunEnded(900L, new HashMap<String, Metric>());
+ mAggListener.testRunStarted(
+ EasyMock.eq("run2"), EasyMock.eq(1), EasyMock.eq(0), EasyMock.anyLong());
+ mAggListener.testStarted(EasyMock.eq(test1), EasyMock.anyLong());
+ mAggListener.testEnded(
+ EasyMock.eq(test1),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mAggListener.testRunEnded(450L, new HashMap<String, Metric>());
+
+ mAggListener.invocationEnded(500L);
+ mDetailedListener.invocationEnded(500L);
+
+ EasyMock.replay(mAggListener, mDetailedListener);
+ mAggregator =
+ new ResultAggregator(
+ Arrays.asList(mAggListener, mDetailedListener),
+ RetryStrategy.RETRY_ANY_FAILURE);
+ mAggregator.setLogSaver(logger);
+ mAggregator.invocationStarted(mInvocationContext);
+ // Attempt 1
+ mAggregator.testRunStarted("run1", 2, 0);
+ mAggregator.testStarted(test1);
+ mAggregator.testEnded(test1, new HashMap<String, Metric>());
+ mAggregator.testStarted(test2);
+ mAggregator.testFailed(test2, "I failed. retry me.");
+ mAggregator.testEnded(test2, new HashMap<String, Metric>());
+ mAggregator.testRunFailed("I failed");
+ mAggregator.testRunEnded(450L, new HashMap<String, Metric>());
+ // Attempt 2
+ mAggregator.testRunStarted("run1", 2, 1);
+ mAggregator.testStarted(test2);
+ mAggregator.testEnded(test2, new HashMap<String, Metric>());
+ mAggregator.testRunFailed("I failed 2");
+ mAggregator.testRunEnded(450L, new HashMap<String, Metric>());
+ // run 2
+ mAggregator.testRunStarted("run2", 1, 0);
+ mAggregator.testStarted(test1);
+ mAggregator.testEnded(test1, new HashMap<String, Metric>());
+ mAggregator.testRunEnded(450L, new HashMap<String, Metric>());
+
+ mAggregator.invocationEnded(500L);
+ EasyMock.verify(mAggListener, mDetailedListener);
+ }
+
/** Test aggregation of results coming from a module first then from a simple test run. */
@Test
public void testForwarding_module_noModule() {
@@ -342,6 +718,7 @@
mAggregator.testStarted(test2);
mAggregator.testFailed(test2, "I failed. retry me.");
mAggregator.testEnded(test2, new HashMap<String, Metric>());
+ mAggregator.testRunFailed("I failed");
mAggregator.testRunEnded(450L, new HashMap<String, Metric>());
// Attempt 2
mAggregator.testRunStarted("run1", 2, 1);
@@ -500,6 +877,143 @@
EasyMock.verify(mAggListener, mDetailedListener);
}
+ @Test
+ public void testForwarding_noModule_module_runFailure() {
+ TestDescription test1 = new TestDescription("classname", "test1");
+ TestDescription test2 = new TestDescription("classname", "test2");
+ ILogSaver logger = EasyMock.createMock(ILogSaver.class);
+
+ EasyMock.expect(mDetailedListener.supportGranularResults()).andStubReturn(true);
+
+ // Invocation level
+ mAggListener.setLogSaver(logger);
+ mAggListener.invocationStarted(mInvocationContext);
+ EasyMock.expect(mAggListener.getSummary()).andStubReturn(null);
+ mDetailedListener.setLogSaver(logger);
+ mDetailedListener.invocationStarted(mInvocationContext);
+ EasyMock.expect(mDetailedListener.getSummary()).andStubReturn(null);
+
+ mAggListener.testModuleStarted(mModuleContext);
+ mDetailedListener.testModuleStarted(mModuleContext);
+
+ // Detailed receives the breakdown
+ mDetailedListener.testRunStarted(
+ EasyMock.eq("run1"), EasyMock.eq(2), EasyMock.eq(0), EasyMock.anyLong());
+ mDetailedListener.testStarted(EasyMock.eq(test1), EasyMock.anyLong());
+ mDetailedListener.testEnded(
+ EasyMock.eq(test1),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mDetailedListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong());
+ mDetailedListener.testFailed(test2, "I failed. retry me.");
+ mDetailedListener.testEnded(
+ EasyMock.eq(test2),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mDetailedListener.testRunEnded(450L, new HashMap<String, Metric>());
+ mDetailedListener.testRunStarted(
+ EasyMock.eq("run1"), EasyMock.eq(2), EasyMock.eq(1), EasyMock.anyLong());
+ mDetailedListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong());
+ mDetailedListener.testEnded(
+ EasyMock.eq(test2),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mDetailedListener.testRunEnded(450L, new HashMap<String, Metric>());
+
+ // Aggregated listeners receives the aggregated results
+ mAggListener.testRunStarted(
+ EasyMock.eq("run1"), EasyMock.eq(2), EasyMock.eq(0), EasyMock.anyLong());
+ mAggListener.testStarted(EasyMock.eq(test1), EasyMock.anyLong());
+ mAggListener.testEnded(
+ EasyMock.eq(test1),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mAggListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong());
+ mAggListener.testEnded(
+ EasyMock.eq(test2),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mAggListener.testRunEnded(450L, new HashMap<String, Metric>());
+
+ mAggListener.testModuleEnded();
+ mDetailedListener.testModuleEnded();
+
+ // Detailed receives the breakdown for non-module
+ mDetailedListener.testRunStarted(
+ EasyMock.eq("run2"), EasyMock.eq(1), EasyMock.eq(0), EasyMock.anyLong());
+ mDetailedListener.testStarted(EasyMock.eq(test1), EasyMock.anyLong());
+ mDetailedListener.testFailed(test1, "I failed. retry me.");
+ mDetailedListener.testEnded(
+ EasyMock.eq(test1),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mDetailedListener.testRunEnded(450L, new HashMap<String, Metric>());
+ mDetailedListener.testRunStarted(
+ EasyMock.eq("run2"), EasyMock.eq(1), EasyMock.eq(1), EasyMock.anyLong());
+ mDetailedListener.testStarted(EasyMock.eq(test1), EasyMock.anyLong());
+ mDetailedListener.testEnded(
+ EasyMock.eq(test1),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mDetailedListener.testRunFailed(EasyMock.eq("I failed\n\nI failed 2"));
+ mDetailedListener.testRunEnded(450L, new HashMap<String, Metric>());
+
+ // Aggregated listeners receives the aggregated results for non-module
+ mAggListener.testRunStarted(
+ EasyMock.eq("run2"), EasyMock.eq(1), EasyMock.eq(0), EasyMock.anyLong());
+ mAggListener.testStarted(EasyMock.eq(test1), EasyMock.anyLong());
+ mAggListener.testEnded(
+ EasyMock.eq(test1),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mAggListener.testRunFailed(EasyMock.eq("I failed\n\nI failed 2"));
+ mAggListener.testRunEnded(900L, new HashMap<String, Metric>());
+
+ mAggListener.invocationEnded(500L);
+ mDetailedListener.invocationEnded(500L);
+
+ EasyMock.replay(mAggListener, mDetailedListener);
+ mAggregator =
+ new ResultAggregator(
+ Arrays.asList(mAggListener, mDetailedListener),
+ RetryStrategy.RETRY_ANY_FAILURE);
+ mAggregator.setLogSaver(logger);
+ mAggregator.invocationStarted(mInvocationContext);
+ // First run that is not a module
+ mAggregator.testRunStarted("run2", 1, 0);
+ mAggregator.testStarted(test1);
+ mAggregator.testFailed(test1, "I failed. retry me.");
+ mAggregator.testEnded(test1, new HashMap<String, Metric>());
+ mAggregator.testRunFailed("I failed");
+ mAggregator.testRunEnded(450L, new HashMap<String, Metric>());
+
+ mAggregator.testRunStarted("run2", 1, 1);
+ mAggregator.testStarted(test1);
+ mAggregator.testEnded(test1, new HashMap<String, Metric>());
+ mAggregator.testRunFailed("I failed 2");
+ mAggregator.testRunEnded(450L, new HashMap<String, Metric>());
+
+ // Module start
+ mAggregator.testModuleStarted(mModuleContext);
+ // Attempt 1
+ mAggregator.testRunStarted("run1", 2, 0);
+ mAggregator.testStarted(test1);
+ mAggregator.testEnded(test1, new HashMap<String, Metric>());
+ mAggregator.testStarted(test2);
+ mAggregator.testFailed(test2, "I failed. retry me.");
+ mAggregator.testEnded(test2, new HashMap<String, Metric>());
+ mAggregator.testRunEnded(450L, new HashMap<String, Metric>());
+ // Attempt 2
+ mAggregator.testRunStarted("run1", 2, 1);
+ mAggregator.testStarted(test2);
+ mAggregator.testEnded(test2, new HashMap<String, Metric>());
+ mAggregator.testRunEnded(450L, new HashMap<String, Metric>());
+ mAggregator.testModuleEnded();
+
+ mAggregator.invocationEnded(500L);
+ EasyMock.verify(mAggListener, mDetailedListener);
+ }
+
/** Test when two modules follow each others. */
@Test
public void testForwarding_module_module() {
@@ -642,4 +1156,112 @@
mAggregator.invocationEnded(500L);
EasyMock.verify(mAggListener, mDetailedListener);
}
+
+ @Test
+ public void testForwarding_module_pass_fail_fail() {
+ TestDescription test1 = new TestDescription("classname", "test1");
+ TestDescription test2 = new TestDescription("classname", "test2");
+ ILogSaver logger = EasyMock.createMock(ILogSaver.class);
+
+ EasyMock.expect(mDetailedListener.supportGranularResults()).andStubReturn(true);
+
+ // Invocation level
+ mAggListener.setLogSaver(logger);
+ mAggListener.invocationStarted(mInvocationContext);
+ EasyMock.expect(mAggListener.getSummary()).andStubReturn(null);
+ mDetailedListener.setLogSaver(logger);
+ mDetailedListener.invocationStarted(mInvocationContext);
+ EasyMock.expect(mDetailedListener.getSummary()).andStubReturn(null);
+
+ mAggListener.testModuleStarted(mModuleContext);
+ mDetailedListener.testModuleStarted(mModuleContext);
+
+ // Detailed receives the breakdown
+ mDetailedListener.testRunStarted(
+ EasyMock.eq("run1"), EasyMock.eq(2), EasyMock.eq(0), EasyMock.anyLong());
+ mDetailedListener.testStarted(EasyMock.eq(test1), EasyMock.anyLong());
+ mDetailedListener.testEnded(
+ EasyMock.eq(test1),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mDetailedListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong());
+ mDetailedListener.testFailed(test2, "I failed. retry me.");
+ mDetailedListener.testEnded(
+ EasyMock.eq(test2),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mDetailedListener.testRunEnded(450L, new HashMap<String, Metric>());
+ mDetailedListener.testRunStarted(
+ EasyMock.eq("run1"), EasyMock.eq(2), EasyMock.eq(1), EasyMock.anyLong());
+ mDetailedListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong());
+ mDetailedListener.testEnded(
+ EasyMock.eq(test2),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mDetailedListener.testRunEnded(450L, new HashMap<String, Metric>());
+ mDetailedListener.testRunStarted(
+ EasyMock.eq("run1"), EasyMock.eq(2), EasyMock.eq(2), EasyMock.anyLong());
+ mDetailedListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong());
+ mDetailedListener.testEnded(
+ EasyMock.eq(test2),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mDetailedListener.testRunEnded(450L, new HashMap<String, Metric>());
+
+ // Aggregated listeners receives the aggregated results
+ mAggListener.testRunStarted(
+ EasyMock.eq("run1"), EasyMock.eq(2), EasyMock.eq(0), EasyMock.anyLong());
+ mAggListener.testStarted(EasyMock.eq(test1), EasyMock.anyLong());
+ mAggListener.testEnded(
+ EasyMock.eq(test1),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mAggListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong());
+ mAggListener.testEnded(
+ EasyMock.eq(test2),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mAggListener.testRunEnded(450L, new HashMap<String, Metric>());
+
+ mAggListener.testModuleEnded();
+ mDetailedListener.testModuleEnded();
+
+ mAggListener.invocationEnded(500L);
+ mDetailedListener.invocationEnded(500L);
+
+ EasyMock.replay(mAggListener, mDetailedListener);
+ mAggregator =
+ new ResultAggregator(
+ Arrays.asList(mAggListener, mDetailedListener),
+ RetryStrategy.RETRY_ANY_FAILURE);
+ mAggregator.setLogSaver(logger);
+ mAggregator.invocationStarted(mInvocationContext);
+
+ // Module 1 starts
+ mAggregator.testModuleStarted(mModuleContext);
+ // Attempt 1
+ mAggregator.testRunStarted("run1", 2, 0);
+ mAggregator.testStarted(test1);
+ mAggregator.testEnded(test1, new HashMap<String, Metric>());
+ mAggregator.testStarted(test2);
+ mAggregator.testFailed(test2, "I failed. retry me.");
+ mAggregator.testEnded(test2, new HashMap<String, Metric>());
+ mAggregator.testRunEnded(450L, new HashMap<String, Metric>());
+ // Attempt 2
+ mAggregator.testRunStarted("run1", 2, 1);
+ mAggregator.testStarted(test2);
+ mAggregator.testEnded(test2, new HashMap<String, Metric>());
+ mAggregator.testRunFailed("failed2");
+ mAggregator.testRunEnded(450L, new HashMap<String, Metric>());
+ // Attempt 3
+ mAggregator.testRunStarted("run1", 2, 2);
+ mAggregator.testStarted(test2);
+ mAggregator.testEnded(test2, new HashMap<String, Metric>());
+ mAggregator.testRunFailed("failed3");
+ mAggregator.testRunEnded(450L, new HashMap<String, Metric>());
+ mAggregator.testModuleEnded();
+
+ mAggregator.invocationEnded(500L);
+ EasyMock.verify(mAggListener, mDetailedListener);
+ }
}
diff --git a/tests/src/com/android/tradefed/testtype/suite/BaseTestSuiteTest.java b/tests/src/com/android/tradefed/testtype/suite/BaseTestSuiteTest.java
index 659391e..43d36b8 100644
--- a/tests/src/com/android/tradefed/testtype/suite/BaseTestSuiteTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/BaseTestSuiteTest.java
@@ -134,6 +134,41 @@
}
}
+ /**
+ * Test that we create a module and the parameterized version of it for the include filter if
+ * not explicitly excluded.
+ */
+ @Test
+ public void testSetupFilters_parameterized_filter() throws Exception {
+ File tmpDir = FileUtil.createTempDir(TEST_MODULE);
+ File moduleConfig = new File(tmpDir, "CtsGestureTestCases.config");
+ moduleConfig.createNewFile();
+ try {
+ OptionSetter setter = new OptionSetter(mRunner);
+ setter.setOptionValue("enable-parameterized-modules", "true");
+ // The Gesture module has a parameter "instant".
+ setter.setOptionValue("module", "Gesture");
+ mRunner.setupFilters(tmpDir);
+ assertEquals(2, mRunner.getIncludeFilter().size());
+ assertThat(
+ mRunner.getIncludeFilter(),
+ hasItem(
+ new SuiteTestFilter(
+ mRunner.getRequestedAbi(), "CtsGestureTestCases", null)
+ .toString()));
+ assertThat(
+ mRunner.getIncludeFilter(),
+ hasItem(
+ new SuiteTestFilter(
+ mRunner.getRequestedAbi(),
+ "CtsGestureTestCases[instant]",
+ null)
+ .toString()));
+ } finally {
+ FileUtil.recursiveDelete(tmpDir);
+ }
+ }
+
@Test
public void testSetupFilters_match() throws Exception {
File tmpDir = FileUtil.createTempDir(TEST_MODULE);
diff --git a/tests/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapperTest.java b/tests/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapperTest.java
index b64498b..15e0560 100644
--- a/tests/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapperTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapperTest.java
@@ -15,12 +15,17 @@
*/
package com.android.tradefed.testtype.suite;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.testrunner.TestResult.TestStatus;
import com.android.tradefed.config.IConfiguration;
import com.android.tradefed.config.Option;
+import com.android.tradefed.config.OptionSetter;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.DeviceUnresponsiveException;
import com.android.tradefed.device.ITestDevice;
@@ -36,10 +41,12 @@
import com.android.tradefed.result.TestDescription;
import com.android.tradefed.result.TestResult;
import com.android.tradefed.result.TestRunResult;
+import com.android.tradefed.testtype.IDeviceTest;
import com.android.tradefed.testtype.IRemoteTest;
import com.android.tradefed.testtype.ITestFilterReceiver;
+import com.android.tradefed.testtype.retry.BaseRetryDecision;
+import com.android.tradefed.testtype.retry.IRetryDecision;
import com.android.tradefed.testtype.retry.RetryStatistics;
-import com.android.tradefed.testtype.retry.RetryStrategy;
import org.easymock.EasyMock;
import org.junit.Before;
@@ -63,6 +70,7 @@
private static final String RUN_NAME = "test run";
private static final String RUN_NAME_2 = "test run 2";
private InvocationContext mModuleInvocationContext;
+ private IRetryDecision mDecision;
private class BasicFakeTest implements IRemoteTest {
@@ -139,7 +147,9 @@
}
}
- private class FakeTest extends BasicFakeTest implements ITestFilterReceiver {
+ private class FakeTest extends BasicFakeTest implements ITestFilterReceiver, IDeviceTest {
+
+ private ITestDevice mDevice;
public FakeTest(ArrayList<TestDescription> testCases) {
super(testCases);
@@ -180,6 +190,16 @@
@Override
public void clearExcludeFilters() {}
+
+ @Override
+ public void setDevice(ITestDevice device) {
+ mDevice = device;
+ }
+
+ @Override
+ public ITestDevice getDevice() {
+ return mDevice;
+ }
}
private class MultiTestOneRunFakeTest extends FakeTest {
@@ -238,12 +258,12 @@
}
private GranularRetriableTestWrapper createGranularTestWrapper(
- IRemoteTest test, int maxRunCount) {
+ IRemoteTest test, int maxRunCount) throws Exception {
return createGranularTestWrapper(test, maxRunCount, new ArrayList<>());
}
private GranularRetriableTestWrapper createGranularTestWrapper(
- IRemoteTest test, int maxRunCount, List<IMetricCollector> collectors) {
+ IRemoteTest test, int maxRunCount, List<IMetricCollector> collectors) throws Exception {
GranularRetriableTestWrapper granularTestWrapper =
new GranularRetriableTestWrapper(test, null, null, null, maxRunCount);
granularTestWrapper.setModuleId("test module");
@@ -255,6 +275,12 @@
granularTestWrapper.setLogSaver(new FileSystemLogSaver());
IConfiguration mockModuleConfiguration = Mockito.mock(IConfiguration.class);
granularTestWrapper.setModuleConfig(mockModuleConfiguration);
+ mDecision = new BaseRetryDecision();
+ OptionSetter setter = new OptionSetter(mDecision);
+ setter.setOptionValue("retry-strategy", "RETRY_ANY_FAILURE");
+ setter.setOptionValue("max-testcase-run-count", Integer.toString(maxRunCount));
+ mDecision.setInvocationContext(mModuleInvocationContext);
+ granularTestWrapper.setRetryDecision(mDecision);
return granularTestWrapper;
}
@@ -275,7 +301,6 @@
.run(Mockito.any(ITestInvocationListener.class));
GranularRetriableTestWrapper granularTestWrapper = createGranularTestWrapper(mockTest, 1);
- granularTestWrapper.setRetryStrategy(RetryStrategy.RETRY_ANY_FAILURE);
try {
granularTestWrapper.run(new CollectingTestListener());
fail("Should have thrown an exception.");
@@ -327,7 +352,6 @@
int maxRunCount = 5;
GranularRetriableTestWrapper granularTestWrapper =
createGranularTestWrapper(test, maxRunCount);
- granularTestWrapper.setRetryStrategy(RetryStrategy.RETRY_ANY_FAILURE);
granularTestWrapper.run(new CollectingTestListener());
// Verify the test runs several times but under the same run name
assertEquals(1, granularTestWrapper.getTestRunResultCollected().size());
@@ -363,7 +387,7 @@
}
// Since tests stay failed, we have two failure in our monitoring.
- RetryStatistics stats = granularTestWrapper.getRetryStatistics();
+ RetryStatistics stats = mDecision.getRetryStatistics();
assertEquals(0, stats.mRetrySuccess);
assertEquals(2, stats.mRetryFailure);
}
@@ -387,7 +411,6 @@
int maxRunCount = 5;
GranularRetriableTestWrapper granularTestWrapper =
createGranularTestWrapper(test, maxRunCount);
- granularTestWrapper.setRetryStrategy(RetryStrategy.RETRY_ANY_FAILURE);
granularTestWrapper.run(new CollectingTestListener());
// Verify the test runs several times but under the same run name
assertEquals(1, granularTestWrapper.getTestRunResultCollected().size());
@@ -423,7 +446,7 @@
}
// One success since one test recover, one test never recover so one failure
- RetryStatistics stats = granularTestWrapper.getRetryStatistics();
+ RetryStatistics stats = mDecision.getRetryStatistics();
assertEquals(1, stats.mRetrySuccess);
assertEquals(1, stats.mRetryFailure);
}
@@ -448,7 +471,6 @@
int maxRunCount = 5;
GranularRetriableTestWrapper granularTestWrapper =
createGranularTestWrapper(test, maxRunCount);
- granularTestWrapper.setRetryStrategy(RetryStrategy.RETRY_ANY_FAILURE);
granularTestWrapper.run(new CollectingTestListener());
// Verify the test runs several times but under the same run name
assertEquals(1, granularTestWrapper.getTestRunResultCollected().size());
@@ -512,7 +534,7 @@
.containsKey(fakeTestCase2));
// One success since one test recover, one test never recover so one failure\
- RetryStatistics stats = granularTestWrapper.getRetryStatistics();
+ RetryStatistics stats = mDecision.getRetryStatistics();
assertEquals(2, stats.mRetrySuccess);
assertEquals(0, stats.mRetryFailure);
}
@@ -535,7 +557,6 @@
int maxRunCount = 3;
GranularRetriableTestWrapper granularTestWrapper =
createGranularTestWrapper(test, maxRunCount);
- granularTestWrapper.setRetryStrategy(RetryStrategy.RETRY_ANY_FAILURE);
granularTestWrapper.run(new CollectingTestListener());
assertEquals(1, granularTestWrapper.getTestRunResultCollected().size());
@@ -575,7 +596,6 @@
GranularRetriableTestWrapper granularTestWrapper =
createGranularTestWrapper(test, maxRunCount);
- granularTestWrapper.setRetryStrategy(RetryStrategy.RETRY_ANY_FAILURE);
granularTestWrapper.run(new CollectingTestListener());
// Two runs.
assertEquals(2, granularTestWrapper.getTestRunResultCollected().size());
@@ -616,7 +636,6 @@
FakeTest test = new FakeTest(testCases);
test.setRunFailure("I failed!");
GranularRetriableTestWrapper granularTestWrapper = createGranularTestWrapper(test, 3);
- granularTestWrapper.setRetryStrategy(RetryStrategy.RETRY_ANY_FAILURE);
granularTestWrapper.run(new CollectingTestListener());
assertEquals(1, granularTestWrapper.getTestRunResultCollected().size());
@@ -632,7 +651,7 @@
}
// No Test cases tracking since it was a run retry.
- RetryStatistics stats = granularTestWrapper.getRetryStatistics();
+ RetryStatistics stats = mDecision.getRetryStatistics();
assertEquals(0, stats.mRetrySuccess);
assertEquals(0, stats.mRetryFailure);
}
@@ -652,7 +671,6 @@
test.setRunFailure("I failed!");
test.setClearRunFailure(3);
GranularRetriableTestWrapper granularTestWrapper = createGranularTestWrapper(test, 7);
- granularTestWrapper.setRetryStrategy(RetryStrategy.RETRY_ANY_FAILURE);
granularTestWrapper.run(new CollectingTestListener());
assertEquals(1, granularTestWrapper.getTestRunResultCollected().size());
@@ -672,7 +690,7 @@
assertEquals(2, lastRes.getNumCompleteTests());
// No Test cases tracking since it was a run retry.
- RetryStatistics stats = granularTestWrapper.getRetryStatistics();
+ RetryStatistics stats = mDecision.getRetryStatistics();
assertEquals(0, stats.mRetrySuccess);
assertEquals(0, stats.mRetryFailure);
}
@@ -687,7 +705,12 @@
testCases.add(fakeTestCase2);
FakeTest test = new FakeTest(testCases);
GranularRetriableTestWrapper granularTestWrapper = createGranularTestWrapper(test, 3);
- granularTestWrapper.setRetryStrategy(RetryStrategy.ITERATIONS);
+ IRetryDecision decision = new BaseRetryDecision();
+ OptionSetter setter = new OptionSetter(decision);
+ setter.setOptionValue("retry-strategy", "ITERATIONS");
+ setter.setOptionValue("max-testcase-run-count", Integer.toString(3));
+ decision.setInvocationContext(mModuleInvocationContext);
+ granularTestWrapper.setRetryDecision(decision);
granularTestWrapper.run(new CollectingTestListener());
assertEquals(1, granularTestWrapper.getTestRunResultCollected().size());
@@ -703,7 +726,7 @@
}
// No Test cases tracking since it was a run retry.
- RetryStatistics stats = granularTestWrapper.getRetryStatistics();
+ RetryStatistics stats = mDecision.getRetryStatistics();
assertEquals(0, stats.mRetrySuccess);
assertEquals(0, stats.mRetryFailure);
}
@@ -719,7 +742,13 @@
FakeTest test = new FakeTest(testCases);
test.addFailedTestCase(fakeTestCase2);
GranularRetriableTestWrapper granularTestWrapper = createGranularTestWrapper(test, 3);
- granularTestWrapper.setRetryStrategy(RetryStrategy.RERUN_UNTIL_FAILURE);
+ IRetryDecision decision = new BaseRetryDecision();
+ OptionSetter setter = new OptionSetter(decision);
+ setter.setOptionValue("retry-strategy", "RERUN_UNTIL_FAILURE");
+ setter.setOptionValue("max-testcase-run-count", Integer.toString(3));
+ decision.setInvocationContext(mModuleInvocationContext);
+ granularTestWrapper.setRetryDecision(decision);
+
granularTestWrapper.run(new CollectingTestListener());
assertEquals(1, granularTestWrapper.getTestRunResultCollected().size());
@@ -735,8 +764,10 @@
assertEquals(2, res.getNumCompleteTests());
// No stats since no retry occurred.
- RetryStatistics stats = granularTestWrapper.getRetryStatistics();
- assertNull(stats);
+ RetryStatistics stats = mDecision.getRetryStatistics();
+ assertNotNull(stats);
+ assertEquals(0, stats.mRetrySuccess);
+ assertEquals(0, stats.mRetryFailure);
}
/**
@@ -768,7 +799,6 @@
test.addTestBecomePass(fakeTestCase1, 5);
GranularRetriableTestWrapper granularTestWrapper =
createGranularTestWrapper(test, 7, collectors);
- granularTestWrapper.setRetryStrategy(RetryStrategy.RETRY_ANY_FAILURE);
granularTestWrapper.run(new CollectingTestListener());
assertEquals(1, granularTestWrapper.getTestRunResultCollected().size());
@@ -809,7 +839,7 @@
assertFalse(lastRes.getRunProtoMetrics().containsKey("not-called"));
// Check that failure are cleared
- RetryStatistics stats = granularTestWrapper.getRetryStatistics();
+ RetryStatistics stats = mDecision.getRetryStatistics();
assertEquals(1, stats.mRetrySuccess);
assertEquals(0, stats.mRetryFailure);
}
@@ -819,15 +849,20 @@
*/
@Test
public void testIntraModuleRun_rebootAtLastIntraModuleRetry() throws Exception {
+ IRetryDecision decision = new BaseRetryDecision();
+ OptionSetter setter = new OptionSetter(decision);
+ setter.setOptionValue("reboot-at-last-retry", "true");
+ setter.setOptionValue("retry-strategy", "RETRY_ANY_FAILURE");
+ setter.setOptionValue("max-testcase-run-count", Integer.toString(3));
+ decision.setInvocationContext(mModuleInvocationContext);
FakeTest test = new FakeTest();
test.setRunFailure("I failed!");
ITestDevice mMockDevice = EasyMock.createMock(ITestDevice.class);
+ test.setDevice(mMockDevice);
mModuleInvocationContext.addAllocatedDevice("default-device1", mMockDevice);
GranularRetriableTestWrapper granularTestWrapper = createGranularTestWrapper(test, 3);
- granularTestWrapper.setRetryStrategy(RetryStrategy.RETRY_ANY_FAILURE);
- granularTestWrapper.setRebootAtLastRetry(true);
+ granularTestWrapper.setRetryDecision(decision);
EasyMock.expect(mMockDevice.getIDevice()).andStubReturn(EasyMock.createMock(IDevice.class));
- EasyMock.expect(mMockDevice.getSerialNumber()).andReturn("SERIAL");
mMockDevice.reboot();
EasyMock.replay(mMockDevice);
granularTestWrapper.run(new CollectingTestListener());
@@ -839,26 +874,28 @@
*/
@Test
public void testIntraModuleRun_rebootMultiDevicesAtLastIntraModuleRetry() throws Exception {
+ IRetryDecision decision = new BaseRetryDecision();
+ OptionSetter setter = new OptionSetter(decision);
+ setter.setOptionValue("reboot-at-last-retry", "true");
+ setter.setOptionValue("retry-strategy", "RETRY_ANY_FAILURE");
+ setter.setOptionValue("max-testcase-run-count", Integer.toString(3));
+ decision.setInvocationContext(mModuleInvocationContext);
FakeTest test = new FakeTest();
test.setRunFailure("I failed!");
ITestDevice mMockDevice = EasyMock.createMock(ITestDevice.class);
ITestDevice mMockDevice2 = EasyMock.createMock(ITestDevice.class);
+ test.setDevice(mMockDevice);
mModuleInvocationContext.addAllocatedDevice("default-device1", mMockDevice);
mModuleInvocationContext.addAllocatedDevice("default-device2", mMockDevice2);
GranularRetriableTestWrapper granularTestWrapper = createGranularTestWrapper(test, 3);
- granularTestWrapper.setRetryStrategy(RetryStrategy.RETRY_ANY_FAILURE);
- granularTestWrapper.setRebootAtLastRetry(true);
+ granularTestWrapper.setRetryDecision(decision);
EasyMock.expect(mMockDevice.getIDevice()).andStubReturn(EasyMock.createMock(IDevice.class));
- EasyMock.expect(mMockDevice.getSerialNumber()).andReturn("SERIAL");
EasyMock.expect(mMockDevice2.getIDevice()).andStubReturn(EasyMock.createMock(IDevice.class));
- EasyMock.expect(mMockDevice2.getSerialNumber()).andReturn("SERIAL-2");
mMockDevice.reboot();
mMockDevice2.reboot();
- EasyMock.replay(mMockDevice);
- EasyMock.replay(mMockDevice2);
+ EasyMock.replay(mMockDevice, mMockDevice2);
granularTestWrapper.run(new CollectingTestListener());
- EasyMock.verify(mMockDevice);
- EasyMock.verify(mMockDevice2);
+ EasyMock.verify(mMockDevice, mMockDevice2);
}
/** Collector that track if it was called or not */
diff --git a/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java b/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java
index 62b3512..c5a62c4 100644
--- a/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java
@@ -66,6 +66,8 @@
import com.android.tradefed.testtype.IRemoteTest;
import com.android.tradefed.testtype.ITestFilterReceiver;
import com.android.tradefed.testtype.StubTest;
+import com.android.tradefed.testtype.retry.BaseRetryDecision;
+import com.android.tradefed.testtype.retry.IRetryDecision;
import com.android.tradefed.util.AbiUtils;
import com.android.tradefed.util.MultiMap;
@@ -1492,9 +1494,15 @@
mTestSuite.setDevice(mMockDevice);
mTestSuite.setBuild(mMockBuildInfo);
mTestSuite.setConfiguration(mStubMainConfiguration);
- mStubMainConfiguration.getCommandOptions().setMaxRetryCount(maxRunLimit);
OptionSetter cmdSetter = new OptionSetter(mStubMainConfiguration.getCommandOptions());
cmdSetter.setOptionValue("retry-strategy", "RETRY_ANY_FAILURE");
+ IRetryDecision decision = new BaseRetryDecision();
+ OptionSetter setter = new OptionSetter(decision);
+ setter.setOptionValue("retry-strategy", "RETRY_ANY_FAILURE");
+ setter.setOptionValue("max-testcase-run-count", Integer.toString(maxRunLimit));
+ decision.setInvocationContext(mContext);
+ mStubMainConfiguration.setConfigurationObject(
+ Configuration.RETRY_DECISION_TYPE_NAME, decision);
mContext = new InvocationContext();
mTestSuite.setInvocationContext(mContext);
mContext.addAllocatedDevice(ConfigurationDef.DEFAULT_DEVICE_NAME, mMockDevice);
diff --git a/tests/src/com/android/tradefed/testtype/suite/ModuleDefinitionTest.java b/tests/src/com/android/tradefed/testtype/suite/ModuleDefinitionTest.java
index 6bb0d10..e2c178a 100644
--- a/tests/src/com/android/tradefed/testtype/suite/ModuleDefinitionTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/ModuleDefinitionTest.java
@@ -57,7 +57,8 @@
import com.android.tradefed.testtype.IDeviceTest;
import com.android.tradefed.testtype.IRemoteTest;
import com.android.tradefed.testtype.ITestFilterReceiver;
-import com.android.tradefed.testtype.retry.RetryStrategy;
+import com.android.tradefed.testtype.retry.BaseRetryDecision;
+import com.android.tradefed.testtype.retry.IRetryDecision;
import com.android.tradefed.testtype.suite.module.BaseModuleController;
import com.android.tradefed.testtype.suite.module.IModuleController;
import com.android.tradefed.testtype.suite.module.TestFailureModuleController;
@@ -100,6 +101,8 @@
private ILogSaver mMockLogSaver;
private ILogSaverListener mMockLogSaverListener;
+ private IRetryDecision mDecision = new BaseRetryDecision();
+
private interface ITestInterface extends IRemoteTest, IBuildReceiver, IDeviceTest {}
/** Test implementation that allows us to exercise different use cases * */
@@ -289,7 +292,7 @@
mMapDeviceTargetPreparer,
mMultiTargetPrepList,
new Configuration("", ""));
-
+ mModule.setRetryDecision(mDecision);
mModule.getModuleInvocationContext().addAllocatedDevice(DEFAULT_DEVICE_NAME, mMockDevice);
mModule.getModuleInvocationContext()
.addDeviceBuildInfo(DEFAULT_DEVICE_NAME, mMockBuildInfo);
@@ -398,6 +401,7 @@
mMapDeviceTargetPreparer,
mMultiTargetPrepList,
new Configuration("", ""));
+ mModule.setRetryDecision(mDecision);
mModule.getModuleInvocationContext().addAllocatedDevice(DEFAULT_DEVICE_NAME, mMockDevice);
mModule.getModuleInvocationContext()
.addDeviceBuildInfo(DEFAULT_DEVICE_NAME, mMockBuildInfo);
@@ -751,6 +755,7 @@
mMapDeviceTargetPreparer,
mMultiTargetPrepList,
new Configuration("", ""));
+ mModule.setRetryDecision(mDecision);
mModule.getModuleInvocationContext().addAllocatedDevice(DEFAULT_DEVICE_NAME, mMockDevice);
mModule.getModuleInvocationContext()
.addDeviceBuildInfo(DEFAULT_DEVICE_NAME, mMockBuildInfo);
@@ -799,6 +804,7 @@
mMapDeviceTargetPreparer,
mMultiTargetPrepList,
new Configuration("", ""));
+ mModule.setRetryDecision(mDecision);
mModule.getModuleInvocationContext().addAllocatedDevice(DEFAULT_DEVICE_NAME, mMockDevice);
mModule.getModuleInvocationContext()
.addDeviceBuildInfo(DEFAULT_DEVICE_NAME, mMockBuildInfo);
@@ -861,6 +867,7 @@
mMapDeviceTargetPreparer,
mMultiTargetPrepList,
new Configuration("", ""));
+ mModule.setRetryDecision(mDecision);
mModule.getModuleInvocationContext().addAllocatedDevice(DEFAULT_DEVICE_NAME, mMockDevice);
mModule.getModuleInvocationContext()
.addDeviceBuildInfo(DEFAULT_DEVICE_NAME, mMockBuildInfo);
@@ -1020,6 +1027,7 @@
mMapDeviceTargetPreparer,
mMultiTargetPrepList,
config);
+ mModule.setRetryDecision(mDecision);
mModule.getModuleInvocationContext().addAllocatedDevice(DEFAULT_DEVICE_NAME, mMockDevice);
mModule.getModuleInvocationContext()
.addDeviceBuildInfo(DEFAULT_DEVICE_NAME, mMockBuildInfo);
@@ -1031,9 +1039,9 @@
mMockListener.testEnded(
EasyMock.anyObject(),
EasyMock.anyLong(),
- (HashMap<String, Metric>) EasyMock.anyObject());
+ EasyMock.<HashMap<String, Metric>>anyObject());
mMockListener.testRunEnded(
- EasyMock.anyLong(), (HashMap<String, Metric>) EasyMock.anyObject());
+ EasyMock.anyLong(), EasyMock.<HashMap<String, Metric>>anyObject());
replayMocks();
mModule.run(mMockListener, null, null);
verifyMocks();
@@ -1081,6 +1089,7 @@
mMapDeviceTargetPreparer,
mMultiTargetPrepList,
new Configuration("", ""));
+ mModule.setRetryDecision(mDecision);
mModule.setLogSaver(mMockLogSaver);
mModule.getModuleInvocationContext().addAllocatedDevice(DEFAULT_DEVICE_NAME, mMockDevice);
mModule.getModuleInvocationContext()
@@ -1164,11 +1173,12 @@
mMapDeviceTargetPreparer,
mMultiTargetPrepList,
config);
+ mModule.setRetryDecision(mDecision);
mModule.setLogSaver(mMockLogSaver);
mMockListener.testRunStarted(
EasyMock.eq("fakeName"), EasyMock.eq(0), EasyMock.eq(0), EasyMock.anyLong());
mMockListener.testRunEnded(
- EasyMock.anyLong(), (HashMap<String, Metric>) EasyMock.anyObject());
+ EasyMock.anyLong(), EasyMock.<HashMap<String, Metric>>anyObject());
replayMocks();
mModule.run(mMockListener, null, failureListener);
verifyMocks();
@@ -1187,6 +1197,7 @@
mMapDeviceTargetPreparer,
mMultiTargetPrepList,
new Configuration("", ""));
+ mModule.setRetryDecision(mDecision);
mModule.getModuleInvocationContext().addAllocatedDevice(DEFAULT_DEVICE_NAME, mMockDevice);
mModule.getModuleInvocationContext()
.addDeviceBuildInfo(DEFAULT_DEVICE_NAME, mMockBuildInfo);
@@ -1255,6 +1266,7 @@
mMapDeviceTargetPreparer,
mMultiTargetPrepList,
new Configuration("", ""));
+ mModule.setRetryDecision(mDecision);
mModule.setLogSaver(mMockLogSaver);
mModule.getModuleInvocationContext().addAllocatedDevice(DEFAULT_DEVICE_NAME, mMockDevice);
mModule.getModuleInvocationContext()
@@ -1324,7 +1336,7 @@
mMapDeviceTargetPreparer,
mMultiTargetPrepList,
new Configuration("", ""));
-
+ mModule.setRetryDecision(mDecision);
mModule.getModuleInvocationContext().addAllocatedDevice(DEFAULT_DEVICE_NAME, mMockDevice);
mModule.getModuleInvocationContext()
.addDeviceBuildInfo(DEFAULT_DEVICE_NAME, mMockBuildInfo);
@@ -1377,6 +1389,7 @@
mMapDeviceTargetPreparer,
mMultiTargetPrepList,
new Configuration("", ""));
+ mModule.setRetryDecision(mDecision);
mModule.getModuleInvocationContext().addAllocatedDevice(DEFAULT_DEVICE_NAME, mMockDevice);
mModule.getModuleInvocationContext()
.addDeviceBuildInfo(DEFAULT_DEVICE_NAME, mMockBuildInfo);
@@ -1418,7 +1431,13 @@
mMapDeviceTargetPreparer,
mMultiTargetPrepList,
new Configuration("", ""));
- mModule.setRetryStrategy(RetryStrategy.ITERATIONS, false);
+ IRetryDecision decision = new BaseRetryDecision();
+ OptionSetter setter = new OptionSetter(decision);
+ setter.setOptionValue("retry-strategy", "ITERATIONS");
+ setter.setOptionValue("max-testcase-run-count", Integer.toString(3));
+ decision.setInvocationContext(mModule.getModuleInvocationContext());
+ mModule.setRetryDecision(decision);
+ mModule.setMergeAttemps(false);
mModule.getModuleInvocationContext().addAllocatedDevice(DEFAULT_DEVICE_NAME, mMockDevice);
mModule.getModuleInvocationContext()
.addDeviceBuildInfo(DEFAULT_DEVICE_NAME, mMockBuildInfo);
@@ -1503,7 +1522,13 @@
mMapDeviceTargetPreparer,
mMultiTargetPrepList,
new Configuration("", ""));
- mModule.setRetryStrategy(RetryStrategy.RETRY_ANY_FAILURE, false);
+ IRetryDecision decision = new BaseRetryDecision();
+ OptionSetter setter = new OptionSetter(decision);
+ setter.setOptionValue("retry-strategy", "RETRY_ANY_FAILURE");
+ setter.setOptionValue("max-testcase-run-count", Integer.toString(3));
+ decision.setInvocationContext(mModule.getModuleInvocationContext());
+ mModule.setRetryDecision(decision);
+ mModule.setMergeAttemps(false);
mModule.getModuleInvocationContext().addAllocatedDevice(DEFAULT_DEVICE_NAME, mMockDevice);
mModule.getModuleInvocationContext()
@@ -1518,6 +1543,9 @@
EasyMock.expect(mMockCleaner.isTearDownDisabled()).andStubReturn(false);
mMockCleaner.tearDown(
EasyMock.eq(mMockDevice), EasyMock.eq(mMockBuildInfo), EasyMock.isNull());
+ EasyMock.expect(mMockDevice.getIDevice())
+ .andReturn(EasyMock.createMock(IDevice.class))
+ .times(3);
// We expect a total count on the run start so 4, all aggregated under the same run
for (int attempt = 0; attempt < 3; attempt++) {
if (attempt == 0) {
diff --git a/tests/src/com/android/tradefed/testtype/suite/params/InstantAppHandlerTest.java b/tests/src/com/android/tradefed/testtype/suite/params/InstantAppHandlerTest.java
index 24209c5..42970ec 100644
--- a/tests/src/com/android/tradefed/testtype/suite/params/InstantAppHandlerTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/params/InstantAppHandlerTest.java
@@ -48,7 +48,7 @@
mModuleConfig = new Configuration("test", "test");
}
- private class TestFilterable implements IRemoteTest, ITestAnnotationFilterReceiver {
+ protected static class TestFilterable implements IRemoteTest, ITestAnnotationFilterReceiver {
public Set<String> mReceivedFiltered = new HashSet<>();
diff --git a/tests/src/com/android/tradefed/testtype/suite/params/SecondaryUserHandlerTest.java b/tests/src/com/android/tradefed/testtype/suite/params/SecondaryUserHandlerTest.java
new file mode 100644
index 0000000..5573742
--- /dev/null
+++ b/tests/src/com/android/tradefed/testtype/suite/params/SecondaryUserHandlerTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tradefed.testtype.suite.params;
+
+import static org.junit.Assert.assertEquals;
+
+import android.platform.test.annotations.SystemUserOnly;
+
+import com.android.tradefed.config.Configuration;
+import com.android.tradefed.config.IConfiguration;
+import com.android.tradefed.testtype.suite.params.InstantAppHandlerTest.TestFilterable;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link SecondaryUserHandler}. */
+@RunWith(JUnit4.class)
+public class SecondaryUserHandlerTest {
+
+ private SecondaryUserHandler mHandler;
+ private IConfiguration mModuleConfig;
+
+ @Before
+ public void setUp() {
+ mHandler = new SecondaryUserHandler();
+ mModuleConfig = new Configuration("test", "test");
+ }
+
+ /** Test that when a module configuration go through the handler it gets tuned properly. */
+ @Test
+ public void testApplySetup() {
+ TestFilterable test = new TestFilterable();
+ assertEquals(0, test.mReceivedFiltered.size());
+ mModuleConfig.setTest(test);
+ mHandler.applySetup(mModuleConfig);
+
+ // User zero is filtered
+ assertEquals(1, test.mReceivedFiltered.size());
+ assertEquals(
+ SystemUserOnly.class.getCanonicalName(), test.mReceivedFiltered.iterator().next());
+ }
+}
diff --git a/tests/src/com/android/tradefed/testtype/suite/retry/RetryReschedulerTest.java b/tests/src/com/android/tradefed/testtype/suite/retry/RetryReschedulerTest.java
index 380e75c..000e565 100644
--- a/tests/src/com/android/tradefed/testtype/suite/retry/RetryReschedulerTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/retry/RetryReschedulerTest.java
@@ -347,6 +347,45 @@
verify(mSuite).setExcludeFilter(excludeRun1);
}
+ /** Ensure that the --module option can be used to force a single module to run. */
+ @Test
+ public void testReschedule_module_option() throws Exception {
+ OptionSetter setter = new OptionSetter(mTest);
+ setter.setOptionValue(BaseTestSuite.MODULE_OPTION, "run0");
+ populateFakeResults(2, 2, 1, 0, 0, false);
+ mMockLoader.init();
+ EasyMock.expect(mMockLoader.getCommandLine()).andReturn("previous_command");
+ EasyMock.expect(mMockFactory.createConfigurationFromArgs(EasyMock.anyObject()))
+ .andReturn(mRescheduledConfiguration);
+ EasyMock.expect(mMockLoader.loadPreviousRecord()).andReturn(mFakeRecord);
+
+ mRescheduledConfiguration.setTests(EasyMock.anyObject());
+ EasyMock.expectLastCall().times(1);
+
+ EasyMock.expect(mMockRescheduler.scheduleConfig(mRescheduledConfiguration)).andReturn(true);
+ EasyMock.replay(
+ mMockRescheduler,
+ mMockLoader,
+ mMockFactory,
+ mRescheduledConfiguration,
+ mMockCommandOptions);
+ mTest.run(null);
+ EasyMock.verify(
+ mMockRescheduler,
+ mMockLoader,
+ mMockFactory,
+ mRescheduledConfiguration,
+ mMockCommandOptions);
+
+ Set<String> excludeRun0 = new HashSet<>();
+ excludeRun0.add("run0 test.class#testPass0");
+ verify(mSuite).setExcludeFilter(excludeRun0);
+ Set<String> excludeRun1 = new HashSet<>();
+ // Only run0 was requested to run.
+ excludeRun1.add("run1");
+ verify(mSuite).setExcludeFilter(excludeRun1);
+ }
+
/**
* Test that if an exclude-filter is provided without abi, we are still able to exclude all the
* matching modules for all abis.
@@ -391,6 +430,47 @@
verify(mSuite).setExcludeFilter(excludeRun1);
}
+ /** Ensure that --module works when abi are present. */
+ @Test
+ public void testReschedule_moduleOption_abi() throws Exception {
+ OptionSetter setter = new OptionSetter(mTest);
+ // We specify to exclude "run1"
+ setter.setOptionValue(BaseTestSuite.MODULE_OPTION, "run0");
+ populateFakeResults(2, 2, 1, 0, 0, false, new Abi("armeabi-v7a", "32"));
+ mMockLoader.init();
+ EasyMock.expect(mMockLoader.getCommandLine()).andReturn("previous_command");
+ EasyMock.expect(mMockFactory.createConfigurationFromArgs(EasyMock.anyObject()))
+ .andReturn(mRescheduledConfiguration);
+ EasyMock.expect(mMockLoader.loadPreviousRecord()).andReturn(mFakeRecord);
+
+ mRescheduledConfiguration.setTests(EasyMock.anyObject());
+ EasyMock.expectLastCall().times(1);
+
+ EasyMock.expect(mMockRescheduler.scheduleConfig(mRescheduledConfiguration)).andReturn(true);
+ EasyMock.replay(
+ mMockRescheduler,
+ mMockLoader,
+ mMockFactory,
+ mRescheduledConfiguration,
+ mMockCommandOptions);
+ mTest.run(null);
+ EasyMock.verify(
+ mMockRescheduler,
+ mMockLoader,
+ mMockFactory,
+ mRescheduledConfiguration,
+ mMockCommandOptions);
+
+ Set<String> excludeRun0 = new HashSet<>();
+ // Run with the abi are excluded
+ excludeRun0.add("armeabi-v7a run0 test.class#testPass0");
+ verify(mSuite).setExcludeFilter(excludeRun0);
+ Set<String> excludeRun1 = new HashSet<>();
+ // Even if run1 had failed test cases, it was excluded so it's not running.
+ excludeRun1.add("armeabi-v7a run1");
+ verify(mSuite).setExcludeFilter(excludeRun1);
+ }
+
/** Test rescheduling a configuration when no parameterized tests previously failed. */
@Test
public void testReschedule_parameterized_nofail() throws Exception {
diff --git a/tests/src/com/android/tradefed/util/BuildTestsZipUtilsTest.java b/tests/src/com/android/tradefed/util/BuildTestsZipUtilsTest.java
index 29f2f0f..22da4fd 100644
--- a/tests/src/com/android/tradefed/util/BuildTestsZipUtilsTest.java
+++ b/tests/src/com/android/tradefed/util/BuildTestsZipUtilsTest.java
@@ -113,4 +113,35 @@
FileUtil.recursiveDelete(testDir);
}
}
+
+ /**
+ * Tests that when the search finds the file in the sub testcases dir and under a different
+ * module directory we still properly find it and return it.
+ */
+ @Test
+ public void testGetApkFile_fromTestDir_differentModule_testCase() throws Exception {
+ DeviceBuildInfo buildInfo = new DeviceBuildInfo();
+ File testDir = FileUtil.createTempDir("test-dir-build-tests");
+ try {
+ buildInfo.setTestsDir(testDir, "1");
+ File testcasedir = new File(testDir, "testcases");
+ testcasedir.mkdir();
+ File apkDir = new File(testcasedir, "DifferentTestModule");
+ apkDir.mkdir();
+ File apk = new File(apkDir, "TestApk.apk");
+ apk.createNewFile();
+ File apkFile =
+ BuildTestsZipUtils.getApkFile(
+ buildInfo,
+ "TestApk.apk",
+ new ArrayList<>(),
+ AltDirBehavior.FALLBACK,
+ true,
+ null);
+ assertNotNull(apkFile);
+ assertEquals(apk, apkFile);
+ } finally {
+ FileUtil.recursiveDelete(testDir);
+ }
+ }
}
diff --git a/tests/src/com/android/tradefed/util/RemoteZipTest.java b/tests/src/com/android/tradefed/util/RemoteZipTest.java
new file mode 100644
index 0000000..ac461c8
--- /dev/null
+++ b/tests/src/com/android/tradefed/util/RemoteZipTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tradefed.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tradefed.build.IFileDownloader;
+import com.android.tradefed.util.zip.CentralDirectoryInfo;
+import com.android.tradefed.util.zip.EndCentralDirectoryInfo;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mockito;
+import org.mockito.stubbing.Answer;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+
+/** Unit tests for {@link RemoteZip} */
+@RunWith(JUnit4.class)
+public class RemoteZipTest {
+
+ private static final String REMOTE_FILE =
+ "aosp_master-linux-yakju-userdebug/P123/device-tests.zip";
+
+ private IFileDownloader mDownloader;
+ private List<CentralDirectoryInfo> mExpectedEntries;
+ private long mZipFileSize;
+
+ private void saveTestDataFile(File destFile, long startOffset, long size) {
+ try {
+ final InputStream inputStream =
+ ZipUtilTest.class.getResourceAsStream("/util/partial_zip.zip");
+ FileUtil.writeToFile(inputStream, destFile, false, startOffset, size);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mDownloader = Mockito.mock(IFileDownloader.class);
+
+ Mockito.doAnswer(
+ (Answer)
+ invocation -> {
+ String remoteFilePath = (String) invocation.getArgument(0);
+ File destFile = (File) invocation.getArgument(1);
+ long startOffset = (long) invocation.getArgument(2);
+ long size = (long) invocation.getArgument(3);
+ saveTestDataFile(destFile, startOffset, size);
+ return null;
+ })
+ .when(mDownloader)
+ .downloadFile(
+ Mockito.eq(REMOTE_FILE),
+ Mockito.anyObject(),
+ Mockito.anyLong(),
+ Mockito.anyLong());
+
+ File zipFile = FileUtil.createTempFileForRemote("zipfile", null);
+ // Delete it so name is available
+ zipFile.delete();
+ try {
+ saveTestDataFile(zipFile, -1, 0);
+ EndCentralDirectoryInfo endCentralDirInfo = new EndCentralDirectoryInfo(zipFile);
+ mExpectedEntries =
+ ZipUtil.getZipCentralDirectoryInfos(
+ zipFile, endCentralDirInfo, endCentralDirInfo.getCentralDirOffset());
+ mZipFileSize = zipFile.length();
+ } finally {
+ FileUtil.deleteFile(zipFile);
+ }
+ }
+
+ @Test
+ public void testGetZipEntries() throws Exception {
+ File destDir = null;
+ try {
+ destDir = FileUtil.createTempDir("test");
+
+ RemoteZip remoteZip = new RemoteZip(REMOTE_FILE, mZipFileSize, mDownloader);
+
+ List<CentralDirectoryInfo> entries = remoteZip.getZipEntries();
+
+ assertEquals(7, entries.size());
+ assertTrue(mExpectedEntries.containsAll(entries));
+ } finally {
+ FileUtil.recursiveDelete(destDir);
+ }
+ }
+
+ @Test
+ public void testDownloadFilesFromZip() throws Exception {
+ File destDir = null;
+ try {
+ destDir = FileUtil.createTempDir("test");
+
+ List<CentralDirectoryInfo> files = new ArrayList<>();
+ for (CentralDirectoryInfo info : mExpectedEntries) {
+ if (info.getFileName().equals("large_text/file.txt")
+ || info.getFileName().equals("executable/executable_file")) {
+ files.add(info);
+ }
+ }
+
+ RemoteZip remoteZip = new RemoteZip(REMOTE_FILE, mZipFileSize, mDownloader);
+ remoteZip.downloadFiles(destDir, files);
+
+ File targetFile = Paths.get(destDir.getPath(), "large_text", "file.txt").toFile();
+ assertEquals(4146093769L, FileUtil.calculateCrc32(targetFile));
+ targetFile = Paths.get(destDir.getPath(), "executable", "executable_file").toFile();
+ assertTrue(targetFile.exists());
+ // File not in the list is not unzipped.
+ targetFile = Paths.get(destDir.getPath(), "empty_file").toFile();
+ assertFalse(targetFile.exists());
+ } finally {
+ FileUtil.recursiveDelete(destDir);
+ }
+ }
+}
diff --git a/tests/src/com/android/tradefed/util/SubprocessTestResultsParserTest.java b/tests/src/com/android/tradefed/util/SubprocessTestResultsParserTest.java
index 9d84bab..7d95731 100644
--- a/tests/src/com/android/tradefed/util/SubprocessTestResultsParserTest.java
+++ b/tests/src/com/android/tradefed/util/SubprocessTestResultsParserTest.java
@@ -346,6 +346,19 @@
}
}
+ /** Tests that the parser can be joined immediately if no connection was established. */
+ @Test
+ public void testParser_noConnection() throws Exception {
+ ITestInvocationListener listener = EasyMock.createMock(ITestInvocationListener.class);
+ EasyMock.replay(listener);
+ try (SubprocessTestResultsParser parser =
+ new SubprocessTestResultsParser(listener, true, new InvocationContext())) {
+ // returns immediately as a connection was not established
+ assertTrue(parser.joinReceiver(50, false));
+ EasyMock.verify(listener);
+ }
+ }
+
/** Tests the parser receiving event on updating test tag. */
@Test
public void testParse_testTag() throws Exception {
diff --git a/tests/src/com/android/tradefed/util/zip/MergedZipEntryCollectionTest.java b/tests/src/com/android/tradefed/util/zip/MergedZipEntryCollectionTest.java
index 5cd860d..3ce254a 100644
--- a/tests/src/com/android/tradefed/util/zip/MergedZipEntryCollectionTest.java
+++ b/tests/src/com/android/tradefed/util/zip/MergedZipEntryCollectionTest.java
@@ -46,7 +46,7 @@
startOffset += info.getCompressedSize() + MergedZipEntryCollection.HEADER_SIZE;
List<MergedZipEntryCollection> collections =
- MergedZipEntryCollection.CreateCollections(entries);
+ MergedZipEntryCollection.createCollections(entries);
assertEquals(1, collections.size());
assertEquals(2, collections.get(0).getZipEntries().size());
assertEquals(10, collections.get(0).getStartOffset());
@@ -69,7 +69,7 @@
startOffset += info.getCompressedSize() + MergedZipEntryCollection.HEADER_SIZE;
List<MergedZipEntryCollection> collections =
- MergedZipEntryCollection.CreateCollections(entries);
+ MergedZipEntryCollection.createCollections(entries);
assertEquals(2, collections.size());
assertEquals(1, collections.get(0).getZipEntries().size());
assertEquals(10, collections.get(0).getStartOffset());
@@ -97,7 +97,7 @@
startOffset += info.getCompressedSize() + MergedZipEntryCollection.HEADER_SIZE;
List<MergedZipEntryCollection> collections =
- MergedZipEntryCollection.CreateCollections(entries);
+ MergedZipEntryCollection.createCollections(entries);
assertEquals(1, collections.size());
assertEquals(11, collections.get(0).getZipEntries().size());
assertEquals(10, collections.get(0).getStartOffset());
@@ -123,7 +123,7 @@
startOffset += info.getCompressedSize() + MergedZipEntryCollection.HEADER_SIZE;
List<MergedZipEntryCollection> collections =
- MergedZipEntryCollection.CreateCollections(entries);
+ MergedZipEntryCollection.createCollections(entries);
assertEquals(2, collections.size());
assertEquals(10, collections.get(0).getZipEntries().size());
assertEquals(10, collections.get(0).getStartOffset());