xref: /aosp_15_r20/external/autotest/server/cros/tradefed/tradefed_test.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li# Lint as: python2, python3
2*9c5db199SXin Li# Copyright 2016 The Chromium OS Authors. All rights reserved.
3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be
4*9c5db199SXin Li# found in the LICENSE file.
5*9c5db199SXin Li
6*9c5db199SXin Li# repohooks/pre-upload.py currently does not run pylint. But for developers who
7*9c5db199SXin Li# want to check their code manually we disable several harmless pylint warnings
8*9c5db199SXin Li# which just distract from more serious remaining issues.
9*9c5db199SXin Li#
10*9c5db199SXin Li# The instance variables _host and _install_paths are not defined in __init__().
11*9c5db199SXin Li# pylint: disable=attribute-defined-outside-init
12*9c5db199SXin Li#
13*9c5db199SXin Li# Many short variable names don't follow the naming convention.
14*9c5db199SXin Li# pylint: disable=invalid-name
15*9c5db199SXin Li#
16*9c5db199SXin Li# _parse_result() and _dir_size() don't access self and could be functions.
17*9c5db199SXin Li# pylint: disable=no-self-use
18*9c5db199SXin Li
19*9c5db199SXin Lifrom collections import namedtuple
20*9c5db199SXin Liimport errno
21*9c5db199SXin Liimport glob
22*9c5db199SXin Liimport hashlib
23*9c5db199SXin Liimport logging
24*9c5db199SXin Liimport os
25*9c5db199SXin Liimport pipes
26*9c5db199SXin Liimport re
27*9c5db199SXin Liimport shutil
28*9c5db199SXin Liimport stat
29*9c5db199SXin Liimport subprocess
30*9c5db199SXin Liimport tempfile
31*9c5db199SXin Liimport time
32*9c5db199SXin Liimport six.moves.urllib_parse as urlparse
33*9c5db199SXin Li
34*9c5db199SXin Lifrom autotest_lib.client.bin import utils as client_utils
35*9c5db199SXin Lifrom autotest_lib.client.common_lib import error
36*9c5db199SXin Lifrom autotest_lib.server import test
37*9c5db199SXin Lifrom autotest_lib.server import utils
38*9c5db199SXin Lifrom autotest_lib.server.cros.tradefed import adb as adb_utils
39*9c5db199SXin Lifrom autotest_lib.server.cros.tradefed import cts_expected_failure_parser
40*9c5db199SXin Lifrom autotest_lib.server.cros.tradefed import tradefed_chromelogin as login
41*9c5db199SXin Lifrom autotest_lib.server.cros.tradefed import tradefed_constants as constants
42*9c5db199SXin Lifrom autotest_lib.server.cros.tradefed import tradefed_utils
43*9c5db199SXin Lifrom autotest_lib.server.cros.tradefed import tradefed_prerequisite
44*9c5db199SXin Lifrom autotest_lib.server.autotest import OFFLOAD_ENVVAR
45*9c5db199SXin Li
46*9c5db199SXin Li# TODO(kinaba): Move to tradefed_utils together with the setup/cleanup methods.
47*9c5db199SXin LiMediaAsset = namedtuple('MediaAssetInfo', ['uri', 'localpath'])
48*9c5db199SXin Li
49*9c5db199SXin Li
50*9c5db199SXin Liclass TradefedTest(test.test):
51*9c5db199SXin Li    """Base class to prepare DUT to run tests via tradefed."""
52*9c5db199SXin Li    version = 1
53*9c5db199SXin Li
54*9c5db199SXin Li    # Default and upperbounds of max_retry, based on board and revision
55*9c5db199SXin Li    # after branching (that is, 'y' of R74-12345.y.z).
56*9c5db199SXin Li    #
57*9c5db199SXin Li    # By default, 0<=y<1 does 5 retries and 1<=y does 10. The |max_retry|
58*9c5db199SXin Li    # parameter in control files can override the count, within the
59*9c5db199SXin Li    # _BRANCH_MAX_RETRY limit below.
60*9c5db199SXin Li    _BRANCH_DEFAULT_RETRY = [(0, 5), (1, 10)]  # dev=5, beta=stable=10
61*9c5db199SXin Li    _BRANCH_MAX_RETRY = [(0, 12), (1, 30),      # dev=12, beta=30, stable=99
62*9c5db199SXin Li        (constants.APPROXIMATE_STABLE_BRANCH_NUMBER, 99)]
63*9c5db199SXin Li    # TODO(kinaba): betty-arcnext
64*9c5db199SXin Li    _BOARD_MAX_RETRY = {'betty': 0}
65*9c5db199SXin Li
66*9c5db199SXin Li    _SHARD_CMD = None
67*9c5db199SXin Li    _board_arch = None
68*9c5db199SXin Li    _board_name = None
69*9c5db199SXin Li    _model_name = None
70*9c5db199SXin Li    _release_branch_number = None  # The 'y' of OS version Rxx-xxxxx.y.z
71*9c5db199SXin Li    _android_version = None
72*9c5db199SXin Li    _first_api_level = None
73*9c5db199SXin Li    _num_media_bundles = 0
74*9c5db199SXin Li    _abilist = []
75*9c5db199SXin Li
76*9c5db199SXin Li    # A job will be aborted after 16h. Subtract 30m for setup/teardown.
77*9c5db199SXin Li    _MAX_LAB_JOB_LENGTH_IN_SEC = 16 * 60 * 60 - 30 * 60
78*9c5db199SXin Li    _job_deadline = None
79*9c5db199SXin Li
80*9c5db199SXin Li    # Currently this is only used for dependency injection for testing.
81*9c5db199SXin Li    def __init__(self, *args, **kwargs):
82*9c5db199SXin Li        super().__init__(*args)
83*9c5db199SXin Li        self._adb = kwargs.get('adb', adb_utils.Adb())
84*9c5db199SXin Li
85*9c5db199SXin Li    def _log_java_version(self):
86*9c5db199SXin Li        """Log java version to debug failures due to version mismatch."""
87*9c5db199SXin Li        utils.run(
88*9c5db199SXin Li            'java',
89*9c5db199SXin Li            args=('-version',),
90*9c5db199SXin Li            ignore_status=False,
91*9c5db199SXin Li            verbose=True,
92*9c5db199SXin Li            stdout_tee=utils.TEE_TO_LOGS,
93*9c5db199SXin Li            stderr_tee=utils.TEE_TO_LOGS)
94*9c5db199SXin Li
95*9c5db199SXin Li    def initialize(self,
96*9c5db199SXin Li                   bundle=None,
97*9c5db199SXin Li                   uri=None,
98*9c5db199SXin Li                   host=None,
99*9c5db199SXin Li                   hosts=None,
100*9c5db199SXin Li                   max_retry=None,
101*9c5db199SXin Li                   load_waivers=True,
102*9c5db199SXin Li                   retry_manual_tests=False,
103*9c5db199SXin Li                   warn_on_test_retry=True,
104*9c5db199SXin Li                   hard_reboot_on_failure=False,
105*9c5db199SXin Li                   use_jdk9=False,
106*9c5db199SXin Li                   use_old_adb=False):
107*9c5db199SXin Li        """Sets up the tools and binary bundles for the test."""
108*9c5db199SXin Li        if utils.is_in_container() and not client_utils.is_moblab():
109*9c5db199SXin Li            self._job_deadline = time.time() + self._MAX_LAB_JOB_LENGTH_IN_SEC
110*9c5db199SXin Li
111*9c5db199SXin Li        self._install_paths = []
112*9c5db199SXin Li        # TODO(pwang): Remove host if we enable multiple hosts everywhere.
113*9c5db199SXin Li        self._hosts = [host] if host else hosts
114*9c5db199SXin Li        for host in self._hosts:
115*9c5db199SXin Li            logging.info('Hostname: %s', host.host_port)
116*9c5db199SXin Li        self._verify_hosts()
117*9c5db199SXin Li
118*9c5db199SXin Li        self._max_retry = self._get_max_retry(max_retry)
119*9c5db199SXin Li        self._warn_on_test_retry = warn_on_test_retry
120*9c5db199SXin Li        # Tests in the lab run within individual lxc container instances.
121*9c5db199SXin Li        if utils.is_in_container():
122*9c5db199SXin Li            cache_root = constants.TRADEFED_CACHE_CONTAINER
123*9c5db199SXin Li        else:
124*9c5db199SXin Li            cache_root = constants.TRADEFED_CACHE_LOCAL
125*9c5db199SXin Li
126*9c5db199SXin Li        # The content of the cache survives across jobs.
127*9c5db199SXin Li        self._safe_makedirs(cache_root)
128*9c5db199SXin Li        self._tradefed_cache = os.path.join(cache_root, 'cache')
129*9c5db199SXin Li        self._tradefed_cache_lock = os.path.join(cache_root, 'lock')
130*9c5db199SXin Li        self._tradefed_cache_dirty = os.path.join(cache_root, 'dirty')
131*9c5db199SXin Li        # The content of the install location does not survive across jobs and
132*9c5db199SXin Li        # is isolated (by using a unique path)_against other autotest instances.
133*9c5db199SXin Li        # This is not needed for the lab, but if somebody wants to run multiple
134*9c5db199SXin Li        # TradedefTest instance.
135*9c5db199SXin Li        self._tradefed_install = tempfile.mkdtemp(
136*9c5db199SXin Li            prefix=constants.TRADEFED_PREFIX)
137*9c5db199SXin Li        # Under lxc the cache is shared between multiple autotest/tradefed
138*9c5db199SXin Li        # instances. We need to synchronize access to it. All binaries are
139*9c5db199SXin Li        # installed through the (shared) cache into the local (unshared)
140*9c5db199SXin Li        # lxc/autotest instance storage.
141*9c5db199SXin Li        # If clearing the cache it must happen before all downloads.
142*9c5db199SXin Li        self._clean_download_cache_if_needed()
143*9c5db199SXin Li        # Set permissions (rwxr-xr-x) to the executable binaries.
144*9c5db199SXin Li        permission = (
145*9c5db199SXin Li            stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH
146*9c5db199SXin Li            | stat.S_IXOTH)
147*9c5db199SXin Li
148*9c5db199SXin Li        adb_dir = constants.ADB_DIR_OLD if use_old_adb else constants.ADB_DIR
149*9c5db199SXin Li        self._install_files(adb_dir, constants.ADB_FILES, permission)
150*9c5db199SXin Li        self._install_files(constants.SDK_TOOLS_DIR,
151*9c5db199SXin Li                            constants.SDK_TOOLS_FILES, permission)
152*9c5db199SXin Li
153*9c5db199SXin Li        # If use_jdk9 is set true, use jdk9 than default jdk8.
154*9c5db199SXin Li        if use_jdk9:
155*9c5db199SXin Li            if utils.is_in_container() and not client_utils.is_moblab():
156*9c5db199SXin Li                logging.info('Lab: switching to JDK9')
157*9c5db199SXin Li                try:
158*9c5db199SXin Li                    os.environ['JAVA_HOME'] = '/usr/lib/jvm/jdk-9.0.4'
159*9c5db199SXin Li                    os.environ['PATH'] = os.environ['JAVA_HOME']\
160*9c5db199SXin Li                                      + '/bin:' + os.environ['PATH']
161*9c5db199SXin Li                    logging.info(
162*9c5db199SXin Li                            subprocess.check_output(['java', '-version'],
163*9c5db199SXin Li                                                    stderr=subprocess.STDOUT))
164*9c5db199SXin Li                except OSError:
165*9c5db199SXin Li                    logging.error('Can\'t change current PATH directory')
166*9c5db199SXin Li            else:
167*9c5db199SXin Li                logging.info('Non-lab environment: should be using JDK9+')
168*9c5db199SXin Li
169*9c5db199SXin Li        # TODO(kinaba): Remove the hack and fully enable the feature.
170*9c5db199SXin Li        # For release branches (Rx-yyyyy.3.0 or above), always use the
171*9c5db199SXin Li        # official build instead of the release build. See b/210369548
172*9c5db199SXin Li        if uri == 'DEV' and self._get_release_branch_number() >= 3:
173*9c5db199SXin Li            uri = 'LATEST'
174*9c5db199SXin Li        # Install the tradefed bundle.
175*9c5db199SXin Li        bundle_install_path = self._install_bundle(
176*9c5db199SXin Li                self._get_bundle_url(uri, bundle))
177*9c5db199SXin Li        self._repository = os.path.join(bundle_install_path,
178*9c5db199SXin Li                                        self._get_tradefed_base_dir())
179*9c5db199SXin Li
180*9c5db199SXin Li        # Load expected test failures to exclude them from re-runs.
181*9c5db199SXin Li        self._waivers = set()
182*9c5db199SXin Li        if load_waivers:
183*9c5db199SXin Li            self._waivers.update(
184*9c5db199SXin Li                    self._get_expected_failures('expectations', bundle))
185*9c5db199SXin Li        if not retry_manual_tests:
186*9c5db199SXin Li            self._waivers.update(
187*9c5db199SXin Li                    self._get_expected_failures('manual_tests', bundle))
188*9c5db199SXin Li
189*9c5db199SXin Li        # Load modules with no tests.
190*9c5db199SXin Li        self._notest_modules = self._get_expected_failures('notest_modules',
191*9c5db199SXin Li                bundle)
192*9c5db199SXin Li        self._hard_reboot_on_failure = hard_reboot_on_failure
193*9c5db199SXin Li
194*9c5db199SXin Li    def _output_perf(self):
195*9c5db199SXin Li        """Output performance values."""
196*9c5db199SXin Li        base = self._default_tradefed_base_dir()
197*9c5db199SXin Li        path = tradefed_utils.get_test_result_xml_path(base)
198*9c5db199SXin Li        if path:
199*9c5db199SXin Li            for metric in tradefed_utils.get_perf_metrics_from_test_result_xml(
200*9c5db199SXin Li                path, self.resultsdir):
201*9c5db199SXin Li                self.output_perf_value(**metric)
202*9c5db199SXin Li
203*9c5db199SXin Li    def _prepare_synchronous_offloads(self):
204*9c5db199SXin Li        """
205*9c5db199SXin Li        Copy files needed for APFE to synchronous offload dir,  with some
206*9c5db199SXin Li        structure to make the post-job postprocessing simpler.
207*9c5db199SXin Li        """
208*9c5db199SXin Li        testname = os.path.basename(self.outputdir)
209*9c5db199SXin Li        # This is yyyy.mm.dd_hh.mm.ss  (start time)
210*9c5db199SXin Li        timestamp_pattern = ("[0-9][0-9][0-9][0-9].[0-9][0-9].[0-9][0-9]" +
211*9c5db199SXin Li                             "_[0-9][0-9].[0-9][0-9].[0-9][0-9]")
212*9c5db199SXin Li        time_glob = os.path.join(
213*9c5db199SXin Li            self._default_tradefed_base_dir(), timestamp_pattern
214*9c5db199SXin Li        )
215*9c5db199SXin Li        for dirpath in glob.glob(time_glob):
216*9c5db199SXin Li            timestamp = os.path.basename(dirpath)
217*9c5db199SXin Li            locs = [os.path.join(dirpath, f) for f in ["test_result.xml",
218*9c5db199SXin Li                                                       "testResult.xml"]]
219*9c5db199SXin Li            for f in locs:
220*9c5db199SXin Li                if os.path.exists(f):
221*9c5db199SXin Li                    subdirs = self._subdirs(f, testname, timestamp)
222*9c5db199SXin Li                    self._copy_to_offload_dir(f, subdirs)
223*9c5db199SXin Li        for z in glob.glob(time_glob+".zip"):
224*9c5db199SXin Li            self._copy_to_offload_dir(z, self._subdirs(z, testname))
225*9c5db199SXin Li
226*9c5db199SXin Li    def _copy_to_offload_dir(self, src_path, subdirs, recursive=True):
227*9c5db199SXin Li        target = os.path.join(os.getenv(OFFLOAD_ENVVAR), *subdirs)
228*9c5db199SXin Li        self._safe_makedirs(target)
229*9c5db199SXin Li        if not recursive or os.path.isfile(src_path):
230*9c5db199SXin Li            return shutil.copy2(src_path, str(target))
231*9c5db199SXin Li        return shutil.copytree(src_path, str(target))
232*9c5db199SXin Li
233*9c5db199SXin Li    def _subdirs(self, path, testname, timestamp=""):
234*9c5db199SXin Li        # CTS results from bvt-arc suites need to be sent to the
235*9c5db199SXin Li        # specially-designated bucket for early EDI entries in APFE,
236*9c5db199SXin Li        # but only there.
237*9c5db199SXin Li        dest = "BVT" if 'bvt-arc' in path else "CTS"
238*9c5db199SXin Li        return ["APFE", dest, testname, timestamp]
239*9c5db199SXin Li
240*9c5db199SXin Li    def cleanup(self):
241*9c5db199SXin Li        """Cleans up any dirtied state."""
242*9c5db199SXin Li
243*9c5db199SXin Li        # We also run a postprocess result and performance data
244*9c5db199SXin Li        # offloading here so that WARN and FAIL runs also run the
245*9c5db199SXin Li        # steps. postprocess() method only runs for PASSing jobs.
246*9c5db199SXin Li        self._prepare_synchronous_offloads()
247*9c5db199SXin Li        self._output_perf()
248*9c5db199SXin Li
249*9c5db199SXin Li        try:
250*9c5db199SXin Li            # Clean up test data that may not be deletable on previous
251*9c5db199SXin Li            # ChromeOS versions. See b/170276268.
252*9c5db199SXin Li            self._run_commands([
253*9c5db199SXin Li                    'cryptohome --action=remove --force [email protected]'
254*9c5db199SXin Li            ],
255*9c5db199SXin Li                               ignore_status=True)
256*9c5db199SXin Li        except:
257*9c5db199SXin Li            logging.error('Failed to clean up the test account.')
258*9c5db199SXin Li
259*9c5db199SXin Li        self._kill_adb_server()
260*9c5db199SXin Li
261*9c5db199SXin Li        if hasattr(self, '_tradefed_install'):
262*9c5db199SXin Li            logging.info('Cleaning up %s.', self._tradefed_install)
263*9c5db199SXin Li            try:
264*9c5db199SXin Li                shutil.rmtree(self._tradefed_install)
265*9c5db199SXin Li            except IOError:
266*9c5db199SXin Li                pass
267*9c5db199SXin Li
268*9c5db199SXin Li    def _kill_adb_server(self):
269*9c5db199SXin Li        # Kill any lingering adb servers.
270*9c5db199SXin Li        try:
271*9c5db199SXin Li            self._adb.run(None,
272*9c5db199SXin Li                          verbose=True,
273*9c5db199SXin Li                          args=('kill-server', ),
274*9c5db199SXin Li                          timeout=constants.ADB_KILL_SERVER_TIMEOUT_SECONDS)
275*9c5db199SXin Li        except error.CmdTimeoutError as e:
276*9c5db199SXin Li            logging.warn(e)
277*9c5db199SXin Li            # `adb kill-server` sometimes hangs up. Kill it more brutally.
278*9c5db199SXin Li            try:
279*9c5db199SXin Li                client_utils.system(
280*9c5db199SXin Li                    'killall adb',
281*9c5db199SXin Li                    ignore_status=True,
282*9c5db199SXin Li                    timeout=constants.ADB_KILL_SERVER_TIMEOUT_SECONDS)
283*9c5db199SXin Li            except error.CmdTimeoutError as e:
284*9c5db199SXin Li                # The timeout is ignored, since the only known failure pattern
285*9c5db199SXin Li                # b/142828365 is due to a zombie process that does not prevent
286*9c5db199SXin Li                # starting a new server with a new adb key.
287*9c5db199SXin Li                logging.warn(e)
288*9c5db199SXin Li        except (error.CmdError, AttributeError):
289*9c5db199SXin Li            pass
290*9c5db199SXin Li
291*9c5db199SXin Li    def _verify_hosts(self):
292*9c5db199SXin Li        """Verify all hosts' ChromeOS consistency."""
293*9c5db199SXin Li        # Check release builder path. E.g. cave-release/R66-10435.0.0
294*9c5db199SXin Li        release_builder_path = set(host.get_release_builder_path()
295*9c5db199SXin Li                                   for host in self._hosts)
296*9c5db199SXin Li        if len(release_builder_path) > 1:
297*9c5db199SXin Li            raise error.TestFail('Hosts\' CHROMEOS_RELEASE_BUILDER_PATH is '
298*9c5db199SXin Li                                 'different: %s', release_builder_path)
299*9c5db199SXin Li
300*9c5db199SXin Li        # Check ChromeOS ARC VERSION. E.g.
301*9c5db199SXin Li        arc_version = set(host.get_arc_version() for host in self._hosts)
302*9c5db199SXin Li        if len(arc_version) > 1:
303*9c5db199SXin Li            raise error.TestFail('Hosts\' CHROMEOS_ARC_VERSION is different: '
304*9c5db199SXin Li                                 '%s', arc_version)
305*9c5db199SXin Li
306*9c5db199SXin Li        # Check ChromeOS model for unibuild.
307*9c5db199SXin Li        # TODO(pwang): Adding a check if we found how to detect host's model.
308*9c5db199SXin Li
309*9c5db199SXin Li    def _verify_arc_hosts(self):
310*9c5db199SXin Li        """Verify all hosts' Android configuration consistency.
311*9c5db199SXin Li
312*9c5db199SXin Li        This method should only be called after all hosts' Android has been
313*9c5db199SXin Li        successfully booted up."""
314*9c5db199SXin Li        # Check all hosts have same Android fingerprint.
315*9c5db199SXin Li        fingerprint = set(
316*9c5db199SXin Li                self._adb.run(host,
317*9c5db199SXin Li                              args=('shell', 'getprop',
318*9c5db199SXin Li                                    'ro.build.fingerprint')).stdout
319*9c5db199SXin Li                for host in self._hosts)
320*9c5db199SXin Li        if len(fingerprint) > 1:
321*9c5db199SXin Li            raise error.TestFail('Hosts\' supported fingerprint is different: '
322*9c5db199SXin Li                                 '%s', fingerprint)
323*9c5db199SXin Li
324*9c5db199SXin Li    def _calculate_test_count_factor(self, bundle):
325*9c5db199SXin Li        """ Calculate the multiplicative factor for the test case number.
326*9c5db199SXin Li
327*9c5db199SXin Li        The value equals to the times each test case is run, which is determined
328*9c5db199SXin Li        by the intersection of the supported ABIs of the CTS/GTS bundle and that
329*9c5db199SXin Li        of the tested device."""
330*9c5db199SXin Li        # This is only a conservative approximation. Some suites only run the
331*9c5db199SXin Li        # primary ABI, so to be fully precise, those have to be counted as 1.
332*9c5db199SXin Li        arm_abis = set(('armeabi-v7a', 'arm64-v8a'))
333*9c5db199SXin Li        x86_abis = set(('x86', 'x86_64'))
334*9c5db199SXin Li        if bundle and bundle.startswith('arm'):
335*9c5db199SXin Li            tradefed_abis = arm_abis
336*9c5db199SXin Li        elif bundle and bundle.startswith('x86'):
337*9c5db199SXin Li            tradefed_abis = x86_abis
338*9c5db199SXin Li        else:
339*9c5db199SXin Li            tradefed_abis = arm_abis | x86_abis
340*9c5db199SXin Li        self._test_count_factor = len(set(self._get_abilist()) & tradefed_abis)
341*9c5db199SXin Li        # Avoid setting timeout=0 (None) in any cases.
342*9c5db199SXin Li        self._timeout_factor = max(1, self._test_count_factor)
343*9c5db199SXin Li
344*9c5db199SXin Li    def _try_adb_connect(self, host):
345*9c5db199SXin Li        """Attempts to connect to adb on the DUT.
346*9c5db199SXin Li
347*9c5db199SXin Li        @param host: DUT that need to be connected.
348*9c5db199SXin Li        @return boolean indicating if adb connected successfully.
349*9c5db199SXin Li        """
350*9c5db199SXin Li        # Add ADB_TRACE=all for debugging adb connection failures.
351*9c5db199SXin Li        env = os.environ.copy()
352*9c5db199SXin Li        env['ADB_TRACE'] = 'all'
353*9c5db199SXin Li        try:
354*9c5db199SXin Li            # This may fail return failure due to a race condition in adb
355*9c5db199SXin Li            # connect (b/29370989). If adb is already connected, this command
356*9c5db199SXin Li            # will immediately return success.
357*9c5db199SXin Li            host_port = adb_utils.get_adb_target(host)
358*9c5db199SXin Li            result = self._adb.run(
359*9c5db199SXin Li                    host,
360*9c5db199SXin Li                    args=('connect', host_port),
361*9c5db199SXin Li                    verbose=True,
362*9c5db199SXin Li                    env=env,
363*9c5db199SXin Li                    ignore_status=True,
364*9c5db199SXin Li                    timeout=constants.ADB_CONNECT_TIMEOUT_SECONDS)
365*9c5db199SXin Li            if result.exit_status != 0:
366*9c5db199SXin Li                return False
367*9c5db199SXin Li
368*9c5db199SXin Li            result = self._adb.run(
369*9c5db199SXin Li                    host,
370*9c5db199SXin Li                    args=('devices', ),
371*9c5db199SXin Li                    env=env,
372*9c5db199SXin Li                    timeout=constants.ADB_CONNECT_TIMEOUT_SECONDS)
373*9c5db199SXin Li            if not re.search(r'{}\s+(device|unauthorized)'.format(
374*9c5db199SXin Li                    re.escape(host_port)), result.stdout):
375*9c5db199SXin Li                logging.info('No result found in with pattern: %s',
376*9c5db199SXin Li                             r'{}\s+(device|unauthorized)'.format(
377*9c5db199SXin Li                                 re.escape(host_port)))
378*9c5db199SXin Li                return False
379*9c5db199SXin Li
380*9c5db199SXin Li            # Actually test the connection with an adb command as there can be
381*9c5db199SXin Li            # a race between detecting the connected device and actually being
382*9c5db199SXin Li            # able to run a command with authenticated adb.
383*9c5db199SXin Li            result = self._adb.run(
384*9c5db199SXin Li                    host,
385*9c5db199SXin Li                    args=('shell', 'exit'),
386*9c5db199SXin Li                    env=env,
387*9c5db199SXin Li                    ignore_status=True,
388*9c5db199SXin Li                    timeout=constants.ADB_CONNECT_TIMEOUT_SECONDS)
389*9c5db199SXin Li            return result.exit_status == 0
390*9c5db199SXin Li        except error.CmdTimeoutError as e:
391*9c5db199SXin Li            logging.warning(e)
392*9c5db199SXin Li            return False
393*9c5db199SXin Li
394*9c5db199SXin Li    def _android_shell(self, host, command):
395*9c5db199SXin Li        """Run a command remotely on the device in an android shell
396*9c5db199SXin Li
397*9c5db199SXin Li        This function is strictly for internal use only, as commands do not run
398*9c5db199SXin Li        in a fully consistent Android environment. Prefer adb shell instead.
399*9c5db199SXin Li        """
400*9c5db199SXin Li        host.run('android-sh -c ' + pipes.quote(command))
401*9c5db199SXin Li
402*9c5db199SXin Li    def _connect_adb(self, host):
403*9c5db199SXin Li        """Sets up ADB connection to the ARC container.
404*9c5db199SXin Li
405*9c5db199SXin Li        @param host: DUT that should be connected to.
406*9c5db199SXin Li        """
407*9c5db199SXin Li        logging.info('Setting up adb connection.')
408*9c5db199SXin Li
409*9c5db199SXin Li        # adbd may take some time to come up. Repeatedly try to connect to adb.
410*9c5db199SXin Li        utils.poll_for_condition(
411*9c5db199SXin Li            lambda: self._try_adb_connect(host),
412*9c5db199SXin Li            timeout=constants.ADB_READY_TIMEOUT_SECONDS,
413*9c5db199SXin Li            sleep_interval=constants.ADB_POLLING_INTERVAL_SECONDS)
414*9c5db199SXin Li
415*9c5db199SXin Li        logging.info('Successfully setup adb connection.')
416*9c5db199SXin Li
417*9c5db199SXin Li    def _wait_for_arc_boot(self, host):
418*9c5db199SXin Li        """Wait until ARC is fully booted.
419*9c5db199SXin Li
420*9c5db199SXin Li        Tests for the presence of the intent helper app to determine whether ARC
421*9c5db199SXin Li        has finished booting.
422*9c5db199SXin Li        @param host: DUT that need to be connected to.
423*9c5db199SXin Li        """
424*9c5db199SXin Li
425*9c5db199SXin Li        def _intent_helper_running():
426*9c5db199SXin Li            result = self._adb.run(host,
427*9c5db199SXin Li                                   args=('shell', 'pgrep', '-f',
428*9c5db199SXin Li                                         'org.chromium.arc.intent_helper'),
429*9c5db199SXin Li                                   ignore_status=True)
430*9c5db199SXin Li            return bool(result.stdout)
431*9c5db199SXin Li
432*9c5db199SXin Li        utils.poll_for_condition(
433*9c5db199SXin Li            _intent_helper_running,
434*9c5db199SXin Li            exception=error.TestFail(
435*9c5db199SXin Li                'Error: Timed out waiting for intent helper.'),
436*9c5db199SXin Li            timeout=constants.ARC_READY_TIMEOUT_SECONDS,
437*9c5db199SXin Li            sleep_interval=constants.ARC_POLLING_INTERVAL_SECONDS)
438*9c5db199SXin Li
439*9c5db199SXin Li    def _disable_adb_install_dialog(self, host):
440*9c5db199SXin Li        """Disables a dialog shown on adb install execution.
441*9c5db199SXin Li
442*9c5db199SXin Li        By default, on adb install execution, "Allow Google to regularly check
443*9c5db199SXin Li        device activity ... " dialog is shown. It requires manual user action
444*9c5db199SXin Li        so that tests are blocked at the point.
445*9c5db199SXin Li        This method disables it.
446*9c5db199SXin Li        """
447*9c5db199SXin Li        logging.info('Disabling the adb install dialog.')
448*9c5db199SXin Li        result = self._adb.run(host,
449*9c5db199SXin Li                               verbose=True,
450*9c5db199SXin Li                               args=('shell', 'settings', 'put', 'global',
451*9c5db199SXin Li                                     'verifier_verify_adb_installs', '0'))
452*9c5db199SXin Li        logging.info('Disable adb dialog: %s', result.stdout)
453*9c5db199SXin Li
454*9c5db199SXin Li        # Android "RescueParty" feature can reset the above settings when the
455*9c5db199SXin Li        # device crashes often. Disable the rescue during testing.
456*9c5db199SXin Li        # Keeping only for P and below since R has SELinux restrictions.
457*9c5db199SXin Li        if self._get_android_version() < 29:
458*9c5db199SXin Li            self._android_shell(host, 'setprop persist.sys.disable_rescue true')
459*9c5db199SXin Li
460*9c5db199SXin Li    def _ready_arc(self):
461*9c5db199SXin Li        """Ready ARC and adb in parallel for running tests via tradefed."""
462*9c5db199SXin Li        key_path = os.path.join(self.tmpdir, 'test_key')
463*9c5db199SXin Li        with open(key_path, 'w') as f:
464*9c5db199SXin Li            f.write(constants.PRIVATE_KEY)
465*9c5db199SXin Li        os.environ['ADB_VENDOR_KEYS'] = key_path
466*9c5db199SXin Li
467*9c5db199SXin Li        for _ in range(2):
468*9c5db199SXin Li            try:
469*9c5db199SXin Li                # Kill existing adb server to ensure that the env var is picked
470*9c5db199SXin Li                # up, and reset any previous bad state.
471*9c5db199SXin Li                self._kill_adb_server()
472*9c5db199SXin Li
473*9c5db199SXin Li                # TODO(pwang): connect_adb takes 10+ seconds on a single DUT.
474*9c5db199SXin Li                #              Parallelize it if it becomes a bottleneck.
475*9c5db199SXin Li                for host in self._hosts:
476*9c5db199SXin Li                    self._connect_adb(host)
477*9c5db199SXin Li                    self._disable_adb_install_dialog(host)
478*9c5db199SXin Li                    self._wait_for_arc_boot(host)
479*9c5db199SXin Li                self._verify_arc_hosts()
480*9c5db199SXin Li                return
481*9c5db199SXin Li            except (utils.TimeoutError, error.CmdTimeoutError):
482*9c5db199SXin Li                logging.error('Failed to set up adb connection. Retrying...')
483*9c5db199SXin Li        raise error.TestFail('Error: Failed to set up adb connection')
484*9c5db199SXin Li
485*9c5db199SXin Li    def _safe_makedirs(self, path):
486*9c5db199SXin Li        """Creates a directory at |path| and its ancestors.
487*9c5db199SXin Li
488*9c5db199SXin Li        Unlike os.makedirs(), ignore errors even if directories exist.
489*9c5db199SXin Li        """
490*9c5db199SXin Li        try:
491*9c5db199SXin Li            os.makedirs(path)
492*9c5db199SXin Li        except OSError as e:
493*9c5db199SXin Li            if not (e.errno == errno.EEXIST and os.path.isdir(path)):
494*9c5db199SXin Li                raise
495*9c5db199SXin Li
496*9c5db199SXin Li    def _unzip(self, filename):
497*9c5db199SXin Li        """Unzip the file.
498*9c5db199SXin Li
499*9c5db199SXin Li        The destination directory name will be the stem of filename.
500*9c5db199SXin Li        E.g., _unzip('foo/bar/baz.zip') will create directory at
501*9c5db199SXin Li        'foo/bar/baz', and then will inflate zip's content under the directory.
502*9c5db199SXin Li        If here is already a directory at the stem, that directory will be used.
503*9c5db199SXin Li
504*9c5db199SXin Li        @param filename: Path to the zip archive.
505*9c5db199SXin Li        @return Path to the inflated directory.
506*9c5db199SXin Li        """
507*9c5db199SXin Li        destination = os.path.splitext(filename)[0]
508*9c5db199SXin Li        if os.path.isdir(destination):
509*9c5db199SXin Li            logging.info('Skipping unzip %s, reusing content of %s', filename,
510*9c5db199SXin Li                         destination)
511*9c5db199SXin Li            return destination
512*9c5db199SXin Li        tmp = tempfile.mkdtemp(dir=os.path.dirname(filename))
513*9c5db199SXin Li        logging.info('Begin unzip %s', filename)
514*9c5db199SXin Li        try:
515*9c5db199SXin Li            utils.run('unzip', args=('-d', tmp, filename))
516*9c5db199SXin Li        except:
517*9c5db199SXin Li            logging.error('Failed unzip, cleaning up.')
518*9c5db199SXin Li            # Clean up just created files.
519*9c5db199SXin Li            shutil.rmtree(tmp, ignore_errors=True)
520*9c5db199SXin Li            raise
521*9c5db199SXin Li        logging.info('End unzip %s', filename)
522*9c5db199SXin Li        try:
523*9c5db199SXin Li            os.renames(tmp, destination)
524*9c5db199SXin Li        except:
525*9c5db199SXin Li            logging.error('Failed rename, cleaning up.')
526*9c5db199SXin Li            shutil.rmtree(destination, ignore_errors=True)
527*9c5db199SXin Li            shutil.rmtree(tmp, ignore_errors=True)
528*9c5db199SXin Li            raise
529*9c5db199SXin Li        return destination
530*9c5db199SXin Li
531*9c5db199SXin Li    def _dir_size(self, directory):
532*9c5db199SXin Li        """Compute recursive size in bytes of directory."""
533*9c5db199SXin Li        size = 0
534*9c5db199SXin Li        for root, _, files in os.walk(directory):
535*9c5db199SXin Li            for name in files:
536*9c5db199SXin Li                try:
537*9c5db199SXin Li                    size += os.path.getsize(os.path.join(root, name))
538*9c5db199SXin Li                except OSError:
539*9c5db199SXin Li                    logging.error('Inaccessible path (crbug/793696): %s/%s',
540*9c5db199SXin Li                                  root, name)
541*9c5db199SXin Li        return size
542*9c5db199SXin Li
543*9c5db199SXin Li    def _invalidate_download_cache(self):
544*9c5db199SXin Li        """Marks the download cache for deferred deletion.
545*9c5db199SXin Li
546*9c5db199SXin Li        Used to make cache file operations atomic across failures and reboots.
547*9c5db199SXin Li        The caller is responsible to hold the lock to the cache.
548*9c5db199SXin Li        """
549*9c5db199SXin Li        if not os.path.exists(self._tradefed_cache_dirty):
550*9c5db199SXin Li            os.mkdir(self._tradefed_cache_dirty)
551*9c5db199SXin Li
552*9c5db199SXin Li    def _validate_download_cache(self):
553*9c5db199SXin Li        """Validates and unmarks the download cache from deletion.
554*9c5db199SXin Li
555*9c5db199SXin Li        Used to make cache file operations atomic across failures and reboots.
556*9c5db199SXin Li        The caller is responsible to hold the lock to the cache.
557*9c5db199SXin Li        """
558*9c5db199SXin Li        shutil.rmtree(self._tradefed_cache_dirty, ignore_errors=True)
559*9c5db199SXin Li
560*9c5db199SXin Li    def _clean_download_cache_if_needed(self, force=False):
561*9c5db199SXin Li        """Invalidates cache to prevent it from growing too large."""
562*9c5db199SXin Li        # If the cache is large enough to hold a working set, we can simply
563*9c5db199SXin Li        # delete everything without thrashing.
564*9c5db199SXin Li        # TODO(ihf): Investigate strategies like LRU.
565*9c5db199SXin Li        clean = force
566*9c5db199SXin Li        with tradefed_utils.lock(self._tradefed_cache_lock):
567*9c5db199SXin Li            size = self._dir_size(self._tradefed_cache)
568*9c5db199SXin Li            if size > constants.TRADEFED_CACHE_MAX_SIZE:
569*9c5db199SXin Li                logging.info(
570*9c5db199SXin Li                    'Current cache size=%d got too large. Clearing %s.', size,
571*9c5db199SXin Li                    self._tradefed_cache)
572*9c5db199SXin Li                clean = True
573*9c5db199SXin Li            else:
574*9c5db199SXin Li                logging.info('Current cache size=%d of %s.', size,
575*9c5db199SXin Li                             self._tradefed_cache)
576*9c5db199SXin Li            if os.path.exists(self._tradefed_cache_dirty):
577*9c5db199SXin Li                logging.info('Found dirty cache.')
578*9c5db199SXin Li                clean = True
579*9c5db199SXin Li            if clean:
580*9c5db199SXin Li                logging.warning('Cleaning download cache.')
581*9c5db199SXin Li                shutil.rmtree(self._tradefed_cache, ignore_errors=True)
582*9c5db199SXin Li                self._safe_makedirs(self._tradefed_cache)
583*9c5db199SXin Li                shutil.rmtree(self._tradefed_cache_dirty, ignore_errors=True)
584*9c5db199SXin Li
585*9c5db199SXin Li    def _download_to_cache(self, uri):
586*9c5db199SXin Li        """Downloads the uri from the storage server.
587*9c5db199SXin Li
588*9c5db199SXin Li        It always checks the cache for available binaries first and skips
589*9c5db199SXin Li        download if binaries are already in cache.
590*9c5db199SXin Li
591*9c5db199SXin Li        The caller of this function is responsible for holding the cache lock.
592*9c5db199SXin Li
593*9c5db199SXin Li        @param uri: The Google Storage, dl.google.com or local uri.
594*9c5db199SXin Li        @return Path to the downloaded object, name.
595*9c5db199SXin Li        """
596*9c5db199SXin Li        # We are hashing the uri instead of the binary. This is acceptable, as
597*9c5db199SXin Li        # the uris are supposed to contain version information and an object is
598*9c5db199SXin Li        # not supposed to be changed once created.
599*9c5db199SXin Li        output_dir = os.path.join(self._tradefed_cache,
600*9c5db199SXin Li                                  hashlib.md5(uri.encode()).hexdigest())
601*9c5db199SXin Li        # Check for existence of cache entry. We check for directory existence
602*9c5db199SXin Li        # instead of file existence, so that _install_bundle can delete original
603*9c5db199SXin Li        # zip files to save disk space.
604*9c5db199SXin Li        if os.path.exists(output_dir):
605*9c5db199SXin Li            # TODO(crbug.com/800657): Mitigation for the invalid state. Normally
606*9c5db199SXin Li            # this should not happen, but when a lock is force borken due to
607*9c5db199SXin Li            # high IO load, multiple processes may enter the critical section
608*9c5db199SXin Li            # and leave a bad state permanently.
609*9c5db199SXin Li            if os.listdir(output_dir):
610*9c5db199SXin Li                logging.info('Skipping download of %s, reusing content of %s.',
611*9c5db199SXin Li                             uri, output_dir)
612*9c5db199SXin Li                return os.path.join(output_dir,
613*9c5db199SXin Li                    os.path.basename(urlparse.urlparse(uri).path))
614*9c5db199SXin Li            logging.error('Empty cache entry detected %s', output_dir)
615*9c5db199SXin Li        return self._download_to_dir(uri, output_dir)
616*9c5db199SXin Li
617*9c5db199SXin Li    def _download_to_dir(self, uri, output_dir):
618*9c5db199SXin Li        """Downloads the gs|http|https|file uri from the storage server.
619*9c5db199SXin Li
620*9c5db199SXin Li        @param uri: The Google Storage, dl.google.com or local uri.
621*9c5db199SXin Li        @output_dir: The directory where the downloaded file should be placed.
622*9c5db199SXin Li        @return Path to the downloaded object, name.
623*9c5db199SXin Li        """
624*9c5db199SXin Li        # Split uri into 3 pieces for use by gsutil and also by wget.
625*9c5db199SXin Li        parsed = urlparse.urlparse(uri)
626*9c5db199SXin Li        filename = os.path.basename(parsed.path)
627*9c5db199SXin Li        output = os.path.join(output_dir, filename)
628*9c5db199SXin Li
629*9c5db199SXin Li        self._safe_makedirs(output_dir)
630*9c5db199SXin Li        if parsed.scheme not in ['gs', 'http', 'https', 'file']:
631*9c5db199SXin Li            raise error.TestFail(
632*9c5db199SXin Li                'Error: Unknown download scheme %s' % parsed.scheme)
633*9c5db199SXin Li        if parsed.scheme in ['http', 'https']:
634*9c5db199SXin Li            logging.info('Using wget to download %s to %s.', uri, output_dir)
635*9c5db199SXin Li            # We are downloading 1 file at a time, hence using -O over -P.
636*9c5db199SXin Li            utils.run(
637*9c5db199SXin Li                'wget',
638*9c5db199SXin Li                args=('--report-speed=bits', '-O', output, uri),
639*9c5db199SXin Li                verbose=True)
640*9c5db199SXin Li            return output
641*9c5db199SXin Li
642*9c5db199SXin Li        if parsed.scheme in ['file']:
643*9c5db199SXin Li            logging.info('Copy the local file from %s to %s.', parsed.path,
644*9c5db199SXin Li                         output_dir)
645*9c5db199SXin Li            utils.run(
646*9c5db199SXin Li                'cp',
647*9c5db199SXin Li                args=('-f', parsed.path, output),
648*9c5db199SXin Li                verbose=True)
649*9c5db199SXin Li            return output
650*9c5db199SXin Li
651*9c5db199SXin Li        # If the machine can access to the storage server directly,
652*9c5db199SXin Li        # defer to "gsutil" for downloading.
653*9c5db199SXin Li        logging.info('Downloading %s directly to %s.', uri, output)
654*9c5db199SXin Li        # b/17445576: gsutil rsync of individual files is not implemented.
655*9c5db199SXin Li        res = utils.run('gsutil',
656*9c5db199SXin Li                        args=('cp', uri, output),
657*9c5db199SXin Li                        verbose=True,
658*9c5db199SXin Li                        ignore_status=True)
659*9c5db199SXin Li        if not res or res.exit_status != 0:
660*9c5db199SXin Li            logging.warning('Retrying download...')
661*9c5db199SXin Li            utils.run('gsutil', args=('cp', uri, output), verbose=True)
662*9c5db199SXin Li        return output
663*9c5db199SXin Li
664*9c5db199SXin Li    def _instance_copyfile(self, cache_path):
665*9c5db199SXin Li        """Makes a copy of a file from the (shared) cache to a wholy owned
666*9c5db199SXin Li        local instance. Also copies one level of cache directoy (MD5 named).
667*9c5db199SXin Li        """
668*9c5db199SXin Li        filename = os.path.basename(cache_path)
669*9c5db199SXin Li        dirname = os.path.basename(os.path.dirname(cache_path))
670*9c5db199SXin Li        instance_dir = os.path.join(self._tradefed_install, dirname)
671*9c5db199SXin Li        # Make sure destination directory is named the same.
672*9c5db199SXin Li        self._safe_makedirs(instance_dir)
673*9c5db199SXin Li        instance_path = os.path.join(instance_dir, filename)
674*9c5db199SXin Li        shutil.copyfile(cache_path, instance_path)
675*9c5db199SXin Li        return instance_path
676*9c5db199SXin Li
677*9c5db199SXin Li    def _instance_copytree(self, cache_path):
678*9c5db199SXin Li        """Makes a copy of a directory from the (shared and writable) cache to
679*9c5db199SXin Li        a wholy owned local instance.
680*9c5db199SXin Li
681*9c5db199SXin Li        TODO(ihf): Consider using cp -al to only copy links. Not sure if this
682*9c5db199SXin Li        is really a benefit across the container boundary, but it is risky due
683*9c5db199SXin Li        to the possibility of corrupting the original files by an lxc instance.
684*9c5db199SXin Li        """
685*9c5db199SXin Li        # We keep the top 2 names from the cache_path = .../dir1/dir2.
686*9c5db199SXin Li        dir2 = os.path.basename(cache_path)
687*9c5db199SXin Li        dir1 = os.path.basename(os.path.dirname(cache_path))
688*9c5db199SXin Li        instance_path = os.path.join(self._tradefed_install, dir1, dir2)
689*9c5db199SXin Li        # TODO(kinaba): Fix in a safer way.
690*9c5db199SXin Li        # Below is a workaround to avoid copying large CTS/GTS tree in test lab.
691*9c5db199SXin Li        # Contents of $cache_path/android-cts are symlinked to the destination
692*9c5db199SXin Li        # rather than copied.
693*9c5db199SXin Li        #  1) Why not symlink 'android-cts' itself? Because the tests will
694*9c5db199SXin Li        #     create results/ logs/ subplans/ subdirectory there. We do not
695*9c5db199SXin Li        #     want to write to the shared cache.
696*9c5db199SXin Li        #  2) Why not hardlink? Cache and the local directory may be on
697*9c5db199SXin Li        #     different mount points, so hardlink may not work.
698*9c5db199SXin Li        #  3) Why this isn't safe? Cache is cleared when it became full, even
699*9c5db199SXin Li        #     during the test is run on an instance.
700*9c5db199SXin Li        #  4) Why this is acceptable despite the unsatefy? Cache clearance is
701*9c5db199SXin Li        #     a rare event (once in 6 months). Skylab drones won't usually
702*9c5db199SXin Li        #     live that long, and even if it did, the failure is once in 6
703*9c5db199SXin Li        #     months after all.
704*9c5db199SXin Li        special_src = None
705*9c5db199SXin Li        special_dest = None
706*9c5db199SXin Li        if utils.is_in_container() and not client_utils.is_moblab():
707*9c5db199SXin Li            for xts_name in ['android-cts', 'android-gts', 'android-sts']:
708*9c5db199SXin Li                xts_root = os.path.join(cache_path, xts_name)
709*9c5db199SXin Li                if os.path.exists(xts_root):
710*9c5db199SXin Li                    special_src = xts_root
711*9c5db199SXin Li                    special_dest = os.path.join(instance_path, xts_name)
712*9c5db199SXin Li                    break
713*9c5db199SXin Li        if special_src:
714*9c5db199SXin Li            logging.info('SYMLINK&COPY contents of %s to instance %s',
715*9c5db199SXin Li                         cache_path, instance_path)
716*9c5db199SXin Li            self._safe_makedirs(special_dest)
717*9c5db199SXin Li            for entry in os.listdir(special_src):
718*9c5db199SXin Li                # Subdirectories are created by relative path from
719*9c5db199SXin Li                # tools/cts_tradefed. So for 'tools' dir we copy.
720*9c5db199SXin Li                if entry == 'tools':
721*9c5db199SXin Li                    shutil.copytree(os.path.join(special_src, entry),
722*9c5db199SXin Li                                    os.path.join(special_dest, entry))
723*9c5db199SXin Li                elif entry == 'testcases':
724*9c5db199SXin Li                    # Directory structure in testcases/ needs to be
725*9c5db199SXin Li                    # instantiated, because CTS tries `find` command
726*9c5db199SXin Li                    # in the directory without following symlinks
727*9c5db199SXin Li                    for subdir, _, files in os.walk(
728*9c5db199SXin Li                            os.path.join(special_src, entry)):
729*9c5db199SXin Li                        rel = os.path.relpath(subdir, special_src)
730*9c5db199SXin Li                        os.mkdir(os.path.join(special_dest, rel))
731*9c5db199SXin Li                        for file in files:
732*9c5db199SXin Li                            os.symlink(os.path.join(special_src, rel, file),
733*9c5db199SXin Li                                       os.path.join(special_dest, rel, file))
734*9c5db199SXin Li                else:
735*9c5db199SXin Li                    os.symlink(os.path.join(special_src, entry),
736*9c5db199SXin Li                               os.path.join(special_dest, entry))
737*9c5db199SXin Li        else:
738*9c5db199SXin Li            logging.info('Copying %s to instance %s', cache_path,
739*9c5db199SXin Li                         instance_path)
740*9c5db199SXin Li            shutil.copytree(cache_path, instance_path)
741*9c5db199SXin Li        return instance_path
742*9c5db199SXin Li
743*9c5db199SXin Li    def _install_bundle(self, gs_uri):
744*9c5db199SXin Li        """Downloads a zip file, installs it and returns the local path.
745*9c5db199SXin Li
746*9c5db199SXin Li        @param gs_uri: GS bucket that contains the necessary files.
747*9c5db199SXin Li        """
748*9c5db199SXin Li        if not gs_uri.endswith('.zip'):
749*9c5db199SXin Li            raise error.TestFail('Error: Not a .zip file %s.', gs_uri)
750*9c5db199SXin Li        # Atomic write through of file.
751*9c5db199SXin Li        with tradefed_utils.lock(self._tradefed_cache_lock):
752*9c5db199SXin Li            # Atomic operations.
753*9c5db199SXin Li            self._invalidate_download_cache()
754*9c5db199SXin Li            # Download is lazy (cache_path may not actually exist if
755*9c5db199SXin Li            # cache_unzipped does).
756*9c5db199SXin Li            cache_path = self._download_to_cache(gs_uri)
757*9c5db199SXin Li            # Unzip is lazy as well (but cache_unzipped guaranteed to
758*9c5db199SXin Li            # exist).
759*9c5db199SXin Li            cache_unzipped = self._unzip(cache_path)
760*9c5db199SXin Li            # To save space we delete the original zip file. This works as
761*9c5db199SXin Li            # _download only checks existence of the cache directory for
762*9c5db199SXin Li            # lazily skipping download, and unzip itself will bail if the
763*9c5db199SXin Li            # unzipped destination exists. Hence we don't need the original
764*9c5db199SXin Li            # anymore.
765*9c5db199SXin Li            if os.path.exists(cache_path):
766*9c5db199SXin Li                logging.info('Deleting original %s', cache_path)
767*9c5db199SXin Li                os.remove(cache_path)
768*9c5db199SXin Li            # Erase dirty marker from disk.
769*9c5db199SXin Li            self._validate_download_cache()
770*9c5db199SXin Li            # We always copy files to give tradefed a clean copy of the
771*9c5db199SXin Li            # bundle.
772*9c5db199SXin Li            unzipped_local = self._instance_copytree(cache_unzipped)
773*9c5db199SXin Li        return unzipped_local
774*9c5db199SXin Li
775*9c5db199SXin Li    def _install_files(self, gs_dir, files, permission):
776*9c5db199SXin Li        """Installs binary tools."""
777*9c5db199SXin Li        for filename in files:
778*9c5db199SXin Li            gs_uri = os.path.join(gs_dir, filename)
779*9c5db199SXin Li            # Atomic write through of file.
780*9c5db199SXin Li            with tradefed_utils.lock(self._tradefed_cache_lock):
781*9c5db199SXin Li                # We don't want to leave a corrupt cache for other jobs.
782*9c5db199SXin Li                self._invalidate_download_cache()
783*9c5db199SXin Li                cache_path = self._download_to_cache(gs_uri)
784*9c5db199SXin Li                # Mark cache as clean again.
785*9c5db199SXin Li                self._validate_download_cache()
786*9c5db199SXin Li                # This only affects the current job, so not part of cache
787*9c5db199SXin Li                # validation.
788*9c5db199SXin Li                local = self._instance_copyfile(cache_path)
789*9c5db199SXin Li            os.chmod(local, permission)
790*9c5db199SXin Li            # Keep track of PATH.
791*9c5db199SXin Li            local_dir = os.path.dirname(local)
792*9c5db199SXin Li            self._install_paths.append(local_dir)
793*9c5db199SXin Li            self._adb.add_path(local_dir)
794*9c5db199SXin Li
795*9c5db199SXin Li    def _prepare_media(self, media_asset):
796*9c5db199SXin Li        """Downloads and offers the cached media files to tradefed."""
797*9c5db199SXin Li        if media_asset.uri:
798*9c5db199SXin Li            media = self._install_bundle(media_asset.uri)
799*9c5db199SXin Li            if os.path.islink(media_asset.localpath):
800*9c5db199SXin Li                os.unlink(media_asset.localpath)
801*9c5db199SXin Li            if os.path.isdir(media_asset.localpath):
802*9c5db199SXin Li                shutil.rmtree(media_asset.localpath)
803*9c5db199SXin Li            self._safe_makedirs(os.path.dirname(media_asset.localpath))
804*9c5db199SXin Li            os.symlink(media, media_asset.localpath)
805*9c5db199SXin Li
806*9c5db199SXin Li            logging.info('Offered %s as a media directory in %s',
807*9c5db199SXin Li                    media, media_asset.localpath)
808*9c5db199SXin Li
809*9c5db199SXin Li        # Records the number of existing media bundles, to check later.
810*9c5db199SXin Li        if os.path.isdir(media_asset.localpath):
811*9c5db199SXin Li            self._num_media_bundles = len(
812*9c5db199SXin Li                    os.listdir(media_asset.localpath))
813*9c5db199SXin Li
814*9c5db199SXin Li    def _cleanup_media(self, media_asset):
815*9c5db199SXin Li        """Clean up the local copy of cached media files."""
816*9c5db199SXin Li        self._fail_on_unexpected_media_download(media_asset)
817*9c5db199SXin Li        if os.path.islink(media_asset.localpath):
818*9c5db199SXin Li            path = os.readlink(media_asset.localpath)
819*9c5db199SXin Li            os.unlink(media_asset.localpath)
820*9c5db199SXin Li            if os.path.isdir(path):
821*9c5db199SXin Li                logging.info('Cleaning up media files in %s', path)
822*9c5db199SXin Li                shutil.rmtree(path)
823*9c5db199SXin Li
824*9c5db199SXin Li    def _fail_on_unexpected_media_download(self, media_asset):
825*9c5db199SXin Li        if os.path.isdir(media_asset.localpath):
826*9c5db199SXin Li            contents = os.listdir(media_asset.localpath)
827*9c5db199SXin Li            # Ignore a table-of-contents file created by newer xTS
828*9c5db199SXin Li            TOC_FILE = 'contents.toc'
829*9c5db199SXin Li            if TOC_FILE in contents:
830*9c5db199SXin Li                contents.remove(TOC_FILE)
831*9c5db199SXin Li            if len(contents) > self._num_media_bundles:
832*9c5db199SXin Li                raise error.TestFail(
833*9c5db199SXin Li                    'Failed: Unexpected media bundle was added %s' % contents)
834*9c5db199SXin Li
835*9c5db199SXin Li    def _should_push_mediastress_asset(self, target_module, board):
836*9c5db199SXin Li        """Returns whether we should manually push mediastress assets.
837*9c5db199SXin Li
838*9c5db199SXin Li        TODO(b/210801048): Remove this workaround once ARCVM storage performance
839*9c5db199SXin Li        on ARM becomes good enough.
840*9c5db199SXin Li        """
841*9c5db199SXin Li        return (target_module and 'CtsMediaStressTestCases' in target_module
842*9c5db199SXin Li                and board in ['kukui-arc-r'])
843*9c5db199SXin Li
844*9c5db199SXin Li    def _push_mediastress_asset(self, media_asset):
845*9c5db199SXin Li        """Pushes mediastress assets to the DUT for the upcoming test."""
846*9c5db199SXin Li        logging.info(
847*9c5db199SXin Li                'Pushing mediastress assets in advance to workaround slow '
848*9c5db199SXin Li                'storage on ARM boards (b/210801048)')
849*9c5db199SXin Li
850*9c5db199SXin Li        media_dir = os.path.join(media_asset.localpath,
851*9c5db199SXin Li                                 'android-cts-media-1.5')
852*9c5db199SXin Li        copy_media_sh = os.path.join(media_dir, 'copy_media.sh')
853*9c5db199SXin Li        os.chmod(copy_media_sh, 0o755)
854*9c5db199SXin Li
855*9c5db199SXin Li        old_cwd = os.getcwd()
856*9c5db199SXin Li        os.chdir(media_dir)
857*9c5db199SXin Li        try:
858*9c5db199SXin Li            for host in self._hosts:
859*9c5db199SXin Li                host_port = adb_utils.get_adb_target(host)
860*9c5db199SXin Li                self._run(
861*9c5db199SXin Li                        copy_media_sh,
862*9c5db199SXin Li                        args=('all', '-s', host_port),
863*9c5db199SXin Li                        timeout=constants.ADB_PUSH_MEDIASTRESS_TIMEOUT_SECONDS,
864*9c5db199SXin Li                        verbose=True,
865*9c5db199SXin Li                        ignore_status=False,
866*9c5db199SXin Li                        stdout_tee=utils.TEE_TO_LOGS,
867*9c5db199SXin Li                        stderr_tee=utils.TEE_TO_LOGS)
868*9c5db199SXin Li        finally:
869*9c5db199SXin Li            os.chdir(old_cwd)
870*9c5db199SXin Li
871*9c5db199SXin Li    def _fetch_helpers_from_dut(self):
872*9c5db199SXin Li        """Fetches the CTS helpers from the dut and installs into the testcases
873*9c5db199SXin Li           subdirectory of our local autotest copy.
874*9c5db199SXin Li        """
875*9c5db199SXin Li        tf_testcases = os.path.join(self._repository, 'testcases')
876*9c5db199SXin Li
877*9c5db199SXin Li        # Earlier checks enforce that each host has the same build fingerprint,
878*9c5db199SXin Li        # so we can assume that the packages from the first host will work
879*9c5db199SXin Li        # across the whole set.
880*9c5db199SXin Li        package_list = self._adb.run(
881*9c5db199SXin Li                self._hosts[0],
882*9c5db199SXin Li                args=('shell', 'getprop',
883*9c5db199SXin Li                      constants.TRADEFED_CTS_HELPERS_PROPERTY)).stdout.strip()
884*9c5db199SXin Li        for pkg in package_list.split(':'):
885*9c5db199SXin Li            if not pkg:
886*9c5db199SXin Li                continue
887*9c5db199SXin Li            apk_name = pkg + '.apk'
888*9c5db199SXin Li            logging.info('Installing CTS helper package %s to %s', apk_name,
889*9c5db199SXin Li                         tf_testcases)
890*9c5db199SXin Li            self._hosts[0].get_file(
891*9c5db199SXin Li                    os.path.join(constants.BOARD_CTS_HELPERS_DIR, apk_name),
892*9c5db199SXin Li                    tf_testcases)
893*9c5db199SXin Li
894*9c5db199SXin Li    def _run(self, *args, **kwargs):
895*9c5db199SXin Li        """Executes the given command line.
896*9c5db199SXin Li
897*9c5db199SXin Li        To support SDK tools, such as adb or aapt, this adds _install_paths
898*9c5db199SXin Li        to the extra_paths. Before invoking this, ensure _install_files() has
899*9c5db199SXin Li        been called.
900*9c5db199SXin Li        """
901*9c5db199SXin Li        kwargs['extra_paths'] = (
902*9c5db199SXin Li            kwargs.get('extra_paths', []) + self._install_paths)
903*9c5db199SXin Li        return utils.run(*args, **kwargs)
904*9c5db199SXin Li
905*9c5db199SXin Li    def _collect_tradefed_global_log(self, result, destination):
906*9c5db199SXin Li        """Collects the tradefed global log.
907*9c5db199SXin Li
908*9c5db199SXin Li        @param result: The result object from utils.run.
909*9c5db199SXin Li        @param destination: Autotest result directory (destination of logs).
910*9c5db199SXin Li        """
911*9c5db199SXin Li        match = re.search(r'Saved log to /tmp/(tradefed_global_log_.*\.txt)',
912*9c5db199SXin Li                          result.stdout)
913*9c5db199SXin Li        if not match:
914*9c5db199SXin Li            logging.debug(result.stdout)
915*9c5db199SXin Li            logging.error('no tradefed_global_log file is found')
916*9c5db199SXin Li            return
917*9c5db199SXin Li
918*9c5db199SXin Li        name = match.group(1)
919*9c5db199SXin Li        dest = os.path.join(destination, 'logs', 'tmp')
920*9c5db199SXin Li        self._safe_makedirs(dest)
921*9c5db199SXin Li        shutil.copy(os.path.join('/tmp', name), os.path.join(dest, name))
922*9c5db199SXin Li
923*9c5db199SXin Li    def _get_expected_failures(self, directory, bundle_abi):
924*9c5db199SXin Li        """Return a list of expected failures or no test module.
925*9c5db199SXin Li
926*9c5db199SXin Li        @param directory: A directory with expected no tests or failures files.
927*9c5db199SXin Li        @param bundle_abi: 'arm' or 'x86' if the test is for the particular ABI.
928*9c5db199SXin Li                           None otherwise (like GTS, built for multi-ABI.)
929*9c5db199SXin Li        @return: A list of expected failures or no test modules for the current
930*9c5db199SXin Li                 testing device.
931*9c5db199SXin Li        """
932*9c5db199SXin Li        # Load waivers and manual tests so TF doesn't re-run them.
933*9c5db199SXin Li        expected_fail_files = []
934*9c5db199SXin Li        test_board = self._get_board_name()
935*9c5db199SXin Li        test_model = self._get_model_name()
936*9c5db199SXin Li        test_arch = self._get_board_arch()
937*9c5db199SXin Li        sdk_ver = self._get_android_version()
938*9c5db199SXin Li        first_api_level = self._get_first_api_level()
939*9c5db199SXin Li        expected_fail_dir = os.path.join(self.bindir, directory)
940*9c5db199SXin Li        if os.path.exists(expected_fail_dir):
941*9c5db199SXin Li            expected_fail_files += glob.glob(expected_fail_dir + '/*.yaml')
942*9c5db199SXin Li
943*9c5db199SXin Li        waivers = cts_expected_failure_parser.ParseKnownCTSFailures(
944*9c5db199SXin Li            expected_fail_files)
945*9c5db199SXin Li        return waivers.find_waivers(test_arch, test_board, test_model,
946*9c5db199SXin Li                                    bundle_abi, sdk_ver, first_api_level,
947*9c5db199SXin Li                                    self._hosts[0])
948*9c5db199SXin Li
949*9c5db199SXin Li    def _get_abilist(self):
950*9c5db199SXin Li        """Return the abilist supported by calling adb command.
951*9c5db199SXin Li
952*9c5db199SXin Li        This method should only be called after the android environment is
953*9c5db199SXin Li        successfully initialized."""
954*9c5db199SXin Li        if not self._abilist:
955*9c5db199SXin Li            for _ in range(3):
956*9c5db199SXin Li                abilist_str = self._adb.run(
957*9c5db199SXin Li                        self._hosts[0],
958*9c5db199SXin Li                        args=('shell', 'getprop',
959*9c5db199SXin Li                              'ro.product.cpu.abilist')).stdout.strip()
960*9c5db199SXin Li                if abilist_str:
961*9c5db199SXin Li                    self._abilist = abilist_str.split(',')
962*9c5db199SXin Li                    break
963*9c5db199SXin Li                else:
964*9c5db199SXin Li                    # TODO(kinaba): Sometimes getprop returns an empty string.
965*9c5db199SXin Li                    # Investigate why. For now we mitigate the bug by retries.
966*9c5db199SXin Li                    logging.error('Empty abilist.')
967*9c5db199SXin Li        return self._abilist
968*9c5db199SXin Li
969*9c5db199SXin Li    def _get_release_branch_number(self):
970*9c5db199SXin Li        """Returns the DUT branch number (z of Rxx-yyyyy.z.w) or 0 on error."""
971*9c5db199SXin Li        if not self._release_branch_number:
972*9c5db199SXin Li            ver = (self._hosts[0].get_release_version() or '').split('.')
973*9c5db199SXin Li            self._release_branch_number = (int(ver[1]) if len(ver) >= 3 else 0)
974*9c5db199SXin Li        return self._release_branch_number
975*9c5db199SXin Li
976*9c5db199SXin Li    def _get_board_arch(self):
977*9c5db199SXin Li        """Return target DUT arch name."""
978*9c5db199SXin Li        if not self._board_arch:
979*9c5db199SXin Li            self._board_arch = ('arm' if self._hosts[0].get_cpu_arch() == 'arm'
980*9c5db199SXin Li                else 'x86')
981*9c5db199SXin Li        return self._board_arch
982*9c5db199SXin Li
983*9c5db199SXin Li    def _get_board_name(self):
984*9c5db199SXin Li        """Return target DUT board name."""
985*9c5db199SXin Li        if not self._board_name:
986*9c5db199SXin Li            self._board_name = self._hosts[0].get_board().split(':')[1]
987*9c5db199SXin Li        return self._board_name
988*9c5db199SXin Li
989*9c5db199SXin Li    def _get_model_name(self):
990*9c5db199SXin Li        """Return target DUT model name."""
991*9c5db199SXin Li        if not self._model_name:
992*9c5db199SXin Li            self._model_name = self._hosts[0].get_model_from_cros_config()
993*9c5db199SXin Li        return self._model_name
994*9c5db199SXin Li
995*9c5db199SXin Li    def _get_android_version(self):
996*9c5db199SXin Li        """Return target DUT Android SDK version"""
997*9c5db199SXin Li        # TODO(kinaba): factor this out to server/hosts/cros_host.py
998*9c5db199SXin Li        if not self._android_version:
999*9c5db199SXin Li            self._android_version = self._hosts[0].run(
1000*9c5db199SXin Li                'grep ANDROID_SDK /etc/lsb-release',
1001*9c5db199SXin Li                ignore_status=True).stdout.rstrip().split('=')[1]
1002*9c5db199SXin Li        return int(self._android_version)
1003*9c5db199SXin Li
1004*9c5db199SXin Li    def _get_first_api_level(self):
1005*9c5db199SXin Li        """Return target DUT Android first API level."""
1006*9c5db199SXin Li        if not self._first_api_level:
1007*9c5db199SXin Li            self._first_api_level = self._hosts[0].get_arc_first_api_level()
1008*9c5db199SXin Li        return int(self._first_api_level)
1009*9c5db199SXin Li
1010*9c5db199SXin Li    def _get_max_retry(self, max_retry):
1011*9c5db199SXin Li        """Return the maximum number of retries.
1012*9c5db199SXin Li
1013*9c5db199SXin Li        @param max_retry: max_retry specified in the control file.
1014*9c5db199SXin Li        @return: number of retries for this specific host.
1015*9c5db199SXin Li        """
1016*9c5db199SXin Li        if max_retry is None:
1017*9c5db199SXin Li            max_retry = self._get_branch_retry(self._BRANCH_DEFAULT_RETRY)
1018*9c5db199SXin Li        candidate = [max_retry]
1019*9c5db199SXin Li        candidate.append(self._get_board_retry())
1020*9c5db199SXin Li        candidate.append(self._get_branch_retry(self._BRANCH_MAX_RETRY))
1021*9c5db199SXin Li        return min(x for x in candidate if x is not None)
1022*9c5db199SXin Li
1023*9c5db199SXin Li    def _get_board_retry(self):
1024*9c5db199SXin Li        """Return the maximum number of retries for DUT board name.
1025*9c5db199SXin Li
1026*9c5db199SXin Li        @return: number of max_retry or None.
1027*9c5db199SXin Li        """
1028*9c5db199SXin Li        board = self._get_board_name()
1029*9c5db199SXin Li        if board in self._BOARD_MAX_RETRY:
1030*9c5db199SXin Li            return self._BOARD_MAX_RETRY[board]
1031*9c5db199SXin Li        logging.info('No board retry specified for board: %s', board)
1032*9c5db199SXin Li        return None
1033*9c5db199SXin Li
1034*9c5db199SXin Li    def _get_branch_retry(self, table):
1035*9c5db199SXin Li        """Returns the retry count for DUT branch number defined in |table|."""
1036*9c5db199SXin Li        number = self._get_release_branch_number()
1037*9c5db199SXin Li        for lowerbound, retry in reversed(table):
1038*9c5db199SXin Li            if lowerbound <= number:
1039*9c5db199SXin Li                return retry
1040*9c5db199SXin Li        logging.warning('Could not establish channel. Using retry=0.')
1041*9c5db199SXin Li        return 0
1042*9c5db199SXin Li
1043*9c5db199SXin Li    def _is_tablet_mode_device(self):
1044*9c5db199SXin Li        """Returns if running the test on a tabled mode device"""
1045*9c5db199SXin Li        # TODO(kinaba): consider adding per-model check
1046*9c5db199SXin Li        board = self._get_board_name()
1047*9c5db199SXin Li        return any(board.startswith(b) for b in constants.TABLET_MODE_BOARDS)
1048*9c5db199SXin Li
1049*9c5db199SXin Li    def _run_commands(self, commands, **kwargs):
1050*9c5db199SXin Li        """Run commands on all the hosts."""
1051*9c5db199SXin Li        # We need to copy the ADB key to the device to run adb on it.
1052*9c5db199SXin Li        pre_commands = []
1053*9c5db199SXin Li        if any(command.startswith('adb ') for command in commands):
1054*9c5db199SXin Li            key_path = '/tmp/arc.adb_key'
1055*9c5db199SXin Li            for host in self._hosts:
1056*9c5db199SXin Li                host.env['ADB_VENDOR_KEYS'] = key_path
1057*9c5db199SXin Li            pre_commands = [
1058*9c5db199SXin Li                    'adb kill-server',
1059*9c5db199SXin Li                    'echo %s > %s' %
1060*9c5db199SXin Li                    (pipes.quote(constants.PRIVATE_KEY), key_path)
1061*9c5db199SXin Li            ]
1062*9c5db199SXin Li
1063*9c5db199SXin Li        for host in self._hosts:
1064*9c5db199SXin Li            if pre_commands:
1065*9c5db199SXin Li                logging.info('Running DUT adb setup')
1066*9c5db199SXin Li                for command in pre_commands:
1067*9c5db199SXin Li                    host.run(command, ignore_status=True, verbose=False)
1068*9c5db199SXin Li            for command in commands:
1069*9c5db199SXin Li                logging.info('RUN: %s\n', command)
1070*9c5db199SXin Li                output = host.run(command, **kwargs)
1071*9c5db199SXin Li                logging.info('END: %s\n', command)
1072*9c5db199SXin Li                logging.debug(output)
1073*9c5db199SXin Li
1074*9c5db199SXin Li    def _override_powerd_prefs(self):
1075*9c5db199SXin Li        """Overrides powerd prefs to prevent screen from turning off, complying
1076*9c5db199SXin Li        with CTS requirements.
1077*9c5db199SXin Li
1078*9c5db199SXin Li        This is a remote version of PowerPrefChanger which ensures overrided
1079*9c5db199SXin Li        policies won't persist across reboots by bind-mounting onto the config
1080*9c5db199SXin Li        directory.
1081*9c5db199SXin Li        """
1082*9c5db199SXin Li        pref_dir = constants.POWERD_PREF_DIR
1083*9c5db199SXin Li        temp_dir = constants.POWERD_TEMP_DIR
1084*9c5db199SXin Li        commands = (
1085*9c5db199SXin Li                'cp -r %s %s' % (pref_dir, temp_dir),
1086*9c5db199SXin Li                'echo 1 > %s/ignore_external_policy' % temp_dir,
1087*9c5db199SXin Li                'echo 0 | tee %s/{,un}plugged_{dim,off,suspend}_ms' % temp_dir,
1088*9c5db199SXin Li                'mount --bind %s %s' % (temp_dir, pref_dir),
1089*9c5db199SXin Li                'restart powerd',
1090*9c5db199SXin Li        )
1091*9c5db199SXin Li        try:
1092*9c5db199SXin Li            self._run_commands(commands)
1093*9c5db199SXin Li        except (error.AutoservRunError, error.AutoservSSHTimeout):
1094*9c5db199SXin Li            logging.warning('Failed to override powerd policy, tests depending '
1095*9c5db199SXin Li                            'on screen being always on may fail.')
1096*9c5db199SXin Li
1097*9c5db199SXin Li    def _restore_powerd_prefs(self):
1098*9c5db199SXin Li        """Restores powerd prefs overrided by _override_powerd_prefs()."""
1099*9c5db199SXin Li        pref_dir = constants.POWERD_PREF_DIR
1100*9c5db199SXin Li        temp_dir = constants.POWERD_TEMP_DIR
1101*9c5db199SXin Li        commands = (
1102*9c5db199SXin Li                'umount %s' % pref_dir,
1103*9c5db199SXin Li                'restart powerd',
1104*9c5db199SXin Li                'rm -rf %s' % temp_dir,
1105*9c5db199SXin Li        )
1106*9c5db199SXin Li        try:
1107*9c5db199SXin Li            self._run_commands(commands)
1108*9c5db199SXin Li        except (error.AutoservRunError, error.AutoservSSHTimeout):
1109*9c5db199SXin Li            logging.warning('Failed to restore powerd policy, overrided policy '
1110*9c5db199SXin Li                            'will persist until device reboot.')
1111*9c5db199SXin Li
1112*9c5db199SXin Li    def _should_set_cpu_governor(self, target_module, board):
1113*9c5db199SXin Li        """Returns whether we should set performance governor."""
1114*9c5db199SXin Li        # TODO(kinaba): The current restore logic only applies to Kukui
1115*9c5db199SXin Li        # and Trogdor. Please update the logic when expanding the scope.
1116*9c5db199SXin Li        return (target_module and "CtsDeqp" in target_module) and (board in [
1117*9c5db199SXin Li                'kukui-arc-r', 'trogdor-arc-r'
1118*9c5db199SXin Li        ])
1119*9c5db199SXin Li
1120*9c5db199SXin Li    def _set_cpu_governor(self, governor):
1121*9c5db199SXin Li        """Set the specified CPU governor."""
1122*9c5db199SXin Li        self._run_commands([('for i in /sys/devices/system/cpu/cpufreq/*; do'
1123*9c5db199SXin Li                             ' echo %s > $i/scaling_governor; done') % governor
1124*9c5db199SXin Li                            ])
1125*9c5db199SXin Li
1126*9c5db199SXin Li    def _override_cpu_governor(self):
1127*9c5db199SXin Li        """Override the CPU governor for performance mode."""
1128*9c5db199SXin Li        try:
1129*9c5db199SXin Li            self._set_cpu_governor('performance')
1130*9c5db199SXin Li        except (error.AutoservRunError, error.AutoservSSHTimeout):
1131*9c5db199SXin Li            logging.warning('Failed to override CPU governor, tests depending '
1132*9c5db199SXin Li                            'on boosted performance may fail.')
1133*9c5db199SXin Li
1134*9c5db199SXin Li    def _restore_cpu_governor(self):
1135*9c5db199SXin Li        """Restore the CPU governor to the default value."""
1136*9c5db199SXin Li        try:
1137*9c5db199SXin Li            self._set_cpu_governor('schedutil')
1138*9c5db199SXin Li        except (error.AutoservRunError, error.AutoservSSHTimeout):
1139*9c5db199SXin Li            logging.warning('Failed to restore CPU governor, overrided policy '
1140*9c5db199SXin Li                            'will persist until device reboot.')
1141*9c5db199SXin Li
1142*9c5db199SXin Li    def _mute_device(self):
1143*9c5db199SXin Li        """Mutes the device to avoid noises while running tests"""
1144*9c5db199SXin Li        try:
1145*9c5db199SXin Li            self._run_commands(['cras_test_client --mute 1'],
1146*9c5db199SXin Li                               ignore_status=True)
1147*9c5db199SXin Li        except:
1148*9c5db199SXin Li            logging.warning('Failed to mute the device')
1149*9c5db199SXin Li
1150*9c5db199SXin Li    def _clean_crash_logs(self):
1151*9c5db199SXin Li        try:
1152*9c5db199SXin Li            self._run_commands(['rm -f /home/chronos/crash/*'])
1153*9c5db199SXin Li        except (error.AutoservRunError, error.AutoservSSHTimeout):
1154*9c5db199SXin Li            logging.warning('Failed to clean up crash logs.')
1155*9c5db199SXin Li
1156*9c5db199SXin Li    def _run_and_parse_tradefed(self, command):
1157*9c5db199SXin Li        """Kick off the tradefed command.
1158*9c5db199SXin Li
1159*9c5db199SXin Li        @param command: Lists of command tokens.
1160*9c5db199SXin Li        @raise TestFail: when a test failure is detected.
1161*9c5db199SXin Li        @return: tuple of (tests, pass, fail, notexecuted) counts.
1162*9c5db199SXin Li        """
1163*9c5db199SXin Li        target_argument = []
1164*9c5db199SXin Li        for host in self._hosts:
1165*9c5db199SXin Li            target_argument += ['-s', adb_utils.get_adb_target(host)]
1166*9c5db199SXin Li        shard_argument = []
1167*9c5db199SXin Li        if len(self._hosts) > 1:
1168*9c5db199SXin Li            if self._SHARD_CMD:
1169*9c5db199SXin Li                shard_argument = [self._SHARD_CMD, str(len(self._hosts))]
1170*9c5db199SXin Li            else:
1171*9c5db199SXin Li                logging.warning('cts-tradefed shard command isn\'t defined, '
1172*9c5db199SXin Li                                'falling back to use single device.')
1173*9c5db199SXin Li        command = command + target_argument + shard_argument
1174*9c5db199SXin Li
1175*9c5db199SXin Li        try:
1176*9c5db199SXin Li            output = self._run_tradefed(command)
1177*9c5db199SXin Li        except Exception as e:
1178*9c5db199SXin Li            self._log_java_version()
1179*9c5db199SXin Li            if not isinstance(e, error.CmdTimeoutError):
1180*9c5db199SXin Li                # In case this happened due to file corruptions, try to
1181*9c5db199SXin Li                # force to recreate the cache.
1182*9c5db199SXin Li                logging.error('Failed to run tradefed! Cleaning up now.')
1183*9c5db199SXin Li                self._clean_download_cache_if_needed(force=True)
1184*9c5db199SXin Li            raise
1185*9c5db199SXin Li
1186*9c5db199SXin Li        result_destination = self._default_tradefed_base_dir()
1187*9c5db199SXin Li        # Gather the global log first. Datetime parsing below can abort the test
1188*9c5db199SXin Li        # if tradefed startup had failed. Even then the global log is useful.
1189*9c5db199SXin Li        self._collect_tradefed_global_log(output, result_destination)
1190*9c5db199SXin Li        # Result parsing must come after all other essential operations as test
1191*9c5db199SXin Li        # warnings, errors and failures can be raised here.
1192*9c5db199SXin Li        base = self._default_tradefed_base_dir()
1193*9c5db199SXin Li        path = tradefed_utils.get_test_result_xml_path(base)
1194*9c5db199SXin Li        return tradefed_utils.parse_tradefed_testresults_xml(
1195*9c5db199SXin Li            test_result_xml_path=path,
1196*9c5db199SXin Li            waivers=self._waivers)
1197*9c5db199SXin Li
1198*9c5db199SXin Li    def _setup_result_directories(self):
1199*9c5db199SXin Li        """Sets up the results and logs directories for tradefed.
1200*9c5db199SXin Li
1201*9c5db199SXin Li        Tradefed saves the logs and results at:
1202*9c5db199SXin Li          self._repository/results/$datetime/
1203*9c5db199SXin Li          self._repository/results/$datetime.zip
1204*9c5db199SXin Li          self._repository/logs/$datetime/
1205*9c5db199SXin Li        Because other tools rely on the currently chosen Google storage paths
1206*9c5db199SXin Li        we need to keep destination_results in:
1207*9c5db199SXin Li          self.resultsdir/android-cts/results/$datetime/
1208*9c5db199SXin Li          self.resultsdir/android-cts/results/$datetime.zip
1209*9c5db199SXin Li          self.resultsdir/android-cts/results/logs/$datetime/
1210*9c5db199SXin Li        To bridge between them, create symlinks from the former to the latter.
1211*9c5db199SXin Li        """
1212*9c5db199SXin Li        logging.info('Setting up tradefed results and logs directories.')
1213*9c5db199SXin Li
1214*9c5db199SXin Li        results_destination = self._default_tradefed_base_dir()
1215*9c5db199SXin Li        logs_destination = os.path.join(results_destination, 'logs')
1216*9c5db199SXin Li        directory_mapping = [
1217*9c5db199SXin Li            (os.path.join(self._repository, 'results'), results_destination),
1218*9c5db199SXin Li            (os.path.join(self._repository, 'logs'), logs_destination),
1219*9c5db199SXin Li        ]
1220*9c5db199SXin Li
1221*9c5db199SXin Li        for (tradefed_path, final_path) in directory_mapping:
1222*9c5db199SXin Li            if os.path.exists(tradefed_path):
1223*9c5db199SXin Li                shutil.rmtree(tradefed_path)
1224*9c5db199SXin Li            self._safe_makedirs(final_path)
1225*9c5db199SXin Li            os.symlink(final_path, tradefed_path)
1226*9c5db199SXin Li
1227*9c5db199SXin Li    def _default_tradefed_base_dir(self):
1228*9c5db199SXin Li        return os.path.join(self.resultsdir, self._get_tradefed_base_dir())
1229*9c5db199SXin Li
1230*9c5db199SXin Li    def _install_plan(self, subplan):
1231*9c5db199SXin Li        """Copy test subplan to CTS-TF.
1232*9c5db199SXin Li
1233*9c5db199SXin Li        @param subplan: CTS subplan to be copied into TF.
1234*9c5db199SXin Li        """
1235*9c5db199SXin Li        logging.info('Install subplan: %s', subplan)
1236*9c5db199SXin Li        subplans_tf_dir = os.path.join(self._repository, 'subplans')
1237*9c5db199SXin Li        if not os.path.exists(subplans_tf_dir):
1238*9c5db199SXin Li            os.makedirs(subplans_tf_dir)
1239*9c5db199SXin Li        test_subplan_file = os.path.join(self.bindir, 'subplans',
1240*9c5db199SXin Li                                         '%s.xml' % subplan)
1241*9c5db199SXin Li        try:
1242*9c5db199SXin Li            shutil.copy(test_subplan_file, subplans_tf_dir)
1243*9c5db199SXin Li        except (shutil.Error, OSError, IOError) as e:
1244*9c5db199SXin Li            raise error.TestFail(
1245*9c5db199SXin Li                'Error: failed to copy test subplan %s to CTS bundle. %s' %
1246*9c5db199SXin Li                (test_subplan_file, e))
1247*9c5db199SXin Li
1248*9c5db199SXin Li    def _should_skip_test(self, _bundle):
1249*9c5db199SXin Li        """Some tests are expected to fail and are skipped.
1250*9c5db199SXin Li
1251*9c5db199SXin Li        Subclasses should override with specific details.
1252*9c5db199SXin Li        """
1253*9c5db199SXin Li        return False
1254*9c5db199SXin Li
1255*9c5db199SXin Li    def _should_reboot(self, steps):
1256*9c5db199SXin Li        """Oracle to decide if DUT should reboot or just restart Chrome.
1257*9c5db199SXin Li
1258*9c5db199SXin Li        For now we will not reboot after the first two iterations, but on all
1259*9c5db199SXin Li        iterations afterward as before. In particular this means that most CTS
1260*9c5db199SXin Li        tests will now not get a "clean" machine, but one on which tests ran
1261*9c5db199SXin Li        before. But we will still reboot after persistent failures, hopefully
1262*9c5db199SXin Li        not causing too many flakes down the line.
1263*9c5db199SXin Li        """
1264*9c5db199SXin Li        if steps < 3:
1265*9c5db199SXin Li            return False
1266*9c5db199SXin Li        return True
1267*9c5db199SXin Li
1268*9c5db199SXin Li    def _copy_extra_artifacts_dut(self, extra_artifacts, host, output_dir):
1269*9c5db199SXin Li        """ Upload the custom artifacts """
1270*9c5db199SXin Li        self._safe_makedirs(output_dir)
1271*9c5db199SXin Li
1272*9c5db199SXin Li        for artifact in extra_artifacts:
1273*9c5db199SXin Li            logging.info('Copying extra artifacts from "%s" to "%s".',
1274*9c5db199SXin Li                         artifact, output_dir)
1275*9c5db199SXin Li            try:
1276*9c5db199SXin Li                self._adb.run(host,
1277*9c5db199SXin Li                              verbose=True,
1278*9c5db199SXin Li                              timeout=120,
1279*9c5db199SXin Li                              args=('pull', artifact, output_dir))
1280*9c5db199SXin Li            except:
1281*9c5db199SXin Li                # Maybe ADB connection failed, or the artifacts don't exist.
1282*9c5db199SXin Li                logging.exception('Copying extra artifacts failed.')
1283*9c5db199SXin Li
1284*9c5db199SXin Li    def _copy_extra_artifacts_host(self, extra_artifacts, host, output_dir):
1285*9c5db199SXin Li        """ Upload the custom artifacts """
1286*9c5db199SXin Li        self._safe_makedirs(output_dir)
1287*9c5db199SXin Li
1288*9c5db199SXin Li        for artifact in extra_artifacts:
1289*9c5db199SXin Li            logging.info('Copying extra artifacts from "%s" to "%s".',
1290*9c5db199SXin Li                         artifact, output_dir)
1291*9c5db199SXin Li            for extracted_path in glob.glob(artifact):
1292*9c5db199SXin Li                logging.info('... %s', extracted_path)
1293*9c5db199SXin Li                # Move it not to collect it again in future retries.
1294*9c5db199SXin Li                shutil.move(extracted_path, output_dir)
1295*9c5db199SXin Li
1296*9c5db199SXin Li    def _run_tradefed_list_results(self):
1297*9c5db199SXin Li        """Run the `tradefed list results` command.
1298*9c5db199SXin Li
1299*9c5db199SXin Li        @return: tuple of the last (session_id, pass, fail, all_done?).
1300*9c5db199SXin Li        """
1301*9c5db199SXin Li
1302*9c5db199SXin Li        # Fix b/143580192: We set the timeout to 3 min. It never takes more than
1303*9c5db199SXin Li        # 10s on light disk load.
1304*9c5db199SXin Li        output = self._run_tradefed_with_timeout(['list', 'results'], 180)
1305*9c5db199SXin Li
1306*9c5db199SXin Li        # Parses the last session from the output that looks like:
1307*9c5db199SXin Li        #
1308*9c5db199SXin Li        # Session  Pass  Fail  Modules Complete ...
1309*9c5db199SXin Li        # 0        90    10    1 of 2
1310*9c5db199SXin Li        # 1        199   1     2 of 2
1311*9c5db199SXin Li        # ...
1312*9c5db199SXin Li        lastmatch = None
1313*9c5db199SXin Li        for m in re.finditer(r'^(\d+)\s+(\d+)\s+(\d+)\s+(\d+) of (\d+)',
1314*9c5db199SXin Li                             output.stdout, re.MULTILINE):
1315*9c5db199SXin Li            session, passed, failed, done, total = map(int,
1316*9c5db199SXin Li                                                       m.group(1, 2, 3, 4, 5))
1317*9c5db199SXin Li            lastmatch = (session, passed, failed, done == total)
1318*9c5db199SXin Li        return lastmatch
1319*9c5db199SXin Li
1320*9c5db199SXin Li    def _get_bundle_url(self, uri, bundle):
1321*9c5db199SXin Li        # TODO: Replace with NotImplementedError once all subclasses are done
1322*9c5db199SXin Li        return self._get_latest_bundle_url(bundle) if uri == 'LATEST' else (
1323*9c5db199SXin Li                uri or self._get_default_bundle_url(bundle))
1324*9c5db199SXin Li
1325*9c5db199SXin Li    def _tradefed_retry_command(self, template, session_id):
1326*9c5db199SXin Li        raise NotImplementedError('Subclass should override this function')
1327*9c5db199SXin Li
1328*9c5db199SXin Li    def _tradefed_run_command(self, template):
1329*9c5db199SXin Li        raise NotImplementedError('Subclass should override this function')
1330*9c5db199SXin Li
1331*9c5db199SXin Li    def _tradefed_cmd_path(self):
1332*9c5db199SXin Li        raise NotImplementedError('Subclass should override this function')
1333*9c5db199SXin Li
1334*9c5db199SXin Li    def _tradefed_env(self):
1335*9c5db199SXin Li        return None
1336*9c5db199SXin Li
1337*9c5db199SXin Li    def _run_tradefed_with_timeout(self, command, timeout):
1338*9c5db199SXin Li        tradefed = self._tradefed_cmd_path()
1339*9c5db199SXin Li        with tradefed_utils.adb_keepalive(
1340*9c5db199SXin Li                adb_utils.get_adb_targets(self._hosts), self._install_paths):
1341*9c5db199SXin Li            logging.info('RUN(timeout=%d): %s', timeout,
1342*9c5db199SXin Li                         ' '.join([tradefed] + command))
1343*9c5db199SXin Li            output = self._run(
1344*9c5db199SXin Li                tradefed,
1345*9c5db199SXin Li                args=tuple(command),
1346*9c5db199SXin Li                env=self._tradefed_env(),
1347*9c5db199SXin Li                timeout=timeout,
1348*9c5db199SXin Li                verbose=True,
1349*9c5db199SXin Li                ignore_status=False,
1350*9c5db199SXin Li                # Make sure to tee tradefed stdout/stderr to autotest logs
1351*9c5db199SXin Li                # continuously during the test run.
1352*9c5db199SXin Li                stdout_tee=utils.TEE_TO_LOGS,
1353*9c5db199SXin Li                stderr_tee=utils.TEE_TO_LOGS)
1354*9c5db199SXin Li            logging.info('END: %s\n', ' '.join([tradefed] + command))
1355*9c5db199SXin Li        return output
1356*9c5db199SXin Li
1357*9c5db199SXin Li    def _run_tradefed(self, command):
1358*9c5db199SXin Li        timeout = self._timeout * self._timeout_factor
1359*9c5db199SXin Li        if self._job_deadline is not None:
1360*9c5db199SXin Li            clipped = int(min(timeout, self._job_deadline - time.time()))
1361*9c5db199SXin Li            # Even the shortest tradefed run takes 1.5 minutes. Took 2x'ed
1362*9c5db199SXin Li            # value as a threshold that a meaningful test can run.
1363*9c5db199SXin Li            if clipped < 3 * 60:
1364*9c5db199SXin Li                raise error.TestError(
1365*9c5db199SXin Li                        'Hitting job time limit: only %s seconds left' %
1366*9c5db199SXin Li                        clipped)
1367*9c5db199SXin Li            timeout = clipped
1368*9c5db199SXin Li        return self._run_tradefed_with_timeout(command, timeout)
1369*9c5db199SXin Li
1370*9c5db199SXin Li    def _run_tradefed_with_retries(self,
1371*9c5db199SXin Li                                   test_name,
1372*9c5db199SXin Li                                   run_template,
1373*9c5db199SXin Li                                   retry_template,
1374*9c5db199SXin Li                                   timeout,
1375*9c5db199SXin Li                                   media_asset=None,
1376*9c5db199SXin Li                                   enable_default_apps=False,
1377*9c5db199SXin Li                                   target_module=None,
1378*9c5db199SXin Li                                   target_plan=None,
1379*9c5db199SXin Li                                   executable_test_count=None,
1380*9c5db199SXin Li                                   bundle=None,
1381*9c5db199SXin Li                                   use_helpers=False,
1382*9c5db199SXin Li                                   extra_artifacts=[],
1383*9c5db199SXin Li                                   extra_artifacts_host=[],
1384*9c5db199SXin Li                                   login_precondition_commands=[],
1385*9c5db199SXin Li                                   precondition_commands=[],
1386*9c5db199SXin Li                                   prerequisites=[]):
1387*9c5db199SXin Li        """Run CTS/GTS with retry logic.
1388*9c5db199SXin Li
1389*9c5db199SXin Li        We first kick off the specified module. Then rerun just the failures
1390*9c5db199SXin Li        on the next MAX_RETRY iterations.
1391*9c5db199SXin Li        """
1392*9c5db199SXin Li        for prereq in prerequisites:
1393*9c5db199SXin Li            result = tradefed_prerequisite.check(prereq, self._hosts)
1394*9c5db199SXin Li            if not result[0]:
1395*9c5db199SXin Li                raise error.TestError(result[1])
1396*9c5db199SXin Li
1397*9c5db199SXin Li        # On dev and beta channels timeouts are sharp, lenient on stable.
1398*9c5db199SXin Li        self._timeout = timeout
1399*9c5db199SXin Li        if (self._get_release_branch_number() >=
1400*9c5db199SXin Li                constants.APPROXIMATE_STABLE_BRANCH_NUMBER):
1401*9c5db199SXin Li            self._timeout += 3600
1402*9c5db199SXin Li
1403*9c5db199SXin Li        if self._should_skip_test(bundle):
1404*9c5db199SXin Li            logging.warning('Skipped test %s', ' '.join(test_name))
1405*9c5db199SXin Li            return
1406*9c5db199SXin Li
1407*9c5db199SXin Li        steps = -1  # For historic reasons the first iteration is not counted.
1408*9c5db199SXin Li        self.summary = ''
1409*9c5db199SXin Li        board = self._get_board_name()
1410*9c5db199SXin Li        session_id = None
1411*9c5db199SXin Li
1412*9c5db199SXin Li        self._setup_result_directories()
1413*9c5db199SXin Li        if media_asset:
1414*9c5db199SXin Li            self._prepare_media(media_asset)
1415*9c5db199SXin Li
1416*9c5db199SXin Li        # This loop retries failures. For this reason please do not raise
1417*9c5db199SXin Li        # TestFail in this loop if you suspect the failure might be fixed
1418*9c5db199SXin Li        # in the next loop iteration.
1419*9c5db199SXin Li        while steps < self._max_retry:
1420*9c5db199SXin Li            steps += 1
1421*9c5db199SXin Li            keep_media = media_asset and media_asset.uri and steps >= 1
1422*9c5db199SXin Li            self._run_commands(login_precondition_commands, ignore_status=True)
1423*9c5db199SXin Li            # TODO(kinaba): Make it a general config (per-model choice
1424*9c5db199SXin Li            # of tablet,clamshell,default) if the code below works.
1425*9c5db199SXin Li            if utils.is_in_container() and not client_utils.is_moblab():
1426*9c5db199SXin Li                # Force laptop mode for non TABLET_MODE_BOARDS
1427*9c5db199SXin Li                if not self._is_tablet_mode_device():
1428*9c5db199SXin Li                    self._run_commands(
1429*9c5db199SXin Li                        ['inject_powerd_input_event --code=tablet --value=0'],
1430*9c5db199SXin Li                        ignore_status=True)
1431*9c5db199SXin Li
1432*9c5db199SXin Li            session_log_dir = os.path.join(self.resultsdir,
1433*9c5db199SXin Li                                           'login_session_log',
1434*9c5db199SXin Li                                           'step%02d' % steps)
1435*9c5db199SXin Li            with login.login_chrome(hosts=self._hosts,
1436*9c5db199SXin Li                                    board=board,
1437*9c5db199SXin Li                                    dont_override_profile=keep_media,
1438*9c5db199SXin Li                                    enable_default_apps=enable_default_apps,
1439*9c5db199SXin Li                                    log_dir=session_log_dir) as current_logins:
1440*9c5db199SXin Li                if self._should_reboot(steps):
1441*9c5db199SXin Li                    # TODO(rohitbm): Evaluate if power cycle really helps with
1442*9c5db199SXin Li                    # Bluetooth test failures, and then make the implementation
1443*9c5db199SXin Li                    # more strict by first running complete restart and reboot
1444*9c5db199SXin Li                    # retries and then perform power cycle.
1445*9c5db199SXin Li                    #
1446*9c5db199SXin Li                    # Currently, (steps + 1 == self._max_retry) means that
1447*9c5db199SXin Li                    # hard_reboot is attempted after "this" cycle failed. Then,
1448*9c5db199SXin Li                    # the last remaining 1 step will be run on the rebooted DUT.
1449*9c5db199SXin Li                    hard_reboot = (self._hard_reboot_on_failure
1450*9c5db199SXin Li                        and steps + 1 == self._max_retry)
1451*9c5db199SXin Li                    for current_login in current_logins:
1452*9c5db199SXin Li                        current_login.need_reboot(hard_reboot=hard_reboot)
1453*9c5db199SXin Li                self._ready_arc()
1454*9c5db199SXin Li                self._calculate_test_count_factor(bundle)
1455*9c5db199SXin Li
1456*9c5db199SXin Li                # Check the ABI list and skip (pass) the tests if not applicable.
1457*9c5db199SXin Li                # This needs to be done after _ready_arc() for reading the device's
1458*9c5db199SXin Li                # ABI list from the booted ARC instance.
1459*9c5db199SXin Li                if '--abi' in run_template:
1460*9c5db199SXin Li                    abi = run_template[run_template.index('--abi') + 1]
1461*9c5db199SXin Li                    abilist = self._get_abilist()
1462*9c5db199SXin Li                    if abilist and abi not in abilist:
1463*9c5db199SXin Li                        logging.info(
1464*9c5db199SXin Li                                'Specified ABI %s is not in the device ABI list %s. Skipping.',
1465*9c5db199SXin Li                                abi, abilist)
1466*9c5db199SXin Li                        return
1467*9c5db199SXin Li
1468*9c5db199SXin Li                # For CtsMediaStressTestCases, push media assets in advance if
1469*9c5db199SXin Li                # applicable.
1470*9c5db199SXin Li                if (not keep_media and media_asset
1471*9c5db199SXin Li                            and self._should_push_mediastress_asset(
1472*9c5db199SXin Li                                    target_module, board)):
1473*9c5db199SXin Li                    self._push_mediastress_asset(media_asset)
1474*9c5db199SXin Li
1475*9c5db199SXin Li                self._run_commands(precondition_commands, ignore_status=True)
1476*9c5db199SXin Li                if use_helpers:
1477*9c5db199SXin Li                    self._fetch_helpers_from_dut()
1478*9c5db199SXin Li
1479*9c5db199SXin Li                # Run tradefed.
1480*9c5db199SXin Li                if session_id == None:
1481*9c5db199SXin Li                    if target_plan is not None:
1482*9c5db199SXin Li                        self._install_plan(target_plan)
1483*9c5db199SXin Li
1484*9c5db199SXin Li                    logging.info('Running %s:', test_name)
1485*9c5db199SXin Li                    command = self._tradefed_run_command(run_template)
1486*9c5db199SXin Li                else:
1487*9c5db199SXin Li                    logging.info('Retrying failures of %s with session_id %d:',
1488*9c5db199SXin Li                                 test_name, session_id)
1489*9c5db199SXin Li                    command = self._tradefed_retry_command(retry_template,
1490*9c5db199SXin Li                                                           session_id)
1491*9c5db199SXin Li
1492*9c5db199SXin Li                if media_asset and media_asset.uri:
1493*9c5db199SXin Li                    # Clean-up crash logs from previous sessions to ensure
1494*9c5db199SXin Li                    # enough disk space for 16GB storage devices: b/156075084.
1495*9c5db199SXin Li                    if not keep_media:
1496*9c5db199SXin Li                        self._clean_crash_logs()
1497*9c5db199SXin Li                # b/196748125. Mute before running tests to avoid noises.
1498*9c5db199SXin Li                self._mute_device()
1499*9c5db199SXin Li                set_performance_governor = self._should_set_cpu_governor(
1500*9c5db199SXin Li                        target_module, board)
1501*9c5db199SXin Li                # TODO(b/182397469): speculatively disable the "screen-on"
1502*9c5db199SXin Li                # handler for dEQP. Revert when the issue is resolved.
1503*9c5db199SXin Li                keep_screen_on = not (target_module
1504*9c5db199SXin Li                                      and "CtsDeqpTestCases" in target_module)
1505*9c5db199SXin Li                if set_performance_governor:
1506*9c5db199SXin Li                    self._override_cpu_governor()
1507*9c5db199SXin Li                if keep_screen_on:
1508*9c5db199SXin Li                    self._override_powerd_prefs()
1509*9c5db199SXin Li                try:
1510*9c5db199SXin Li                    waived_tests = self._run_and_parse_tradefed(command)
1511*9c5db199SXin Li                finally:
1512*9c5db199SXin Li                    if keep_screen_on:
1513*9c5db199SXin Li                        self._restore_powerd_prefs()
1514*9c5db199SXin Li                    if set_performance_governor:
1515*9c5db199SXin Li                        self._restore_cpu_governor()
1516*9c5db199SXin Li                if media_asset:
1517*9c5db199SXin Li                    self._fail_on_unexpected_media_download(media_asset)
1518*9c5db199SXin Li                result = self._run_tradefed_list_results()
1519*9c5db199SXin Li                if not result:
1520*9c5db199SXin Li                    logging.error('Did not find any test results. Retry.')
1521*9c5db199SXin Li                    for current_login in current_logins:
1522*9c5db199SXin Li                        current_login.need_reboot()
1523*9c5db199SXin Li                    continue
1524*9c5db199SXin Li
1525*9c5db199SXin Li                last_waived = len(waived_tests)
1526*9c5db199SXin Li                last_session_id, last_passed, last_failed, last_all_done =\
1527*9c5db199SXin Li                    result
1528*9c5db199SXin Li
1529*9c5db199SXin Li                if last_failed > last_waived or not utils.is_in_container():
1530*9c5db199SXin Li                    for host in self._hosts:
1531*9c5db199SXin Li                        dir_name = "%s-step%02d" % (host.hostname, steps)
1532*9c5db199SXin Li                        output_dir = os.path.join(
1533*9c5db199SXin Li                            self.resultsdir, 'extra_artifacts', dir_name)
1534*9c5db199SXin Li                        self._copy_extra_artifacts_dut(
1535*9c5db199SXin Li                            extra_artifacts, host, output_dir)
1536*9c5db199SXin Li                        self._copy_extra_artifacts_host(
1537*9c5db199SXin Li                            extra_artifacts_host, host, output_dir)
1538*9c5db199SXin Li
1539*9c5db199SXin Li                if last_passed + last_failed > 0:
1540*9c5db199SXin Li                    # At least one test had run, which means the media push step
1541*9c5db199SXin Li                    # of tradefed didn't fail. To free up the storage earlier,
1542*9c5db199SXin Li                    # delete the copy on the server side. See crbug.com/970881
1543*9c5db199SXin Li                    if media_asset:
1544*9c5db199SXin Li                        self._cleanup_media(media_asset)
1545*9c5db199SXin Li
1546*9c5db199SXin Li                if last_failed < last_waived:
1547*9c5db199SXin Li                    logging.error(
1548*9c5db199SXin Li                        'Error: Internal waiver bookkeeping has become '
1549*9c5db199SXin Li                        'inconsistent (f=%d, w=%d)', last_failed, last_waived)
1550*9c5db199SXin Li
1551*9c5db199SXin Li                msg = 'run' if session_id == None else ' retry'
1552*9c5db199SXin Li                msg += '(p=%s, f=%s, w=%s)' % (last_passed, last_failed,
1553*9c5db199SXin Li                                               last_waived)
1554*9c5db199SXin Li                self.summary += msg
1555*9c5db199SXin Li                logging.info('RESULT: %s %s', msg, result)
1556*9c5db199SXin Li
1557*9c5db199SXin Li                # Overwrite last_all_done if the executed test count is equal
1558*9c5db199SXin Li                # to the known test count of the job.
1559*9c5db199SXin Li                if (not last_all_done and executable_test_count != None and
1560*9c5db199SXin Li                    (last_passed + last_failed in executable_test_count)):
1561*9c5db199SXin Li                    logging.warning('Overwriting all_done as True, since the '
1562*9c5db199SXin Li                                    'explicitly set executable_test_count '
1563*9c5db199SXin Li                                    'tests have run.')
1564*9c5db199SXin Li                    last_all_done = True
1565*9c5db199SXin Li
1566*9c5db199SXin Li                # Check for no-test modules. We use the "all_done" indicator
1567*9c5db199SXin Li                # provided by list_results to decide if there are outstanding
1568*9c5db199SXin Li                # modules to iterate over (similar to missing tests just on a
1569*9c5db199SXin Li                # per-module basis).
1570*9c5db199SXin Li                notest = (last_passed + last_failed == 0 and last_all_done)
1571*9c5db199SXin Li                if target_module in self._notest_modules:
1572*9c5db199SXin Li                    if notest:
1573*9c5db199SXin Li                        logging.info('Package has no tests as expected.')
1574*9c5db199SXin Li                        return
1575*9c5db199SXin Li                    else:
1576*9c5db199SXin Li                        # We expected no tests, but the new bundle drop must
1577*9c5db199SXin Li                        # have added some for us. Alert us to the situation.
1578*9c5db199SXin Li                        raise error.TestFail(
1579*9c5db199SXin Li                            'Failed: Remove module %s from '
1580*9c5db199SXin Li                            'notest_modules directory!' % target_module)
1581*9c5db199SXin Li                elif notest:
1582*9c5db199SXin Li                    logging.error('Did not find any tests in module. Hoping '
1583*9c5db199SXin Li                                  'this is transient. Retry after reboot.')
1584*9c5db199SXin Li                    for current_login in current_logins:
1585*9c5db199SXin Li                        current_login.need_reboot()
1586*9c5db199SXin Li                    continue
1587*9c5db199SXin Li
1588*9c5db199SXin Li                # After the no-test check, commit the pass/fail count.
1589*9c5db199SXin Li                waived = last_waived
1590*9c5db199SXin Li                session_id, passed, failed, all_done =\
1591*9c5db199SXin Li                    last_session_id, last_passed, last_failed, last_all_done
1592*9c5db199SXin Li
1593*9c5db199SXin Li                # Check if all the tests passed.
1594*9c5db199SXin Li                if failed <= waived and all_done:
1595*9c5db199SXin Li                    break
1596*9c5db199SXin Li
1597*9c5db199SXin Li                # TODO(b/127908450) Tradefed loses track of not-executed tests
1598*9c5db199SXin Li                # when the commandline pattern included '*', and retry run for
1599*9c5db199SXin Li                # them wrongly declares all tests passed. This is misleading.
1600*9c5db199SXin Li                # Rather, we give up the retry and report the result as FAIL.
1601*9c5db199SXin Li                if not all_done and '*' in ''.join(run_template):
1602*9c5db199SXin Li                    break
1603*9c5db199SXin Li
1604*9c5db199SXin Li        if session_id == None:
1605*9c5db199SXin Li            raise error.TestFail('Error: Could not find any tests in module.')
1606*9c5db199SXin Li
1607*9c5db199SXin Li        if failed <= waived and all_done:
1608*9c5db199SXin Li            # TODO(ihf): Make this error.TestPass('...') once
1609*9c5db199SXin Li            # available.
1610*9c5db199SXin Li            if steps > 0 and self._warn_on_test_retry:
1611*9c5db199SXin Li                raise error.TestWarn(
1612*9c5db199SXin Li                    'Passed: after %d retries passing %d tests, '
1613*9c5db199SXin Li                    'waived=%d. %s' % (steps, passed, waived,
1614*9c5db199SXin Li                                       self.summary))
1615*9c5db199SXin Li            return
1616*9c5db199SXin Li
1617*9c5db199SXin Li        raise error.TestFail(
1618*9c5db199SXin Li                'Failed: after %d retries giving up. '
1619*9c5db199SXin Li                'passed=%d, failed=%d, waived=%d%s. %s' %
1620*9c5db199SXin Li                (steps, passed, failed, waived,
1621*9c5db199SXin Li                 '' if all_done else ', notexec>=1', self.summary))
1622