Merge "Snap for 5611628 from 49ddcfc5b100a5470fc824de62ab91170275ac94 to sdk-release" into sdk-release
diff --git a/.classpath b/.classpath
index 5ed457d..2f5448b 100644
--- a/.classpath
+++ b/.classpath
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
- <classpathentry kind="src" path="src"/>
+ <classpathentry excluding="com/android/tradefed/testtype/junit4/LongevityHostRunner.java|com/android/tradefed/testtype/junit4/builder/DeviceJUnit4ClassRunnerBuilder.java" kind="src" path="src"/>
<classpathentry kind="src" path="res"/>
<classpathentry kind="src" path="hamcrest"/>
<classpathentry kind="src" path="jline"/>
@@ -10,7 +10,11 @@
<classpathentry combineaccessrules="false" kind="src" path="/ddmlib"/>
<classpathentry combineaccessrules="false" kind="src" path="/loganalysis"/>
<classpathentry combineaccessrules="false" kind="src" path="/platform-annotations"/>
- <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER">
+ <attributes>
+ <attribute name="module" value="true"/>
+ </attributes>
+ </classpathentry>
<classpathentry exported="true" kind="var" path="TRADEFED_ROOT/prebuilts/misc/common/commons-compress/commons-compress-prebuilt.jar"/>
<classpathentry exported="true" kind="var" path="TRADEFED_ROOT/prebuilts/misc/common/devtools-annotations/devtools-annotations-prebuilt.jar"/>
<classpathentry exported="true" kind="var" path="TRADEFED_ROOT/prebuilts/misc/common/kxml2/kxml2-2.3.0.jar"/>
diff --git a/Android.bp b/Android.bp
index cbd1e50..2b63059 100644
--- a/Android.bp
+++ b/Android.bp
@@ -69,6 +69,7 @@
},
static_libs: [
"platformprotos",
+ "asuite_proto_java",
],
}
diff --git a/atest/Android.bp b/atest/Android.bp
index d67972b..f93be04 100644
--- a/atest/Android.bp
+++ b/atest/Android.bp
@@ -52,6 +52,7 @@
exclude_srcs: [
"*_unittest.py",
"*/*_unittest.py",
+ "asuite_lib_test/*.py",
"proto/*_pb2.py",
"proto/__init__.py",
],
@@ -111,6 +112,7 @@
"unittest_data/**/.*",
],
exclude_srcs: [
+ "asuite_lib_test/*.py",
"proto/*_pb2.py",
"proto/__init__.py",
],
@@ -149,6 +151,20 @@
},
}
+java_library_host {
+ name: "asuite_proto_java",
+ srcs: [
+ "proto/*.proto",
+ ],
+ libs: [
+ "libprotobuf-java-full",
+ ],
+ proto: {
+ canonical_path_from_root: false,
+ include_dirs: ["external/protobuf/src"],
+ },
+}
+
python_library_host {
name: "asuite_proto",
defaults: ["asuite_default"],
@@ -174,3 +190,4 @@
"asuite_metrics",
],
}
+
diff --git a/atest/TEST_MAPPING b/atest/TEST_MAPPING
index 0769294..5ea63b3 100644
--- a/atest/TEST_MAPPING
+++ b/atest/TEST_MAPPING
@@ -3,6 +3,22 @@
{
"name": "atest_unittests",
"host": true
+ },
+ {
+ "name": "asuite_metrics_lib_tests",
+ "host": true
+ },
+ {
+ "name": "asuite_metrics_lib_py3_tests",
+ "host": true
+ },
+ {
+ "name": "asuite_cc_lib_tests",
+ "host": true
+ },
+ {
+ "name": "asuite_cc_lib_py3_tests",
+ "host": true
}
]
}
diff --git a/atest/asuite_lib_test/Android.bp b/atest/asuite_lib_test/Android.bp
new file mode 100644
index 0000000..ba93d93
--- /dev/null
+++ b/atest/asuite_lib_test/Android.bp
@@ -0,0 +1,83 @@
+// 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.
+
+// Separate asuite_metrics and asuite_cc_client libs to different tests, due to asuite_cc_client
+// also include asuite_metrics and other needed python files, in order to make sure asuite_metrics
+// tests result is accurate, separate them to two different test modules.
+
+// For testing asuite_metrics python2 libs
+python_test_host {
+ name: "asuite_metrics_lib_tests",
+ main: "asuite_lib_run_test.py",
+ // These tests primarily check that the metric libs can be imported properly (see b/132086641).
+ // Specify a different pkg_path so that we can properly test them in isolation.
+ pkg_path: "asuite_test",
+ srcs: [
+ "asuite_lib_run_test.py",
+ "asuite_metrics_test.py",
+ ],
+ libs: [
+ "asuite_metrics",
+ ],
+ test_suites: ["general-tests"],
+ defaults: ["atest_py2_default"],
+}
+
+// For testing asuite_metrics python3 libs
+python_test_host {
+ name: "asuite_metrics_lib_py3_tests",
+ main: "asuite_lib_run_test.py",
+ pkg_path: "asuite_test",
+ srcs: [
+ "asuite_lib_run_test.py",
+ "asuite_metrics_test.py",
+ ],
+ libs: [
+ "asuite_metrics",
+ ],
+ test_suites: ["general-tests"],
+ defaults: ["atest_lib_default"],
+}
+
+// For testing asuite_cc_client python2 libs
+python_test_host {
+ name: "asuite_cc_lib_tests",
+ main: "asuite_lib_run_test.py",
+ pkg_path: "asuite_test",
+ srcs: [
+ "asuite_lib_run_test.py",
+ "asuite_cc_client_test.py",
+ ],
+ libs: [
+ "asuite_cc_client",
+ ],
+ test_suites: ["general-tests"],
+ defaults: ["atest_py2_default"],
+}
+
+// For testing asuite_cc_client python3 libs
+python_test_host {
+ name: "asuite_cc_lib_py3_tests",
+ main: "asuite_lib_run_test.py",
+ pkg_path: "asuite_test",
+ srcs: [
+ "asuite_lib_run_test.py",
+ "asuite_cc_client_test.py",
+ ],
+ libs: [
+ "asuite_cc_client",
+ ],
+ test_suites: ["general-tests"],
+ defaults: ["atest_lib_default"],
+}
diff --git a/atest/asuite_lib_test/asuite_cc_client_test.py b/atest/asuite_lib_test/asuite_cc_client_test.py
new file mode 100755
index 0000000..8ae1068
--- /dev/null
+++ b/atest/asuite_lib_test/asuite_cc_client_test.py
@@ -0,0 +1,35 @@
+#!/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_execution_info."""
+
+import unittest
+
+
+class AsuiteCCLibTest(unittest.TestCase):
+ """Tests for verify asuite_metrics libs"""
+
+ def test_import_asuite_cc_lib(self):
+ """Test asuite_cc_lib."""
+ # pylint: disable=import-error, unused-variable
+ from asuite.metrics import metrics
+ from asuite.metrics import metrics_base
+ from asuite.metrics import metrics_utils
+
+ # TODO (b/132602907): Add the real usage for checking if metrics pass or fail.
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/atest/asuite_lib_test/asuite_lib_run_test.py b/atest/asuite_lib_test/asuite_lib_run_test.py
new file mode 100644
index 0000000..ad98ae8
--- /dev/null
+++ b/atest/asuite_lib_test/asuite_lib_run_test.py
@@ -0,0 +1,77 @@
+#!/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.
+"""Main entrypoint for all of atest's unittest."""
+
+import os
+import sys
+import unittest
+from importlib import import_module
+
+
+def get_test_modules():
+ """Returns a list of test modules.
+
+ Finds all the test files (*_test.py) and get their relative
+ path (internal/lib/utils_test.py) and translate it to an import path and
+ strip the py ext (internal.lib.utils_test).
+
+ Returns:
+ List of strings (the testable module import path).
+ """
+ testable_modules = []
+ base_path = os.path.dirname(os.path.realpath(__file__))
+
+ for dirpath, _, files in os.walk(base_path):
+ for f in files:
+ if f.endswith("_test.py"):
+ # Now transform it into a relative import path.
+ full_file_path = os.path.join(dirpath, f)
+ rel_file_path = os.path.relpath(full_file_path, base_path)
+ rel_file_path, _ = os.path.splitext(rel_file_path)
+ rel_file_path = rel_file_path.replace(os.sep, ".")
+ testable_modules.append(rel_file_path)
+
+ return testable_modules
+
+
+def main(_):
+ """Main unittest entry.
+
+ Args:
+ argv: A list of system arguments. (unused)
+
+ Returns:
+ 0 if success. None-zero if fails.
+ """
+ # Force remove syspath related to atest to make sure the env is clean.
+ # These tests need to run in isolation (to find bugs like b/132086641)
+ # so we scrub out all atest modules.
+ for path in sys.path:
+ if path.endswith('/atest'):
+ sys.path.remove(path)
+ test_modules = get_test_modules()
+ for mod in test_modules:
+ import_module(mod)
+
+ loader = unittest.defaultTestLoader
+ test_suite = loader.loadTestsFromNames(test_modules)
+ runner = unittest.TextTestRunner(verbosity=2)
+ result = runner.run(test_suite)
+ sys.exit(not result.wasSuccessful())
+
+
+if __name__ == '__main__':
+ main(sys.argv[1:])
diff --git a/atest/asuite_lib_test/asuite_metrics_test.py b/atest/asuite_lib_test/asuite_metrics_test.py
new file mode 100755
index 0000000..b150f7f
--- /dev/null
+++ b/atest/asuite_lib_test/asuite_metrics_test.py
@@ -0,0 +1,33 @@
+#!/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_execution_info."""
+
+import unittest
+
+
+class AsuiteMetricsTest(unittest.TestCase):
+ """Tests for verify asuite_metrics libs"""
+
+ def test_import_asuite_metrics_lib(self):
+ """Test asuite_metrics_lib."""
+ # pylint: disable=import-error, unused-variable
+ from asuite import asuite_metrics
+
+ # TODO (b/132602907): Add the real usage for checking if metrics pass or fail.
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/atest/atest_execution_info_unittest.py b/atest/atest_execution_info_unittest.py
index 2c4ec31..f4fd7dc 100755
--- a/atest/atest_execution_info_unittest.py
+++ b/atest/atest_execution_info_unittest.py
@@ -32,7 +32,7 @@
test_time='(10ms)',
runner_total=None,
group_total=2,
- perf_info={}
+ additional_info={}
)
# pylint: disable=protected-access
diff --git a/atest/proto/clientanalytics.proto b/atest/proto/clientanalytics.proto
index 41293ea..e75bf78 100644
--- a/atest/proto/clientanalytics.proto
+++ b/atest/proto/clientanalytics.proto
@@ -1,5 +1,7 @@
syntax = "proto2";
+option java_package = "com.android.asuite.clearcut";
+
message LogRequest {
optional ClientInfo client_info = 1;
optional int32 log_source = 2;
diff --git a/atest/proto/common.proto b/atest/proto/common.proto
index d11a0f9..49cc48c 100644
--- a/atest/proto/common.proto
+++ b/atest/proto/common.proto
@@ -1,5 +1,7 @@
syntax = "proto2";
+option java_package = "com.android.asuite.clearcut";
+
message Duration {
required int64 seconds = 1;
required int32 nanos = 2;
@@ -11,4 +13,4 @@
enum UserType {
GOOGLE = 0;
EXTERNAL = 1;
-}
\ No newline at end of file
+}
diff --git a/atest/proto/external_user_log.proto b/atest/proto/external_user_log.proto
index 505497e..533ff0a 100644
--- a/atest/proto/external_user_log.proto
+++ b/atest/proto/external_user_log.proto
@@ -2,6 +2,8 @@
import "proto/common.proto";
+option java_package = "com.android.asuite.clearcut";
+
// Proto used by Atest CLI Tool for External Non-PII Users
message AtestLogEventExternal {
diff --git a/atest/proto/internal_user_log.proto b/atest/proto/internal_user_log.proto
index 8fbecc6..05d4dee 100644
--- a/atest/proto/internal_user_log.proto
+++ b/atest/proto/internal_user_log.proto
@@ -2,6 +2,8 @@
import "proto/common.proto";
+option java_package = "com.android.asuite.clearcut";
+
// Proto used by Atest CLI Tool for internal Users
message AtestLogEventInternal {
diff --git a/atest/result_reporter.py b/atest/result_reporter.py
index 52eb181..2cd4930 100644
--- a/atest/result_reporter.py
+++ b/atest/result_reporter.py
@@ -357,14 +357,8 @@
test.status,
constants.GREEN),
test.test_time))
- if test.perf_info.keys():
- print('\t%s: %s(ns) %s: %s(ns) %s: %s'
- %(au.colorize('cpu_time', constants.BLUE),
- test.perf_info['cpu_time'],
- au.colorize('real_time', constants.BLUE),
- test.perf_info['real_time'],
- au.colorize('iterations', constants.BLUE),
- test.perf_info['iterations']))
+ for key, data in test.additional_info.items():
+ print('\t%s: %s' % (au.colorize(key, constants.BLUE), data))
elif test.status == test_runner_base.IGNORED_STATUS:
# Example: [33/92] test_name: IGNORED (12ms)
print('[%s/%s] %s: %s %s' % (test.test_count, test.group_total,
diff --git a/atest/result_reporter_unittest.py b/atest/result_reporter_unittest.py
index 6b14932..3468a48 100755
--- a/atest/result_reporter_unittest.py
+++ b/atest/result_reporter_unittest.py
@@ -32,7 +32,7 @@
test_time='(10ms)',
runner_total=None,
group_total=2,
- perf_info={}
+ additional_info={}
)
RESULT_PASSED_TEST_MODULE_2 = test_runner_base.TestResult(
@@ -45,7 +45,7 @@
test_time='(10ms)',
runner_total=None,
group_total=2,
- perf_info={}
+ additional_info={}
)
RESULT_PASSED_TEST_RUNNER_2_NO_MODULE = test_runner_base.TestResult(
@@ -58,7 +58,7 @@
test_time='(10ms)',
runner_total=None,
group_total=2,
- perf_info={}
+ additional_info={}
)
RESULT_FAILED_TEST = test_runner_base.TestResult(
@@ -71,7 +71,7 @@
test_time='',
runner_total=None,
group_total=2,
- perf_info={}
+ additional_info={}
)
RESULT_RUN_FAILURE = test_runner_base.TestResult(
@@ -84,7 +84,7 @@
test_time='',
runner_total=None,
group_total=2,
- perf_info={}
+ additional_info={}
)
RESULT_INVOCATION_FAILURE = test_runner_base.TestResult(
@@ -97,7 +97,7 @@
test_time='',
runner_total=None,
group_total=None,
- perf_info={}
+ additional_info={}
)
RESULT_IGNORED_TEST = test_runner_base.TestResult(
@@ -110,7 +110,7 @@
test_time='(10ms)',
runner_total=None,
group_total=2,
- perf_info={}
+ additional_info={}
)
RESULT_ASSUMPTION_FAILED_TEST = test_runner_base.TestResult(
@@ -123,7 +123,7 @@
test_time='(10ms)',
runner_total=None,
group_total=2,
- perf_info={}
+ additional_info={}
)
#pylint: disable=protected-access
diff --git a/atest/test_finders/module_finder.py b/atest/test_finders/module_finder.py
index e3767e4..6c3ecd6 100644
--- a/atest/test_finders/module_finder.py
+++ b/atest/test_finders/module_finder.py
@@ -212,13 +212,12 @@
return test_config
return rel_config
- def _get_test_info_filter(self, path, methods, module_name, **kwargs):
+ def _get_test_info_filter(self, path, methods, **kwargs):
"""Get test info filter.
Args:
path: A string of the test's path.
methods: A set of method name strings.
- module_name: A string of the module name.
rel_module_dir: Optional. A string of the module dir relative to
root.
class_name: Optional. A string of the class name.
@@ -251,7 +250,6 @@
kwargs.get('class_name', '*'), methods), frozenset())])
# Path to non-module dir, treat as package.
elif (not file_name
- and not self.module_info.is_auto_gen_test_config(module_name)
and kwargs.get('rel_module_dir', None) !=
os.path.relpath(path, self.root_dir)):
dir_items = [os.path.join(path, f) for f in os.listdir(path)]
@@ -371,7 +369,7 @@
if not test_path:
return None
test_filter = self._get_test_info_filter(
- test_path, methods, module_name, class_name=class_name,
+ test_path, methods, class_name=class_name,
is_native_test=is_native_test)
tinfo = self._get_test_info(test_path, rel_config, module_name,
test_filter)
@@ -488,7 +486,7 @@
if not rel_module_dir:
return None
rel_config = os.path.join(rel_module_dir, constants.MODULE_CONFIG)
- test_filter = self._get_test_info_filter(path, methods, None,
+ test_filter = self._get_test_info_filter(path, methods,
rel_module_dir=rel_module_dir)
return self._get_test_info(path, rel_config, None, test_filter)
diff --git a/atest/test_runners/event_handler.py b/atest/test_runners/event_handler.py
index e1523a2..a5d00f3 100644
--- a/atest/test_runners/event_handler.py
+++ b/atest/test_runners/event_handler.py
@@ -124,7 +124,7 @@
test_time='',
runner_total=None,
group_total=self.state['current_group_total'],
- perf_info={}))
+ additional_info={}))
def _invocation_failed(self, event_data):
# Broadest possible failure. May not even start the module/test run.
@@ -138,7 +138,7 @@
test_time='',
runner_total=None,
group_total=self.state['current_group_total'],
- perf_info={}))
+ additional_info={}))
def _run_ended(self, event_data):
pass
@@ -170,14 +170,11 @@
status = test_runner_base.PASSED_STATUS
trace = None
- cpu_time = event_data.get('cpu_time', None)
- real_time = event_data.get('real_time', None)
- iterations = event_data.get('iterations', None)
- perf_info = {}
- if (cpu_time and real_time and iterations):
- perf_info['cpu_time'] = cpu_time
- perf_info['real_time'] = real_time
- perf_info['iterations'] = iterations
+ default_event_keys = ['className', 'end_time', 'testName']
+ additional_info = {}
+ for event_key in event_data.keys():
+ if event_key not in default_event_keys:
+ additional_info[event_key] = event_data.get(event_key, None)
self.reporter.process_test_result(test_runner_base.TestResult(
runner_name=self.runner_name,
@@ -188,7 +185,7 @@
test_count=self.state['test_count'],
test_time=test_time,
runner_total=None,
- perf_info=perf_info,
+ additional_info=additional_info,
group_total=self.state['current_group_total']))
def _log_association(self, event_data):
@@ -253,7 +250,7 @@
test_time='',
runner_total=None,
group_total=self.state['current_group_total'],
- perf_info={}))
+ additional_info={}))
raise EventHandleError(EVENTS_NOT_BALANCED % (start_event,
event_name))
diff --git a/atest/test_runners/event_handler_unittest.py b/atest/test_runners/event_handler_unittest.py
index e10cee2..c1994eb 100755
--- a/atest/test_runners/event_handler_unittest.py
+++ b/atest/test_runners/event_handler_unittest.py
@@ -107,6 +107,11 @@
('TEST_ENDED', {'end_time':9876450, 'className':'someClassName2',
'testName':'someTestName2', 'cpu_time':'1234.1234(ns)',
'real_time':'5678.5678(ns)', 'iterations':'6666'}),
+ ('TEST_STARTED', {'start_time':10, 'className':'someClassName3',
+ 'testName':'someTestName3'}),
+ ('TEST_ENDED', {'end_time':70, 'className':'someClassName3',
+ 'testName':'someTestName3', 'additional_info_min':'102773',
+ 'additional_info_mean':'105973', 'additional_info_median':'103778'}),
('TEST_RUN_ENDED', {}),
('TEST_MODULE_ENDED', {'foo': 'bar'}),
]
@@ -137,7 +142,7 @@
test_time='(996ms)',
runner_total=None,
group_total=2,
- perf_info={}
+ additional_info={}
))
call2 = mock.call(test_runner_base.TestResult(
runner_name=atf_tr.AtestTradefedTestRunner.NAME,
@@ -149,7 +154,7 @@
test_time='(2h44m36.402s)',
runner_total=None,
group_total=2,
- perf_info={}
+ additional_info={}
))
self.mock_reporter.process_test_result.assert_has_calls([call1, call2])
@@ -167,7 +172,7 @@
test_time='',
runner_total=None,
group_total=2,
- perf_info={}
+ additional_info={}
))
self.mock_reporter.process_test_result.assert_has_calls([call])
@@ -185,7 +190,7 @@
test_time='',
runner_total=None,
group_total=None,
- perf_info={}
+ additional_info={}
))
self.mock_reporter.process_test_result.assert_has_calls([call])
@@ -204,7 +209,7 @@
test_time='(8ms)',
runner_total=None,
group_total=2,
- perf_info={}
+ additional_info={}
))
self.mock_reporter.process_test_result.assert_has_calls([call])
# Event pair: TEST_STARTED -> TEST_RUN_ENDED
@@ -236,7 +241,7 @@
test_time='(10ms)',
runner_total=None,
group_total=2,
- perf_info={}
+ additional_info={}
))
call2 = mock.call(test_runner_base.TestResult(
runner_name=atf_tr.AtestTradefedTestRunner.NAME,
@@ -248,11 +253,11 @@
test_time='(62ms)',
runner_total=None,
group_total=2,
- perf_info={}
+ additional_info={}
))
self.mock_reporter.process_test_result.assert_has_calls([call1, call2])
- def test_process_event_with_perf_info(self):
+ def test_process_event_with_additional_info(self):
"""Test process_event method with perf information."""
for name, data in EVENTS_WITH_PERF_INFO:
self.fake_eh.process_event(name, data)
@@ -266,11 +271,11 @@
test_time='(996ms)',
runner_total=None,
group_total=2,
- perf_info={}
+ additional_info={}
))
- test_perf_info = {'cpu_time':'1234.1234(ns)', 'real_time':'5678.5678(ns)',
- 'iterations':'6666'}
+ test_additional_info = {'cpu_time':'1234.1234(ns)', 'real_time':'5678.5678(ns)',
+ 'iterations':'6666'}
call2 = mock.call(test_runner_base.TestResult(
runner_name=atf_tr.AtestTradefedTestRunner.NAME,
group_name='someTestModule',
@@ -281,9 +286,25 @@
test_time='(2h44m36.402s)',
runner_total=None,
group_total=2,
- perf_info=test_perf_info
+ additional_info=test_additional_info
))
- self.mock_reporter.process_test_result.assert_has_calls([call1, call2])
+
+ test_additional_info2 = {'additional_info_min':'102773',
+ 'additional_info_mean':'105973',
+ 'additional_info_median':'103778'}
+ call3 = mock.call(test_runner_base.TestResult(
+ runner_name=atf_tr.AtestTradefedTestRunner.NAME,
+ group_name='someTestModule',
+ test_name='someClassName3#someTestName3',
+ status=test_runner_base.PASSED_STATUS,
+ details=None,
+ test_count=3,
+ test_time='(60ms)',
+ runner_total=None,
+ group_total=2,
+ additional_info=test_additional_info2
+ ))
+ self.mock_reporter.process_test_result.assert_has_calls([call1, call2, call3])
if __name__ == '__main__':
unittest.main()
diff --git a/atest/test_runners/robolectric_test_runner.py b/atest/test_runners/robolectric_test_runner.py
index e2de7f5..dacbc62 100644
--- a/atest/test_runners/robolectric_test_runner.py
+++ b/atest/test_runners/robolectric_test_runner.py
@@ -56,7 +56,11 @@
def __init__(self, results_dir, **kwargs):
"""Init stuff for robolectric runner class."""
super(RobolectricTestRunner, self).__init__(results_dir, **kwargs)
- self.is_verbose = logging.getLogger().isEnabledFor(logging.DEBUG)
+ # TODO: Rollback when b/131301211 is fixed.
+ if not os.getenv(test_runner_base.OLD_OUTPUT_ENV_VAR):
+ self.is_verbose = True
+ else:
+ self.is_verbose = logging.getLogger().isEnabledFor(logging.DEBUG)
def run_tests(self, test_infos, extra_args, reporter):
"""Run the list of test_infos. See base class for more.
@@ -69,9 +73,10 @@
Returns:
0 if tests succeed, non-zero otherwise.
"""
+ # TODO: Rollback when b/131301211 is fixed.
if os.getenv(test_runner_base.OLD_OUTPUT_ENV_VAR):
- return self.run_tests_raw(test_infos, extra_args, reporter)
- return self.run_tests_pretty(test_infos, extra_args, reporter)
+ return self.run_tests_pretty(test_infos, extra_args, reporter)
+ return self.run_tests_raw(test_infos, extra_args, reporter)
def run_tests_raw(self, test_infos, extra_args, reporter):
"""Run the list of test_infos with raw output.
diff --git a/atest/test_runners/test_runner_base.py b/atest/test_runners/test_runner_base.py
index fc2ab0c..562c32d 100644
--- a/atest/test_runners/test_runner_base.py
+++ b/atest/test_runners/test_runner_base.py
@@ -41,7 +41,7 @@
'test_name', 'status', 'details',
'test_count', 'test_time',
'runner_total', 'group_total',
- 'perf_info'])
+ 'additional_info'])
ASSUMPTION_FAILED = 'ASSUMPTION_FAILED'
FAILED_STATUS = 'FAILED'
PASSED_STATUS = 'PASSED'
diff --git a/src/com/android/tradefed/device/IDeviceStateMonitor.java b/src/com/android/tradefed/device/IDeviceStateMonitor.java
index a305ce3..7a166cf 100644
--- a/src/com/android/tradefed/device/IDeviceStateMonitor.java
+++ b/src/com/android/tradefed/device/IDeviceStateMonitor.java
@@ -116,6 +116,14 @@
public boolean waitForDeviceInRecovery(long waitTime);
/**
+ * Waits for the device to be in the 'adb sideload' state
+ *
+ * @param waitTime the maximum time in ms to wait
+ * @return True if the device is in sideload before the timeout, False otherwise.
+ */
+ public boolean waitForDeviceInSideload(long waitTime);
+
+ /**
* Gets the serial number of the device.
*/
public String getSerialNumber();
diff --git a/src/com/android/tradefed/device/INativeDevice.java b/src/com/android/tradefed/device/INativeDevice.java
index 99a1b04..172f0bd 100644
--- a/src/com/android/tradefed/device/INativeDevice.java
+++ b/src/com/android/tradefed/device/INativeDevice.java
@@ -391,6 +391,22 @@
public String executeAdbCommand(String... commandArgs) throws DeviceNotAvailableException;
/**
+ * Helper method which executes a adb command as a system command with a specified timeout.
+ *
+ * <p>{@link #executeShellCommand(String)} should be used instead wherever possible, as that
+ * method provides better failure detection and performance.
+ *
+ * @param timeout the time in milliseconds before the device is considered unresponsive, 0L for
+ * no timeout
+ * @param commandArgs the adb command and arguments to run
+ * @return the stdout from command. <code>null</code> if command failed to execute.
+ * @throws DeviceNotAvailableException if connection with device is lost and cannot be
+ * recovered.
+ */
+ public String executeAdbCommand(long timeout, String... commandArgs)
+ throws DeviceNotAvailableException;
+
+ /**
* Helper method which executes a fastboot command as a system command with a default timeout
* of 2 minutes.
* <p/>
@@ -880,6 +896,15 @@
public void rebootIntoRecovery() throws DeviceNotAvailableException;
/**
+ * Reboots the device into adb sideload mode (note that this is a special mode under recovery)
+ *
+ * <p>Blocks until device enters sideload mode
+ *
+ * @throws DeviceNotAvailableException if device is not in sideload after reboot
+ */
+ public void rebootIntoSideload() throws DeviceNotAvailableException;
+
+ /**
* An alternate to {@link #reboot()} that only blocks until device is online ie visible to adb.
*
* @throws DeviceNotAvailableException if device is not available after reboot
@@ -1051,6 +1076,15 @@
public boolean waitForDeviceInRecovery(final long waitTime);
/**
+ * Blocks for the device to be in the 'adb sideload' state
+ *
+ * @param waitTime the time in ms to wait
+ * @return <code>true</code> if device boots into sideload before time expires. <code>false
+ * </code> otherwise
+ */
+ public boolean waitForDeviceInSideload(final long waitTime);
+
+ /**
* Waits for device to be responsive to a basic adb shell command.
*
* @param waitTime the time in ms to wait
diff --git a/src/com/android/tradefed/device/NativeDevice.java b/src/com/android/tradefed/device/NativeDevice.java
index a1d307a..f4c8f37 100644
--- a/src/com/android/tradefed/device/NativeDevice.java
+++ b/src/com/android/tradefed/device/NativeDevice.java
@@ -250,14 +250,20 @@
/** the output from the command */
String mOutput = null;
private String[] mCmd;
+ private long mTimeout;
AdbAction(String[] cmd) {
+ this(getCommandTimeout(), cmd);
+ }
+
+ AdbAction(long timeout, String[] cmd) {
+ mTimeout = timeout;
mCmd = cmd;
}
@Override
public boolean run() throws TimeoutException, IOException {
- CommandResult result = getRunUtil().runTimedCmd(getCommandTimeout(), mCmd);
+ CommandResult result = getRunUtil().runTimedCmd(mTimeout, mCmd);
// TODO: how to determine device not present with command failing for other reasons
if (result.getStatus() == CommandStatus.EXCEPTION) {
throw new IOException();
@@ -1777,13 +1783,19 @@
*/
@Override
public String executeAdbCommand(String... cmdArgs) throws DeviceNotAvailableException {
+ return executeAdbCommand(getCommandTimeout(), cmdArgs);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String executeAdbCommand(long timeout, String... cmdArgs)
+ throws DeviceNotAvailableException {
final String[] fullCmd = buildAdbCommand(cmdArgs);
- AdbAction adbAction = new AdbAction(fullCmd);
+ AdbAction adbAction = new AdbAction(timeout, fullCmd);
performDeviceAction(String.format("adb %s", cmdArgs[0]), adbAction, MAX_RETRY_ATTEMPTS);
return adbAction.mOutput;
}
-
/**
* {@inheritDoc}
*/
@@ -2913,6 +2925,24 @@
}
}
+
+ /** {@inheritDoc} */
+ @Override
+ public void rebootIntoSideload() throws DeviceNotAvailableException {
+ if (TestDeviceState.FASTBOOT == getDeviceState()) {
+ CLog.w(
+ "device %s in fastboot when requesting boot to sideload. "
+ + "Rebooting to userspace first.",
+ getSerialNumber());
+ rebootUntilOnline();
+ }
+ doAdbReboot("sideload");
+ if (!waitForDeviceInSideload(mOptions.getAdbRecoveryTimeout())) {
+ // using recovery mode because sideload is a sub-mode under recovery
+ recoverDeviceInRecovery();
+ }
+ }
+
/**
* {@inheritDoc}
*/
@@ -3388,6 +3418,12 @@
return mStateMonitor.waitForDeviceInRecovery(waitTime);
}
+ /** {@inheritDoc} */
+ @Override
+ public boolean waitForDeviceInSideload(long waitTime) {
+ return mStateMonitor.waitForDeviceInSideload(waitTime);
+ }
+
/**
* Small helper function to throw an NPE if the passed arg is null. This should be used when
* some value will be stored and used later, in which case it'll avoid hard-to-trace
diff --git a/src/com/android/tradefed/device/NativeDeviceStateMonitor.java b/src/com/android/tradefed/device/NativeDeviceStateMonitor.java
index a107c71..43f48eb 100644
--- a/src/com/android/tradefed/device/NativeDeviceStateMonitor.java
+++ b/src/com/android/tradefed/device/NativeDeviceStateMonitor.java
@@ -154,6 +154,12 @@
return waitForDeviceState(TestDeviceState.RECOVERY, waitTime);
}
+ /** {@inheritDoc} */
+ @Override
+ public boolean waitForDeviceInSideload(long waitTime) {
+ return waitForDeviceState(TestDeviceState.SIDELOAD, waitTime);
+ }
+
/**
* {@inheritDoc}
*/
diff --git a/src/com/android/tradefed/device/TestDevice.java b/src/com/android/tradefed/device/TestDevice.java
index c82a8bd..8df4860 100644
--- a/src/com/android/tradefed/device/TestDevice.java
+++ b/src/com/android/tradefed/device/TestDevice.java
@@ -901,7 +901,7 @@
*/
@Override
protected void doAdbReboot(final String into) throws DeviceNotAvailableException {
- if (!doAdbFrameworkReboot(into)) {
+ if (!TestDeviceState.ONLINE.equals(getDeviceState()) || !doAdbFrameworkReboot(into)) {
super.doAdbReboot(into);
}
}
diff --git a/src/com/android/tradefed/device/TestDeviceState.java b/src/com/android/tradefed/device/TestDeviceState.java
index de159d9..33512e2 100644
--- a/src/com/android/tradefed/device/TestDeviceState.java
+++ b/src/com/android/tradefed/device/TestDeviceState.java
@@ -27,6 +27,7 @@
FASTBOOT,
ONLINE,
RECOVERY,
+ SIDELOAD,
NOT_AVAILABLE;
/**
@@ -39,6 +40,8 @@
return DeviceState.ONLINE;
case RECOVERY:
return DeviceState.RECOVERY;
+ case SIDELOAD:
+ return DeviceState.SIDELOAD;
default:
return null;
}
@@ -59,6 +62,8 @@
return TestDeviceState.NOT_AVAILABLE;
case RECOVERY:
return TestDeviceState.RECOVERY;
+ case SIDELOAD:
+ return TestDeviceState.SIDELOAD;
case UNAUTHORIZED:
return TestDeviceState.NOT_AVAILABLE;
case BOOTLOADER:
diff --git a/src/com/android/tradefed/device/cloud/NestedRemoteDevice.java b/src/com/android/tradefed/device/cloud/NestedRemoteDevice.java
index 82de51c..ebf577e 100644
--- a/src/com/android/tradefed/device/cloud/NestedRemoteDevice.java
+++ b/src/com/android/tradefed/device/cloud/NestedRemoteDevice.java
@@ -31,10 +31,12 @@
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;
@@ -180,6 +182,13 @@
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);
+ }
try (InputStreamSource source = new FileInputStreamSource(launcherLog)) {
logger.testLog(logName, LogDataType.TEXT, source);
}
diff --git a/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDevice.java b/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDevice.java
index d136ab3..4ba6cfc 100644
--- a/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDevice.java
+++ b/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDevice.java
@@ -43,6 +43,8 @@
import java.io.File;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
/**
@@ -52,11 +54,6 @@
*/
public class RemoteAndroidVirtualDevice extends RemoteAndroidDevice implements ITestLoggerReceiver {
- /** The directory where to find debug logs for a nested remote instance. */
- public static final String NESTED_REMOTE_LOG_DIR = "${HOME}/../vsoc-01/cuttlefish_runtime/";
- /** The directory where to find debug logs for an emulator instance. */
- public static final String EMULATOR_REMOTE_LOG_DIR = "/home/vsoc-01/log/";
-
private String mInitialSerial;
private GceAvdInfo mGceAvd;
private ITestLogger mTestLogger;
@@ -69,6 +66,8 @@
private static final long WAIT_FOR_TUNNEL_OFFLINE = 5 * 1000;
private static final int WAIT_TIME_DIVISION = 4;
+ private static final long FETCH_TOMBSTONES_TIMEOUT_MS = 5 * 60 * 1000;
+
/**
* Creates a {@link RemoteAndroidVirtualDevice}.
*
@@ -347,6 +346,39 @@
}
/**
+ * Cuttlefish has a special feature that brings the tombstones to the remote host where we can
+ * get them directly.
+ */
+ @Override
+ public List<File> getTombstones() throws DeviceNotAvailableException {
+ InstanceType type = getOptions().getInstanceType();
+ if (InstanceType.CUTTLEFISH.equals(type) || InstanceType.REMOTE_NESTED_AVD.equals(type)) {
+ List<File> tombs = new ArrayList<>();
+ String remoteRuntimePath =
+ String.format(
+ CommonLogRemoteFileUtil.NESTED_REMOTE_LOG_DIR,
+ getOptions().getInstanceUser())
+ + "tombstones";
+ File tombstonesDir =
+ RemoteFileUtil.fetchRemoteDir(
+ mGceAvd,
+ getOptions(),
+ getRunUtil(),
+ FETCH_TOMBSTONES_TIMEOUT_MS,
+ remoteRuntimePath);
+ if (tombstonesDir == null) {
+ CLog.e("Pulled tombstone dir was not valid. Path: %s", tombstonesDir);
+ } else {
+ // TODO: If possible delete the tombstones parent temp dir.
+ tombs.addAll(Arrays.asList(tombstonesDir.listFiles()));
+ }
+ return tombs;
+ }
+ // If it's not Cuttlefish, use the standard call.
+ return super.getTombstones();
+ }
+
+ /**
* Returns the {@link com.android.tradefed.device.cloud.GceSshTunnelMonitor} of the device.
* Exposed for testing.
*/
diff --git a/src/com/android/tradefed/device/cloud/RemoteFileUtil.java b/src/com/android/tradefed/device/cloud/RemoteFileUtil.java
index 21d4e1d..5330992 100644
--- a/src/com/android/tradefed/device/cloud/RemoteFileUtil.java
+++ b/src/com/android/tradefed/device/cloud/RemoteFileUtil.java
@@ -25,6 +25,7 @@
import java.io.File;
import java.io.IOException;
+import java.util.Arrays;
import java.util.List;
/** Utility class to handle file from a remote instance */
@@ -39,7 +40,7 @@
* @param runUtil a {@link IRunUtil} to execute commands.
* @param timeout in millisecond for the fetch to complete
* @param remoteFilePath The remote path where to find the file.
- * @return True if successful, False otherwise
+ * @return The pulled filed if successful, null otherwise
*/
public static File fetchRemoteFile(
GceAvdInfo remoteInstance,
@@ -95,6 +96,45 @@
}
/**
+ * Fetch a remote directory from the remote host.
+ *
+ * @param remoteInstance The {@link GceAvdInfo} that describe the device.
+ * @param options a {@link TestDeviceOptions} describing the device options to be used for the
+ * GCE device.
+ * @param runUtil a {@link IRunUtil} to execute commands.
+ * @param timeout in millisecond for the fetch to complete
+ * @param remoteDirPath The remote path where to find the directory.
+ * @return The pulled directory {@link File} if successful, null otherwise
+ */
+ public static File fetchRemoteDir(
+ GceAvdInfo remoteInstance,
+ TestDeviceOptions options,
+ IRunUtil runUtil,
+ long timeout,
+ String remoteDirPath) {
+ String dirName = new File(remoteDirPath).getName();
+ File localFile = null;
+ try {
+ localFile = FileUtil.createTempDir(dirName);
+ if (internalScpExec(
+ remoteInstance,
+ options,
+ Arrays.asList("-r"),
+ runUtil,
+ timeout,
+ remoteDirPath,
+ localFile,
+ ScpMode.PULL)) {
+ return localFile;
+ }
+ } catch (IOException e) {
+ CLog.e(e);
+ }
+ FileUtil.deleteFile(localFile);
+ return null;
+ }
+
+ /**
* Push a {@link File} from the local host to the remote instance
*
* @param remoteInstance The {@link GceAvdInfo} that describe the device.
diff --git a/src/com/android/tradefed/device/contentprovider/ContentProviderHandler.java b/src/com/android/tradefed/device/contentprovider/ContentProviderHandler.java
index f88a403..b6babf9 100644
--- a/src/com/android/tradefed/device/contentprovider/ContentProviderHandler.java
+++ b/src/com/android/tradefed/device/contentprovider/ContentProviderHandler.java
@@ -167,53 +167,7 @@
*/
public boolean pullDir(String deviceFilePath, File localDir)
throws DeviceNotAvailableException {
- if (!localDir.isDirectory()) {
- CLog.e("Local path %s is not a directory", localDir.getAbsolutePath());
- return false;
- }
-
- String contentUri = createEscapedContentUri(deviceFilePath);
- String queryContentCommand =
- String.format(
- "content query --user %d --uri %s", mDevice.getCurrentUser(), contentUri);
-
- String listCommandResult = mDevice.executeShellCommand(queryContentCommand);
-
- if (listCommandResult.equals(NO_RESULTS_STRING)) {
- // Empty directory.
- return true;
- }
-
- String[] listResult = listCommandResult.split("[\\r\\n]+");
-
- for (String row : listResult) {
- HashMap<String, String> columnValues = parseQueryResultRow(row);
- boolean isDirectory = Boolean.valueOf(columnValues.get(COLUMN_DIRECTORY));
- String name = columnValues.get(COLUMN_NAME);
- String path = columnValues.get(COLUMN_ABSOLUTE_PATH);
-
- File localChild = new File(localDir, name);
- if (isDirectory) {
- if (!localChild.mkdir()) {
- CLog.w(
- "Failed to create sub directory %s, aborting.",
- localChild.getAbsolutePath());
- return false;
- }
-
- if (!pullDir(path, localChild)) {
- CLog.w("Failed to pull sub directory %s from device, aborting", path);
- return false;
- }
- } else {
- // handle regular file
- if (!pullFile(path, localChild)) {
- CLog.w("Failed to pull file %s from device, aborting", path);
- return false;
- }
- }
- }
- return true;
+ return pullDirInternal(deviceFilePath, localDir, /* currentUser */ null);
}
/**
@@ -227,33 +181,7 @@
*/
public boolean pullFile(String deviceFilePath, File localFile)
throws DeviceNotAvailableException {
- String contentUri = createEscapedContentUri(deviceFilePath);
- String pullCommand =
- String.format(
- "content read --user %d --uri %s", mDevice.getCurrentUser(), contentUri);
-
- // Open the output stream to the local file.
- OutputStream localFileStream;
- try {
- localFileStream = new FileOutputStream(localFile);
- } catch (FileNotFoundException e) {
- CLog.e("Failed to open OutputStream to the local file. Error: %s", e.getMessage());
- return false;
- }
-
- try {
- CommandResult pullResult = mDevice.executeShellV2Command(pullCommand, localFileStream);
- if (isSuccessful(pullResult)) {
- return true;
- }
-
- CLog.e(
- "Failed to pull a file at '%s' to %s using content provider. Error: '%s'",
- deviceFilePath, localFile, pullResult.getStderr());
- return false;
- } finally {
- StreamUtil.close(localFileStream);
- }
+ return pullFileInternal(deviceFilePath, localFile, /* currentUser */ null);
}
/**
@@ -357,4 +285,95 @@
}
return columnValues;
}
+
+ /**
+ * Internal method to actually do the pull directory but without re-querying the current user
+ * while doing the recursive pull.
+ */
+ private boolean pullDirInternal(String deviceFilePath, File localDir, Integer currentUser)
+ throws DeviceNotAvailableException {
+ if (!localDir.isDirectory()) {
+ CLog.e("Local path %s is not a directory", localDir.getAbsolutePath());
+ return false;
+ }
+
+ String contentUri = createEscapedContentUri(deviceFilePath);
+ if (currentUser == null) {
+ // Keep track of the user so if we recursively pull dir we don't re-query it.
+ currentUser = mDevice.getCurrentUser();
+ }
+ String queryContentCommand =
+ String.format("content query --user %d --uri %s", currentUser, contentUri);
+
+ String listCommandResult = mDevice.executeShellCommand(queryContentCommand);
+
+ if (NO_RESULTS_STRING.equals(listCommandResult.trim())) {
+ // Empty directory.
+ return true;
+ }
+
+ String[] listResult = listCommandResult.split("[\\r\\n]+");
+
+ for (String row : listResult) {
+ HashMap<String, String> columnValues = parseQueryResultRow(row);
+ boolean isDirectory = Boolean.valueOf(columnValues.get(COLUMN_DIRECTORY));
+ String name = columnValues.get(COLUMN_NAME);
+ String path = columnValues.get(COLUMN_ABSOLUTE_PATH);
+
+ File localChild = new File(localDir, name);
+ if (isDirectory) {
+ if (!localChild.mkdir()) {
+ CLog.w(
+ "Failed to create sub directory %s, aborting.",
+ localChild.getAbsolutePath());
+ return false;
+ }
+
+ if (!pullDirInternal(path, localChild, currentUser)) {
+ CLog.w("Failed to pull sub directory %s from device, aborting", path);
+ return false;
+ }
+ } else {
+ // handle regular file
+ if (!pullFileInternal(path, localChild, currentUser)) {
+ CLog.w("Failed to pull file %s from device, aborting", path);
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ private boolean pullFileInternal(String deviceFilePath, File localFile, Integer currentUser)
+ throws DeviceNotAvailableException {
+ String contentUri = createEscapedContentUri(deviceFilePath);
+ if (currentUser == null) {
+ currentUser = mDevice.getCurrentUser();
+ }
+ String pullCommand =
+ String.format("content read --user %d --uri %s", currentUser, contentUri);
+
+ // Open the output stream to the local file.
+ OutputStream localFileStream;
+ try {
+ localFileStream = new FileOutputStream(localFile);
+ } catch (FileNotFoundException e) {
+ CLog.e("Failed to open OutputStream to the local file. Error: %s", e.getMessage());
+ return false;
+ }
+
+ try {
+ CommandResult pullResult = mDevice.executeShellV2Command(pullCommand, localFileStream);
+ if (isSuccessful(pullResult)) {
+ return true;
+ }
+
+ CLog.e(
+ "Failed to pull a file at '%s' to %s using content provider. Error: '%s'",
+ deviceFilePath, localFile, pullResult.getStderr());
+ return false;
+ } finally {
+ StreamUtil.close(localFileStream);
+ }
+ }
}
diff --git a/src/com/android/tradefed/device/metric/HostStatsdMetricCollector.java b/src/com/android/tradefed/device/metric/HostStatsdMetricCollector.java
index 70ea199..4f2e029 100644
--- a/src/com/android/tradefed/device/metric/HostStatsdMetricCollector.java
+++ b/src/com/android/tradefed/device/metric/HostStatsdMetricCollector.java
@@ -16,6 +16,7 @@
package com.android.tradefed.device.metric;
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;
@@ -36,6 +37,7 @@
* commands. It has basic push metrics and dump report functions. It can be extended by subclasses
* to process statsd metric report based on the needs.
*/
+@OptionClass(alias = "host-statsd-collector")
public class HostStatsdMetricCollector extends BaseDeviceMetricCollector {
@Option(name = "binary-stats-config", description = "Path to the binary Statsd config file")
diff --git a/src/com/android/tradefed/device/metric/LogcatOnFailureCollector.java b/src/com/android/tradefed/device/metric/LogcatOnFailureCollector.java
index 33be39b..3dcc717 100644
--- a/src/com/android/tradefed/device/metric/LogcatOnFailureCollector.java
+++ b/src/com/android/tradefed/device/metric/LogcatOnFailureCollector.java
@@ -82,7 +82,9 @@
@VisibleForTesting
ILogcatReceiver createLogcatReceiver(ITestDevice device) {
- return new LogcatReceiver(device, "logcat", device.getOptions().getMaxLogcatDataSize(), 0);
+ // 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);
}
@VisibleForTesting
diff --git a/src/com/android/tradefed/result/proto/FileProtoResultReporter.java b/src/com/android/tradefed/result/proto/FileProtoResultReporter.java
index f72abf4..3bb8f03 100644
--- a/src/com/android/tradefed/result/proto/FileProtoResultReporter.java
+++ b/src/com/android/tradefed/result/proto/FileProtoResultReporter.java
@@ -16,6 +16,7 @@
package com.android.tradefed.result.proto;
import com.android.tradefed.config.Option;
+import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.proto.TestRecordProto.TestRecord;
@@ -33,18 +34,74 @@
)
private File mOutputFile;
+ @Option(
+ name = "periodic-proto-writing",
+ description =
+ "Whether or not to output intermediate proto per module following a numbered "
+ + "sequence."
+ )
+ private boolean mPeriodicWriting = false;
+
+ // Current index of the sequence of proto output
+ private int mIndex = 0;
+ private File mCurrentOutputFile;
+
+ @Override
+ public void processStartInvocation(
+ TestRecord invocationStartRecord, IInvocationContext invocationContext) {
+ writeProto(invocationStartRecord);
+ }
+
+ @Override
+ public void processTestModuleEnd(TestRecord moduleRecord) {
+ writeProto(moduleRecord);
+ }
+
+ @Override
+ public void processTestRunEnded(TestRecord runRecord, boolean moduleInProgress) {
+ if (!moduleInProgress) {
+ // If it's a testRun outside of the module scope, output it to ensure we support
+ // non-module use cases.
+ writeProto(runRecord);
+ }
+ }
+
@Override
public void processFinalProto(TestRecord finalRecord) {
+ writeProto(finalRecord);
+ }
+
+ /** Sets the file where to output the result. */
+ public void setFileOutput(File output) {
+ mOutputFile = output;
+ if (mPeriodicWriting) {
+ mCurrentOutputFile = new File(mOutputFile.getAbsolutePath() + mIndex);
+ }
+ }
+
+ /** Enable writing each module individualy to a file. */
+ public void setPeriodicWriting(boolean enabled) {
+ mPeriodicWriting = enabled;
+ }
+
+ private void writeProto(TestRecord record) {
+ File outputFile = mOutputFile;
+ if (mPeriodicWriting) {
+ outputFile = mCurrentOutputFile;
+ }
try {
- finalRecord.writeDelimitedTo(new FileOutputStream(mOutputFile));
+ record.writeDelimitedTo(new FileOutputStream(outputFile));
+ if (mPeriodicWriting) {
+ nextOutputFile();
+ }
} catch (IOException e) {
CLog.e(e);
throw new RuntimeException(e);
}
}
- /** Sets the file where to output the result. */
- public void setFileOutput(File output) {
- mOutputFile = output;
+ private void nextOutputFile() {
+ mIndex++;
+ mCurrentOutputFile = new File(mOutputFile.getAbsolutePath() + mIndex);
}
}
diff --git a/src/com/android/tradefed/result/proto/ProtoResultParser.java b/src/com/android/tradefed/result/proto/ProtoResultParser.java
index 8f5ecd6..50e4340 100644
--- a/src/com/android/tradefed/result/proto/ProtoResultParser.java
+++ b/src/com/android/tradefed/result/proto/ProtoResultParser.java
@@ -32,6 +32,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.proto.TestRecordProtoUtil;
import com.google.common.base.Strings;
import com.google.protobuf.Any;
@@ -39,6 +40,7 @@
import com.google.protobuf.Timestamp;
import java.io.File;
+import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
@@ -62,6 +64,8 @@
private boolean mQuietParsing = true;
+ private boolean mInvocationStarted = false;
+
/** Ctor. */
public ProtoResultParser(
ITestInvocationListener listener,
@@ -108,7 +112,7 @@
// Invocation Start
handleInvocationStart(finalProto);
- evalProto(finalProto.getChildrenList(), false);
+ evalChildrenProto(finalProto.getChildrenList(), false);
// Invocation End
handleInvocationEnded(finalProto);
}
@@ -140,33 +144,56 @@
}
}
- private void evalProto(List<ChildReference> children, boolean isInRun) {
+ /**
+ * In case of parsing proto files directly, handle direct parsing of them as a sequence.
+ * Associated with {@link FileProtoResultReporter} when reporting a sequence of files.
+ *
+ * @param protoFile The proto file to be parsed.
+ * @throws IOException
+ */
+ public void processFileProto(File protoFile) throws IOException {
+ TestRecord record = TestRecordProtoUtil.readFromFile(protoFile);
+ if (!mInvocationStarted) {
+ handleInvocationStart(record);
+ mInvocationStarted = true;
+ } else if (record.getParentTestRecordId().isEmpty()) {
+ handleInvocationEnded(record);
+ } else {
+ evalProto(record, false);
+ }
+ }
+
+ private void evalChildrenProto(List<ChildReference> children, boolean isInRun) {
for (ChildReference child : children) {
TestRecord childProto = child.getInlineTestRecord();
- if (isInRun) {
- // test case
- String[] info = childProto.getTestRecordId().split("#");
- TestDescription description = new TestDescription(info[0], info[1]);
- mListener.testStarted(description, timeStampToMillis(childProto.getStartTime()));
- handleTestCaseEnd(description, childProto);
+ evalProto(childProto, isInRun);
+ }
+ }
+
+ private void evalProto(TestRecord childProto, boolean isInRun) {
+ if (isInRun) {
+ // test case
+ String[] info = childProto.getTestRecordId().split("#");
+ TestDescription description = new TestDescription(info[0], info[1]);
+ mListener.testStarted(description, timeStampToMillis(childProto.getStartTime()));
+ handleTestCaseEnd(description, childProto);
+ } else {
+ boolean inRun = false;
+ if (childProto.hasDescription()) {
+ // Module start
+ handleModuleStart(childProto);
} else {
- boolean inRun = false;
- if (childProto.hasDescription()) {
- // Module start
- handleModuleStart(childProto);
- } else {
- // run start
- handleTestRunStart(childProto);
- inRun = true;
- }
- evalProto(childProto.getChildrenList(), inRun);
- if (childProto.hasDescription()) {
- // Module end
- handleModuleProto(childProto);
- } else {
- // run end
- handleTestRunEnd(childProto);
- }
+ // run start
+ handleTestRunStart(childProto);
+ inRun = true;
+ }
+ evalChildrenProto(childProto.getChildrenList(), inRun);
+ if (childProto.hasDescription()) {
+ // Module end
+ handleModuleProto(childProto);
+ } else {
+ // run end
+ handleTestRunEnd(childProto);
}
}
}
diff --git a/src/com/android/tradefed/result/proto/ProtoResultReporter.java b/src/com/android/tradefed/result/proto/ProtoResultReporter.java
index 8fa6c35..227f518 100644
--- a/src/com/android/tradefed/result/proto/ProtoResultReporter.java
+++ b/src/com/android/tradefed/result/proto/ProtoResultReporter.java
@@ -100,8 +100,9 @@
* occurred.
*
* @param runRecord The finalized proto representing the run.
+ * @param moduleInProgress whether or not a module is in progress.
*/
- public void processTestRunEnded(TestRecord runRecord) {}
+ public void processTestRunEnded(TestRecord runRecord, boolean moduleInProgress) {}
/**
* Handling of the partial test case record proto after {@link #testStarted(TestDescription,
@@ -286,7 +287,7 @@
TestRecord runRecord = runBuilder.build();
parentBuilder.addChildren(createChildReference(runRecord));
try {
- processTestRunEnded(runRecord);
+ processTestRunEnded(runRecord, mModuleInProgress);
} catch (RuntimeException e) {
CLog.e("Failed to process test run end:");
CLog.e(e);
diff --git a/src/com/android/tradefed/result/proto/StreamProtoResultReporter.java b/src/com/android/tradefed/result/proto/StreamProtoResultReporter.java
index 2fd1b1b..46c0e47 100644
--- a/src/com/android/tradefed/result/proto/StreamProtoResultReporter.java
+++ b/src/com/android/tradefed/result/proto/StreamProtoResultReporter.java
@@ -59,7 +59,7 @@
}
@Override
- public void processTestRunEnded(TestRecord runRecord) {
+ public void processTestRunEnded(TestRecord runRecord, boolean moduleInProgress) {
writeRecordToSocket(runRecord);
}
diff --git a/src/com/android/tradefed/result/suite/XmlSuiteResultFormatter.java b/src/com/android/tradefed/result/suite/XmlSuiteResultFormatter.java
index 6544565..1aa9997 100644
--- a/src/com/android/tradefed/result/suite/XmlSuiteResultFormatter.java
+++ b/src/com/android/tradefed/result/suite/XmlSuiteResultFormatter.java
@@ -15,6 +15,7 @@
*/
package com.android.tradefed.result.suite;
+import com.android.annotations.VisibleForTesting;
import com.android.ddmlib.testrunner.TestResult.TestStatus;
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.invoker.InvocationContext;
@@ -53,6 +54,8 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
@@ -266,8 +269,9 @@
serializer.attribute(NS, MODULES_TOTAL_ATTR, Integer.toString(holder.totalModules));
serializer.endTag(NS, SUMMARY_TAG);
+ List<TestRunResult> sortedModuleList = sortModules(holder.runResults, holder.modulesAbi);
// Results
- for (TestRunResult module : holder.runResults) {
+ for (TestRunResult module : sortedModuleList) {
serializer.startTag(NS, MODULE_TAG);
// To be compatible of CTS strip the abi from the module name when available.
if (holder.modulesAbi.get(module.getName()) != null) {
@@ -521,6 +525,40 @@
return invocation;
}
+ /** Sort the list of results based on their name without abi primarily then secondly on abi. */
+ @VisibleForTesting
+ List<TestRunResult> sortModules(
+ Collection<TestRunResult> results, Map<String, IAbi> moduleAbis) {
+ List<TestRunResult> sortedList = new ArrayList<>(results);
+ Collections.sort(
+ sortedList,
+ new Comparator<TestRunResult>() {
+ @Override
+ public int compare(TestRunResult o1, TestRunResult o2) {
+ String module1NameStripped = o1.getName();
+ String module1Abi = "";
+ if (moduleAbis.get(module1NameStripped) != null) {
+ module1Abi = moduleAbis.get(module1NameStripped).getName();
+ module1NameStripped = module1NameStripped.replace(module1Abi + " ", "");
+ }
+
+ String module2NameStripped = o2.getName();
+ String module2Abi = "";
+ if (moduleAbis.get(module2NameStripped) != null) {
+ module2Abi = moduleAbis.get(module2NameStripped).getName();
+ module2NameStripped = module2NameStripped.replace(module2Abi + " ", "");
+ }
+ int res = module1NameStripped.compareTo(module2NameStripped);
+ if (res != 0) {
+ return res;
+ }
+ // Use the Abi as discriminant to always sort abi in the same order.
+ return module1Abi.compareTo(module2Abi);
+ }
+ });
+ return sortedList;
+ }
+
/** Handle the parsing and replay of all run history information. */
private void handleRunHistoryLevel(XmlPullParser parser)
throws IOException, XmlPullParserException {
diff --git a/src/com/android/tradefed/targetprep/SideloadOtaTargetPreparer.java b/src/com/android/tradefed/targetprep/SideloadOtaTargetPreparer.java
new file mode 100644
index 0000000..836c4a8
--- /dev/null
+++ b/src/com/android/tradefed/targetprep/SideloadOtaTargetPreparer.java
@@ -0,0 +1,83 @@
+/*
+ * 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.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 java.io.File;
+
+/**
+ * A target preparer that performs sideload of a specified OTA package, applies the package, waits
+ * for device to boot up, and injects the device build properties to use as build info
+ *
+ * <p>This target preparer assumes that the device will be in regular adb mode when started, and
+ * will ensure that the device exits in the same mode but with the newer build applied. Any
+ * unexpected device state transition during the process will be reported as {@link
+ * TargetSetupError}, and same applies to any OTA sideload error detected.
+ */
+@OptionClass(alias = "sideload-ota")
+public class SideloadOtaTargetPreparer extends DeviceBuildInfoInjector {
+
+ private static final String SIDELOAD_CMD = "sideload";
+ // the timeout for state transition from sideload finishes to recovery mode, not making this
+ // an option because it should be a transient state: 10s is already very generous
+ private static final long POST_SIDELOAD_TRANSITION_TIMEOUT = 10 * 1000;
+
+ @Option(name = "sideload-ota-package", description = "the OTA package to be sideloaded")
+ private File mSideloadOtaPackage = null;
+
+ @Option(
+ name = "sideload-timeout",
+ description = "timeout for sideloading the OTA package",
+ isTimeVal = true
+ )
+ // defaults to 10m: assuming USB 2.0 transfer speed, concurrency and some buffer
+ private long mSideloadTimeout = 10 * 60 * 1000;
+
+ @Option(
+ name = "inject-build-info",
+ description =
+ "whether build info should be injected "
+ + "based on device attributes after sideloading"
+ )
+ private boolean mInjectBuildInfo = true;
+
+ @Override
+ public void setUp(ITestDevice device, IBuildInfo buildInfo)
+ throws TargetSetupError, BuildError, DeviceNotAvailableException {
+ if (mSideloadOtaPackage == null) {
+ CLog.i("No sideload file provided, assuming no-op; skipping ...");
+ return;
+ }
+ device.rebootIntoSideload();
+ String filePath = mSideloadOtaPackage.getAbsolutePath();
+ CLog.d("Sideloading package from %s ...", filePath);
+ device.executeAdbCommand(mSideloadTimeout, SIDELOAD_CMD, filePath);
+ // after applying sideload, device should transition to recovery mode
+ device.waitForDeviceInRecovery(POST_SIDELOAD_TRANSITION_TIMEOUT);
+ // now reboot and wait for the device to become available
+ device.reboot();
+ // calling this last because we want to inject device side build info after device boots up
+ if (mInjectBuildInfo) {
+ super.setUp(device, buildInfo);
+ }
+ }
+}
diff --git a/src/com/android/tradefed/testtype/AndroidJUnitTest.java b/src/com/android/tradefed/testtype/AndroidJUnitTest.java
index bc70bb6..adee140 100644
--- a/src/com/android/tradefed/testtype/AndroidJUnitTest.java
+++ b/src/com/android/tradefed/testtype/AndroidJUnitTest.java
@@ -145,11 +145,14 @@
private Integer mMaxShard = null;
@Option(
- name = "device-listeners",
- description =
- "Specify a device side instrumentation listener to be added for the run. "
- + "Can be repeated.")
- private Set<String> mExtraDeviceListeners = new HashSet<>();
+ name = "device-listeners",
+ description =
+ "Specify device side instrumentation listeners to be added for the run. "
+ + "Can be repeated. Note that while the ordering here is followed for "
+ + "now, future versions of AndroidJUnitRunner might not preserve the "
+ + "listener ordering."
+ )
+ private List<String> mExtraDeviceListeners = new ArrayList<>();
@Option(
name = "use-new-run-listener-order",
@@ -329,6 +332,8 @@
mDeviceIncludeFile = mTestFilterDir.replaceAll("/$", "") + "/" + INCLUDE_FILE;
pushTestFile(mIncludeTestFile, mDeviceIncludeFile, listener);
pushedFile = true;
+ // If an explicit include file filter is provided, do not use the package
+ setTestPackageName(null);
}
// if mExcludeTestFile is set, perform filtering with this file
diff --git a/src/com/android/tradefed/testtype/GTest.java b/src/com/android/tradefed/testtype/GTest.java
index d81ec95..a3a4907 100644
--- a/src/com/android/tradefed/testtype/GTest.java
+++ b/src/com/android/tradefed/testtype/GTest.java
@@ -226,6 +226,26 @@
return sb.toString();
}
+ @Override
+ protected String createFlagFile(String filter) throws DeviceNotAvailableException {
+ String flagPath = super.createFlagFile(filter);
+ if (flagPath == null) {
+ // Return null to fall back to base filter
+ return null;
+ }
+ File flagFile = new File(flagPath);
+ String devicePath = "/data/local/tmp/" + flagFile.getName();
+ try {
+ if (!mDevice.pushFile(flagFile, devicePath)) {
+ // Failed to push flagfile, return null to fall back to base filter
+ return null;
+ }
+ } finally {
+ FileUtil.deleteFile(flagFile);
+ }
+ return devicePath;
+ }
+
/**
* Run the given gtest binary
*
diff --git a/src/com/android/tradefed/testtype/GTestBase.java b/src/com/android/tradefed/testtype/GTestBase.java
index a218aed..7766621 100644
--- a/src/com/android/tradefed/testtype/GTestBase.java
+++ b/src/com/android/tradefed/testtype/GTestBase.java
@@ -24,9 +24,12 @@
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.util.ArrayUtil;
+import com.android.tradefed.util.FileUtil;
import com.google.common.annotations.VisibleForTesting;
+import java.io.File;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
@@ -158,6 +161,7 @@
protected static final String GTEST_FLAG_FILTER = "--gtest_filter";
protected static final String GTEST_FLAG_RUN_DISABLED_TESTS = "--gtest_also_run_disabled_tests";
protected static final String GTEST_FLAG_LIST_TESTS = "--gtest_list_tests";
+ protected static final String GTEST_FLAG_FILE = "--gtest_flagfile";
protected static final String GTEST_XML_OUTPUT = "--gtest_output=xml:%s";
// Expected extension for the filter file associated with the binary (json formatted file)
@VisibleForTesting protected static final String FILTER_EXTENSION = ".filter";
@@ -399,7 +403,37 @@
}
}
}
- return filter.toString();
+ String filterFlag = filter.toString();
+ // Handle long args
+ if (filterFlag.length() > 500) {
+ String tmpFlag = createFlagFile(filterFlag);
+ if (tmpFlag != null) {
+ return String.format("%s=%s", GTEST_FLAG_FILE, tmpFlag);
+ }
+ }
+
+ return filterFlag;
+ }
+
+ /**
+ * Create a file containing the filters that will be used via --gtest_flagfile to avoid any OS
+ * limitation in args size.
+ *
+ * @param filter The filter string
+ * @return The path to the file containing the filter.
+ * @throws DeviceNotAvailableException
+ */
+ protected String createFlagFile(String filter) throws DeviceNotAvailableException {
+ File tmpFlagFile = null;
+ try {
+ tmpFlagFile = FileUtil.createTempFile("flagfile", ".txt");
+ FileUtil.writeToFile(filter, tmpFlagFile);
+ } catch (IOException e) {
+ FileUtil.deleteFile(tmpFlagFile);
+ CLog.e(e);
+ return null;
+ }
+ return tmpFlagFile.getAbsolutePath();
}
/**
diff --git a/src/com/android/tradefed/testtype/InstrumentationTest.java b/src/com/android/tradefed/testtype/InstrumentationTest.java
index 99f8441..f468fdc 100644
--- a/src/com/android/tradefed/testtype/InstrumentationTest.java
+++ b/src/com/android/tradefed/testtype/InstrumentationTest.java
@@ -56,7 +56,6 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@@ -298,7 +297,7 @@
private ListInstrumentationParser mListInstrumentationParser = null;
- private Set<String> mExtraDeviceListener = new HashSet<>();
+ private List<String> mExtraDeviceListener = new ArrayList<>();
private boolean mIsRerun = false;
@@ -635,7 +634,7 @@
}
/** Allows to add more custom listeners to the runner */
- public void addDeviceListeners(Set<String> extraListeners) {
+ public void addDeviceListeners(List<String> extraListeners) {
mExtraDeviceListener.addAll(extraListeners);
}
diff --git a/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java b/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java
index 946d878..4b243ed 100644
--- a/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java
+++ b/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java
@@ -19,6 +19,8 @@
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;
@@ -95,6 +97,7 @@
private Map<String, Integer> mAttemptSuccess = new HashMap<>();
private RetryStrategy mRetryStrategy = RetryStrategy.RETRY_TEST_CASE_FAILURE;
+ private boolean mRebootAtLastRetry = false;
public GranularRetriableTestWrapper(
IRemoteTest test,
@@ -172,6 +175,11 @@
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.
*
@@ -293,7 +301,16 @@
break;
}
}
-
+ // 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();
+ }
+ }
+ }
// Run the tests again
intraModuleRun(allListeners);
diff --git a/src/com/android/tradefed/testtype/suite/ITestSuite.java b/src/com/android/tradefed/testtype/suite/ITestSuite.java
index 5021b10..faa79be 100644
--- a/src/com/android/tradefed/testtype/suite/ITestSuite.java
+++ b/src/com/android/tradefed/testtype/suite/ITestSuite.java
@@ -178,6 +178,11 @@
@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 = "skip-all-system-status-check",
description = "Whether all system status check between modules should be skipped")
private boolean mSkipAllSystemStatusCheck = false;
@@ -199,14 +204,12 @@
)
private boolean mReportSystemChecker = false;
- @Deprecated
@Option(
name = "random-order",
description = "Whether randomizing the order of the modules to be ran or not."
)
private boolean mRandomOrder = false;
- @Deprecated
@Option(
name = "random-seed",
description = "Seed to randomize the order of the modules."
@@ -680,6 +683,8 @@
module.setLogSaver(mMainConfiguration.getLogSaver());
// Pass the retry strategy to the module
module.setRetryStrategy(mRetryStrategy, mMergeAttempts);
+ // Pass the reboot strategy at the last intra-module retry to the module
+ module.setRebootAtLastRetry(mRebootAtLastRetry);
// Actually run the module
module.run(listener, moduleListeners, failureListener, mMaxRunLimit);
diff --git a/src/com/android/tradefed/testtype/suite/ModuleDefinition.java b/src/com/android/tradefed/testtype/suite/ModuleDefinition.java
index 117e120..45b78b0 100644
--- a/src/com/android/tradefed/testtype/suite/ModuleDefinition.java
+++ b/src/com/android/tradefed/testtype/suite/ModuleDefinition.java
@@ -143,6 +143,7 @@
private RetryStrategy mRetryStrategy = RetryStrategy.RETRY_TEST_CASE_FAILURE;
private boolean mMergeAttempts = true;
+ private boolean mRebootAtLastRetry = false;
// Token during sharding
private Set<TokenProperty> mRequiredTokens = new HashSet<>();
@@ -590,6 +591,7 @@
retriableTest.setInvocationContext(mModuleInvocationContext);
retriableTest.setLogSaver(mLogSaver);
retriableTest.setRetryStrategy(mRetryStrategy);
+ retriableTest.setRebootAtLastRetry(mRebootAtLastRetry);
return retriableTest;
}
@@ -873,6 +875,11 @@
mMergeAttempts = mergeAttempts;
}
+ /** Sets the flag to reboot devices at the last intra-module retry. */
+ public final void setRebootAtLastRetry(boolean rebootAtLastRetry) {
+ mRebootAtLastRetry = rebootAtLastRetry;
+ }
+
/** Returns a list of tests that ran in this module. */
List<TestRunResult> getTestsResults() {
return mTestsResults;
diff --git a/src/com/android/tradefed/testtype/suite/retry/ResultsPlayer.java b/src/com/android/tradefed/testtype/suite/retry/ResultsPlayer.java
index 3627286..3b7f914 100644
--- a/src/com/android/tradefed/testtype/suite/retry/ResultsPlayer.java
+++ b/src/com/android/tradefed/testtype/suite/retry/ResultsPlayer.java
@@ -135,7 +135,7 @@
TestRunResult module,
Collection<Entry<TestDescription, TestResult>> testSet,
ITestInvocationListener listener) {
- listener.testRunStarted(module.getName(), module.getNumTests());
+ listener.testRunStarted(module.getName(), testSet.size());
for (Map.Entry<TestDescription, TestResult> testEntry : testSet) {
listener.testStarted(testEntry.getKey(), testEntry.getValue().getStartTime());
switch (testEntry.getValue().getStatus()) {
diff --git a/src/com/android/tradefed/util/StringEscapeUtils.java b/src/com/android/tradefed/util/StringEscapeUtils.java
index 1b7ea44..846c0e7 100644
--- a/src/com/android/tradefed/util/StringEscapeUtils.java
+++ b/src/com/android/tradefed/util/StringEscapeUtils.java
@@ -74,7 +74,7 @@
// this may lead to incorrect results such as \n -> n, or \t -> t, but it's unclear why
// a command line param would have \t anyways
param = param.replaceAll("\\\\(.)", "$1");
- String[] args = QuotationAwareTokenizer.tokenizeLine(param);
+ String[] args = QuotationAwareTokenizer.tokenizeLine(param, /* Logging */ false);
if (args.length != 0) {
result.addAll(Arrays.asList(args));
}
diff --git a/tests/src/com/android/tradefed/device/DeviceStateMonitorTest.java b/tests/src/com/android/tradefed/device/DeviceStateMonitorTest.java
index 3aced89..1a76fa6 100644
--- a/tests/src/com/android/tradefed/device/DeviceStateMonitorTest.java
+++ b/tests/src/com/android/tradefed/device/DeviceStateMonitorTest.java
@@ -790,4 +790,14 @@
};
assertNull(mMonitor.waitForDeviceAvailable(WAIT_TIMEOUT_REACHED_MS));
}
+
+ /** Test {@link DeviceStateMonitor#waitForDeviceInSideload(long)} */
+ public void testWaitForDeviceInSideload() throws Exception {
+ mMockDevice = EasyMock.createMock(IDevice.class);
+ EasyMock.expect(mMockDevice.getState()).andReturn(DeviceState.SIDELOAD).anyTimes();
+ EasyMock.expect(mMockDevice.getSerialNumber()).andReturn(SERIAL_NUMBER).anyTimes();
+ EasyMock.replay(mMockDevice);
+ mMonitor = new DeviceStateMonitor(mMockMgr, mMockDevice, true);
+ assertTrue(mMonitor.waitForDeviceInSideload(WAIT_TIMEOUT_NOT_REACHED_MS));
+ }
}
diff --git a/tests/src/com/android/tradefed/device/NativeDeviceTest.java b/tests/src/com/android/tradefed/device/NativeDeviceTest.java
index 27d5ce6..091fc5a 100644
--- a/tests/src/com/android/tradefed/device/NativeDeviceTest.java
+++ b/tests/src/com/android/tradefed/device/NativeDeviceTest.java
@@ -1643,6 +1643,26 @@
EasyMock.verify(mMockIDevice, mMockStateMonitor, mMockDvcMonitor);
}
+ /** Unit test for {@link NativeDevice#rebootIntoSideload()}}. */
+ @Test
+ public void testRebootIntoSideload() throws Exception {
+ NativeDevice testDevice =
+ new NativeDevice(mMockIDevice, mMockStateMonitor, mMockDvcMonitor) {
+ @Override
+ public TestDeviceState getDeviceState() {
+ return TestDeviceState.ONLINE;
+ }
+ };
+ String into = "sideload";
+ mMockIDevice.reboot(into);
+ EasyMock.expectLastCall();
+ EasyMock.expect(mMockStateMonitor.waitForDeviceInSideload(EasyMock.anyLong()))
+ .andReturn(true);
+ EasyMock.replay(mMockIDevice, mMockStateMonitor, mMockDvcMonitor);
+ testDevice.rebootIntoSideload();
+ EasyMock.verify(mMockIDevice, mMockStateMonitor, mMockDvcMonitor);
+ }
+
/** Unit test for {@link NativeDevice#unlockDevice()} already decrypted. */
@Test
public void testUnlockDevice_skipping() throws Exception {
diff --git a/tests/src/com/android/tradefed/device/cloud/RemoteFileUtilTest.java b/tests/src/com/android/tradefed/device/cloud/RemoteFileUtilTest.java
index 4113359..a6886b5 100644
--- a/tests/src/com/android/tradefed/device/cloud/RemoteFileUtilTest.java
+++ b/tests/src/com/android/tradefed/device/cloud/RemoteFileUtilTest.java
@@ -113,6 +113,42 @@
EasyMock.verify(mMockRunUtil);
}
+ /** Test pulling a directory from the remote hosts. */
+ @Test
+ public void testFetchRemoteDir() throws Exception {
+ GceAvdInfo fakeInfo = new GceAvdInfo("ins-gce", HostAndPort.fromHost("127.0.0.1"));
+ String remotePath = "/home/vsoc-01/cuttlefish_runtime/tombstones";
+ CommandResult res = new CommandResult(CommandStatus.SUCCESS);
+ EasyMock.expect(
+ mMockRunUtil.runTimedCmd(
+ EasyMock.anyLong(),
+ EasyMock.eq("scp"),
+ EasyMock.eq("-o"),
+ EasyMock.eq("UserKnownHostsFile=/dev/null"),
+ EasyMock.eq("-o"),
+ EasyMock.eq("StrictHostKeyChecking=no"),
+ EasyMock.eq("-o"),
+ EasyMock.eq("ServerAliveInterval=10"),
+ EasyMock.eq("-i"),
+ EasyMock.anyObject(),
+ EasyMock.eq("-r"),
+ EasyMock.eq("root@127.0.0.1:" + remotePath),
+ EasyMock.anyObject()))
+ .andReturn(res);
+ EasyMock.replay(mMockRunUtil);
+ File resDir = null;
+ try {
+ resDir =
+ RemoteFileUtil.fetchRemoteDir(
+ fakeInfo, mOptions, mMockRunUtil, 500L, remotePath);
+ // The original remote name is used.
+ assertTrue(resDir.isDirectory());
+ } finally {
+ FileUtil.recursiveDelete(resDir);
+ }
+ EasyMock.verify(mMockRunUtil);
+ }
+
/** Test pushing a file to a remote instance via scp. */
@Test
public void testPushFileToRemote() throws Exception {
diff --git a/tests/src/com/android/tradefed/device/contentprovider/ContentProviderHandlerTest.java b/tests/src/com/android/tradefed/device/contentprovider/ContentProviderHandlerTest.java
index df5ad01..88b0d45 100644
--- a/tests/src/com/android/tradefed/device/contentprovider/ContentProviderHandlerTest.java
+++ b/tests/src/com/android/tradefed/device/contentprovider/ContentProviderHandlerTest.java
@@ -230,7 +230,7 @@
public void testPullDir_EmptyDirectory() throws Exception {
File pullTo = FileUtil.createTempDir("content-provider-test");
- doReturn("No result found.").when(mMockDevice).executeShellCommand(anyString());
+ doReturn("No result found.\n").when(mMockDevice).executeShellCommand(anyString());
try {
assertTrue(mProvider.pullDir("path/somewhere", pullTo));
diff --git a/tests/src/com/android/tradefed/result/proto/FileProtoResultReporterTest.java b/tests/src/com/android/tradefed/result/proto/FileProtoResultReporterTest.java
index 591a17b..db48f97 100644
--- a/tests/src/com/android/tradefed/result/proto/FileProtoResultReporterTest.java
+++ b/tests/src/com/android/tradefed/result/proto/FileProtoResultReporterTest.java
@@ -17,17 +17,23 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import com.android.tradefed.config.ConfigurationDescriptor;
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.invoker.InvocationContext;
import com.android.tradefed.invoker.proto.InvocationContext.Context;
+import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.TestDescription;
import com.android.tradefed.result.proto.TestRecordProto.TestRecord;
+import com.android.tradefed.testtype.suite.ModuleDefinition;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.proto.TestRecordProtoUtil;
import com.google.protobuf.Any;
+import org.easymock.EasyMock;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -35,6 +41,9 @@
import org.junit.runners.JUnit4;
import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
/** Unit tests for {@link FileProtoResultReporter}. */
@RunWith(JUnit4.class)
@@ -42,17 +51,23 @@
private FileProtoResultReporter mReporter;
private File mOutput;
+ private List<File> mToDelete = new ArrayList<>();
+ private ITestInvocationListener mMockListener;
@Before
public void setUp() throws Exception {
mOutput = FileUtil.createTempFile("proto-file-reporter-test", ".pb");
mReporter = new FileProtoResultReporter();
mReporter.setFileOutput(mOutput);
+ mMockListener = EasyMock.createMock(ITestInvocationListener.class);
}
@After
public void tearDown() {
FileUtil.deleteFile(mOutput);
+ for (File f : mToDelete) {
+ FileUtil.deleteFile(f);
+ }
}
@Test
@@ -75,4 +90,75 @@
InvocationContext.fromProto(anyDescription.unpack(Context.class));
assertEquals("test", endContext.getAttributes().get("test").get(0));
}
+
+ @Test
+ public void testWriteResults_periodic() throws Exception {
+ TestDescription test1 = new TestDescription("class1", "test1");
+ mReporter.setPeriodicWriting(true);
+ mReporter.setFileOutput(mOutput);
+ IInvocationContext context = new InvocationContext();
+ context.setConfigurationDescriptor(new ConfigurationDescriptor());
+ context.addInvocationAttribute("test", "test");
+ mReporter.invocationStarted(context);
+ mReporter.testModuleStarted(createModuleContext("module1"));
+ mReporter.testRunStarted("run1", 1);
+ mReporter.testStarted(test1);
+ mReporter.testEnded(test1, new HashMap<String, Metric>());
+ mReporter.testRunEnded(200L, new HashMap<String, Metric>());
+ mReporter.testModuleEnded();
+ mReporter.testModuleStarted(createModuleContext("module2"));
+ mReporter.testModuleEnded();
+ // Run without a module
+ mReporter.testRunStarted("run2", 1);
+ mReporter.testStarted(test1);
+ mReporter.testEnded(test1, new HashMap<String, Metric>());
+ mReporter.testRunEnded(200L, new HashMap<String, Metric>());
+ mReporter.invocationEnded(500L);
+
+ int index = 0;
+ File currentOutput = new File(mOutput.getAbsolutePath() + index);
+ if (!currentOutput.exists()) {
+ fail("Should have output at least one file");
+ }
+
+ mMockListener.invocationStarted(EasyMock.anyObject());
+ mMockListener.testModuleStarted(EasyMock.anyObject());
+ mMockListener.testRunStarted(
+ EasyMock.eq("run1"), EasyMock.eq(1), EasyMock.eq(0), EasyMock.anyLong());
+ mMockListener.testStarted(EasyMock.eq(test1), EasyMock.anyLong());
+ mMockListener.testEnded(
+ EasyMock.eq(test1),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mMockListener.testRunEnded(200L, new HashMap<String, Metric>());
+ mMockListener.testModuleEnded();
+ mMockListener.testModuleStarted(EasyMock.anyObject());
+ mMockListener.testModuleEnded();
+ mMockListener.testRunStarted(
+ EasyMock.eq("run2"), EasyMock.eq(1), EasyMock.eq(0), EasyMock.anyLong());
+ mMockListener.testStarted(EasyMock.eq(test1), EasyMock.anyLong());
+ mMockListener.testEnded(
+ EasyMock.eq(test1),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mMockListener.testRunEnded(200L, new HashMap<String, Metric>());
+ mMockListener.invocationEnded(500L);
+
+ EasyMock.replay(mMockListener);
+ ProtoResultParser parser = new ProtoResultParser(mMockListener, context, true);
+ while (currentOutput.exists()) {
+ mToDelete.add(currentOutput);
+ parser.processFileProto(currentOutput);
+ index++;
+ currentOutput = new File(mOutput.getAbsolutePath() + index);
+ }
+ EasyMock.verify(mMockListener);
+ }
+
+ private IInvocationContext createModuleContext(String moduleId) {
+ IInvocationContext context = new InvocationContext();
+ context.addInvocationAttribute(ModuleDefinition.MODULE_ID, moduleId);
+ context.setConfigurationDescriptor(new ConfigurationDescriptor());
+ return context;
+ }
}
diff --git a/tests/src/com/android/tradefed/result/proto/ProtoResultParserTest.java b/tests/src/com/android/tradefed/result/proto/ProtoResultParserTest.java
index e85176d..860835f 100644
--- a/tests/src/com/android/tradefed/result/proto/ProtoResultParserTest.java
+++ b/tests/src/com/android/tradefed/result/proto/ProtoResultParserTest.java
@@ -90,7 +90,7 @@
}
@Override
- public void processTestRunEnded(TestRecord runRecord) {
+ public void processTestRunEnded(TestRecord runRecord, boolean moduleInProgress) {
mParser.processNewProto(runRecord);
}
diff --git a/tests/src/com/android/tradefed/result/suite/XmlSuiteResultFormatterTest.java b/tests/src/com/android/tradefed/result/suite/XmlSuiteResultFormatterTest.java
index ef697dd..8f61d29 100644
--- a/tests/src/com/android/tradefed/result/suite/XmlSuiteResultFormatterTest.java
+++ b/tests/src/com/android/tradefed/result/suite/XmlSuiteResultFormatterTest.java
@@ -50,6 +50,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -538,6 +539,34 @@
assertEquals(RUN_HISTORY, holder.context.getAttributes().getUniqueMap().get("run_history"));
}
+ /** Ensure the order is sorted according to module name and abi. */
+ @Test
+ public void testSortModules() {
+ List<TestRunResult> originalList = new ArrayList<>();
+ originalList.add(createFakeResult("armeabi-v7a module1", 1, 0, 0, 0));
+ originalList.add(createFakeResult("arm64-v8a module3", 1, 0, 0, 0));
+ originalList.add(createFakeResult("armeabi-v7a module2", 1, 0, 0, 0));
+ originalList.add(createFakeResult("arm64-v8a module1", 1, 0, 0, 0));
+ originalList.add(createFakeResult("armeabi-v7a module4", 1, 0, 0, 0));
+ originalList.add(createFakeResult("arm64-v8a module2", 1, 0, 0, 0));
+ Map<String, IAbi> moduleAbis = new HashMap<>();
+ moduleAbis.put("armeabi-v7a module1", new Abi("armeabi-v7a", "32"));
+ moduleAbis.put("arm64-v8a module1", new Abi("arm64-v8a", "64"));
+ moduleAbis.put("armeabi-v7a module2", new Abi("armeabi-v7a", "32"));
+ moduleAbis.put("arm64-v8a module2", new Abi("arm64-v8a", "64"));
+ moduleAbis.put("arm64-v8a module3", new Abi("arm64-v8a", "64"));
+ moduleAbis.put("armeabi-v7a module4", new Abi("armeabi-v7a", "32"));
+
+ List<TestRunResult> sortedResult = mFormatter.sortModules(originalList, moduleAbis);
+ assertEquals(6, sortedResult.size());
+ assertEquals("arm64-v8a module1", sortedResult.get(0).getName());
+ assertEquals("armeabi-v7a module1", sortedResult.get(1).getName());
+ assertEquals("arm64-v8a module2", sortedResult.get(2).getName());
+ assertEquals("armeabi-v7a module2", sortedResult.get(3).getName());
+ assertEquals("arm64-v8a module3", sortedResult.get(4).getName());
+ assertEquals("armeabi-v7a module4", sortedResult.get(5).getName());
+ }
+
private TestRunResult createResultWithLog(String runName, int count, LogDataType type) {
TestRunResult fakeRes = new TestRunResult();
fakeRes.testRunStarted(runName, count);
diff --git a/tests/src/com/android/tradefed/testtype/GTestTest.java b/tests/src/com/android/tradefed/testtype/GTestTest.java
index 2a5e10c..f47190f 100644
--- a/tests/src/com/android/tradefed/testtype/GTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/GTestTest.java
@@ -247,6 +247,23 @@
doTestFilter(String.format("%s=%s:%s", GTEST_FLAG_FILTER, includeFilter1, includeFilter2));
}
+ /** Test that large filters are converted to flagfile. */
+ @Test
+ public void testLargeFilters() throws DeviceNotAvailableException {
+ StringBuilder includeFilter1 = new StringBuilder("abc");
+ for (int i = 0; i < 550; i++) {
+ includeFilter1.append("a");
+ }
+ String includeFilter2 = "def";
+ mGTest.addIncludeFilter(includeFilter1.toString());
+ mGTest.addIncludeFilter(includeFilter2);
+
+ EasyMock.expect(mMockITestDevice.pushFile(EasyMock.anyObject(), EasyMock.anyObject()))
+ .andReturn(true);
+
+ doTestFilter(String.format("%s=/data/local/tmp/flagfile", GTestBase.GTEST_FLAG_FILE));
+ }
+
/** Test the exclude filtering of test methods. */
@Test
public void testExcludeFilter() throws DeviceNotAvailableException {
@@ -277,20 +294,15 @@
@Test
public void testCommandTooLong() throws DeviceNotAvailableException {
String deviceScriptPath = "/data/local/tmp/gtest_script.sh";
- StringBuilder filterString = new StringBuilder(GTEST_FLAG_FILTER);
- filterString.append("=-");
- for (int i = 0; i < 100; i++) {
- if (i != 0) {
- filterString.append(":");
- }
- String filter = String.format("ExcludeClass%d", i);
- filterString.append(filter);
- mGTest.addExcludeFilter(filter);
+ StringBuilder testNameBuilder = new StringBuilder();
+ for (int i = 0; i < 1005; i++) {
+ testNameBuilder.append("a");
}
+ String testName = testNameBuilder.toString();
// filter string will be longer than GTest.GTEST_CMD_CHAR_LIMIT
String nativeTestPath = GTest.DEFAULT_NATIVETEST_PATH;
- String testPath = nativeTestPath + "/test1";
+ String testPath = nativeTestPath + "/" + testName;
// configure the mock file system to have a single test
MockFileUtil.setMockDirContents(mMockITestDevice, nativeTestPath, "test1");
EasyMock.expect(mMockITestDevice.doesFileExist(nativeTestPath)).andReturn(true);
@@ -298,7 +310,7 @@
EasyMock.expect(mMockITestDevice.isDirectory(testPath)).andReturn(false);
// report the file as executable
EasyMock.expect(mMockITestDevice.isExecutable(testPath)).andReturn(true);
- String[] files = new String[] {"test1"};
+ String[] files = new String[] {testName};
EasyMock.expect(mMockITestDevice.getChildren(nativeTestPath)).andReturn(files);
// Expect push of script file
EasyMock.expect(mMockITestDevice.pushString(EasyMock.<String>anyObject(),
@@ -482,6 +494,28 @@
assertEquals("test_path flags", cmd_line);
}
+ /**
+ * Test {@link GTest#getGTestFilters(String)} When the push to file fails, in this case we use
+ * the original filter arguments instead of hte flagfile.
+ */
+ @Test
+ public void testGetGTestFilters_largeFilters_pushFail() throws Exception {
+ StringBuilder includeFilter1 = new StringBuilder("abc");
+ for (int i = 0; i < 550; i++) {
+ includeFilter1.append("a");
+ }
+ mGTest.addIncludeFilter(includeFilter1.toString());
+ // Fail to push
+ EasyMock.expect(mMockITestDevice.pushFile(EasyMock.anyObject(), EasyMock.anyObject()))
+ .andReturn(false);
+
+ EasyMock.replay(mMockITestDevice);
+ String flag = mGTest.getGTestFilters("/path/");
+ // We fallback to the original command line filter
+ assertEquals("--gtest_filter=" + includeFilter1.toString(), flag);
+ EasyMock.verify(mMockITestDevice);
+ }
+
/** Test {@link GTest#getGTestCmdLine(String, String)} with non-default user. */
@Test
public void testGetGTestCmdLine_runAs() throws Exception {
diff --git a/tests/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapperTest.java b/tests/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapperTest.java
index f34d409..c941afd 100644
--- a/tests/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapperTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapperTest.java
@@ -20,10 +20,12 @@
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.device.DeviceNotAvailableException;
import com.android.tradefed.device.DeviceUnresponsiveException;
+import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.metric.BaseDeviceMetricCollector;
import com.android.tradefed.device.metric.DeviceMetricData;
import com.android.tradefed.device.metric.IMetricCollector;
@@ -39,6 +41,8 @@
import com.android.tradefed.testtype.ITestFilterReceiver;
import com.android.tradefed.testtype.suite.ITestSuite.RetryStrategy;
+import org.easymock.EasyMock;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -58,6 +62,7 @@
private static final String RUN_NAME = "test run";
private static final String RUN_NAME_2 = "test run 2";
+ private InvocationContext mModuleInvocationContext;
private class BasicFakeTest implements IRemoteTest {
@@ -252,7 +257,7 @@
granularTestWrapper.setMarkTestsSkipped(false);
granularTestWrapper.setMetricCollectors(collectors);
// Setup InvocationContext.
- granularTestWrapper.setInvocationContext(new InvocationContext());
+ granularTestWrapper.setInvocationContext(mModuleInvocationContext);
// Setup logsaver.
granularTestWrapper.setLogSaver(new FileSystemLogSaver());
IConfiguration mockModuleConfiguration = Mockito.mock(IConfiguration.class);
@@ -260,6 +265,11 @@
return granularTestWrapper;
}
+ @Before
+ public void setUp() {
+ mModuleInvocationContext = new InvocationContext();
+ }
+
/**
* Test that the intra module retry does not inhibit DeviceNotAvailableException. They are
* bubbled up to the top.
@@ -802,6 +812,53 @@
assertTrue(calledCollector.wasCalled);
}
+ /**
+ * Test to reboot device at the last intra-module retry.
+ */
+ @Test
+ public void testIntraModuleRun_rebootAtLastIntraModuleRetry() throws Exception {
+ FakeTest test = new FakeTest();
+ test.setRunFailure("I failed!");
+ ITestDevice mMockDevice = EasyMock.createMock(ITestDevice.class);
+ mModuleInvocationContext.addAllocatedDevice("default-device1", mMockDevice);
+ GranularRetriableTestWrapper granularTestWrapper = createGranularTestWrapper(test, 3);
+ granularTestWrapper.setRetryStrategy(RetryStrategy.RETRY_ANY_FAILURE);
+ granularTestWrapper.setRebootAtLastRetry(true);
+ EasyMock.expect(mMockDevice.getIDevice()).andStubReturn(EasyMock.createMock(IDevice.class));
+ EasyMock.expect(mMockDevice.getSerialNumber()).andReturn("SERIAL");
+ mMockDevice.reboot();
+ EasyMock.replay(mMockDevice);
+ granularTestWrapper.run(new CollectingTestListener());
+ EasyMock.verify(mMockDevice);
+ }
+
+ /**
+ * Test to reboot multi-devices at the last intra-module retry.
+ */
+ @Test
+ public void testIntraModuleRun_rebootMultiDevicesAtLastIntraModuleRetry() throws Exception {
+ FakeTest test = new FakeTest();
+ test.setRunFailure("I failed!");
+ ITestDevice mMockDevice = EasyMock.createMock(ITestDevice.class);
+ ITestDevice mMockDevice2 = EasyMock.createMock(ITestDevice.class);
+ mModuleInvocationContext.addAllocatedDevice("default-device1", mMockDevice);
+ mModuleInvocationContext.addAllocatedDevice("default-device2", mMockDevice2);
+ GranularRetriableTestWrapper granularTestWrapper = createGranularTestWrapper(test, 3);
+ granularTestWrapper.setRetryStrategy(RetryStrategy.RETRY_ANY_FAILURE);
+ granularTestWrapper.setRebootAtLastRetry(true);
+ 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);
+ granularTestWrapper.run(new CollectingTestListener());
+ EasyMock.verify(mMockDevice);
+ EasyMock.verify(mMockDevice2);
+ }
+
/** Collector that track if it was called or not */
public static class CalledMetricCollector extends BaseDeviceMetricCollector {
diff --git a/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java b/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java
index 39e072a..8374a80 100644
--- a/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java
@@ -496,7 +496,7 @@
mTestSuite.run(mMockListener);
verifyMocks();
}
-
+
/**
* Test for {@link ITestSuite#run(ITestInvocationListener)} when the System status checker is
* passing pre-check but failing post-check and we enable reporting a failure for it.
diff --git a/tests/src/com/android/tradefed/testtype/suite/ModuleDefinitionTest.java b/tests/src/com/android/tradefed/testtype/suite/ModuleDefinitionTest.java
index aac5eb3..c71fea9 100644
--- a/tests/src/com/android/tradefed/testtype/suite/ModuleDefinitionTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/ModuleDefinitionTest.java
@@ -1403,7 +1403,6 @@
mMultiTargetPrepList,
new Configuration("", ""));
mModule.setRetryStrategy(RetryStrategy.ITERATIONS, false);
-
mModule.getModuleInvocationContext().addAllocatedDevice(DEFAULT_DEVICE_NAME, mMockDevice);
mModule.getModuleInvocationContext()
.addDeviceBuildInfo(DEFAULT_DEVICE_NAME, mMockBuildInfo);
diff --git a/tests/src/com/android/tradefed/testtype/suite/retry/ResultsPlayerTest.java b/tests/src/com/android/tradefed/testtype/suite/retry/ResultsPlayerTest.java
index 97f2f08..ce47dc8 100644
--- a/tests/src/com/android/tradefed/testtype/suite/retry/ResultsPlayerTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/retry/ResultsPlayerTest.java
@@ -147,7 +147,7 @@
mPlayer.addToReplay(null, createTestRunResult("run1", 2, 1), entry);
// Verify Mock
- mMockListener.testRunStarted("run1", 2);
+ mMockListener.testRunStarted("run1", 1);
// Only the provided test is re-run
mMockListener.testStarted(EasyMock.eq(test), EasyMock.anyLong());
mMockListener.testAssumptionFailure(test, "assertionfailure");
@@ -184,7 +184,7 @@
mPlayer.addToReplay(null, runResult, entry2);
// Verify Mock
- mMockListener.testRunStarted("run1", 5);
+ mMockListener.testRunStarted("run1", 2);
// Only the provided test is re-run
mMockListener.testStarted(EasyMock.eq(test), EasyMock.anyLong());
mMockListener.testAssumptionFailure(test, "assertionfailure");