xref: /aosp_15_r20/external/cronet/build/chromeos/test_runner.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1*6777b538SAndroid Build Coastguard Worker#!/usr/bin/env vpython3
2*6777b538SAndroid Build Coastguard Worker#
3*6777b538SAndroid Build Coastguard Worker# Copyright 2018 The Chromium Authors
4*6777b538SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be
5*6777b538SAndroid Build Coastguard Worker# found in the LICENSE file.
6*6777b538SAndroid Build Coastguard Worker
7*6777b538SAndroid Build Coastguard Workerimport argparse
8*6777b538SAndroid Build Coastguard Workerimport collections
9*6777b538SAndroid Build Coastguard Workerimport json
10*6777b538SAndroid Build Coastguard Workerimport logging
11*6777b538SAndroid Build Coastguard Workerimport os
12*6777b538SAndroid Build Coastguard Workerimport re
13*6777b538SAndroid Build Coastguard Workerimport shutil
14*6777b538SAndroid Build Coastguard Workerimport signal
15*6777b538SAndroid Build Coastguard Workerimport socket
16*6777b538SAndroid Build Coastguard Workerimport sys
17*6777b538SAndroid Build Coastguard Workerimport tempfile
18*6777b538SAndroid Build Coastguard Worker
19*6777b538SAndroid Build Coastguard Worker# The following non-std imports are fetched via vpython. See the list at
20*6777b538SAndroid Build Coastguard Worker# //.vpython3
21*6777b538SAndroid Build Coastguard Workerimport dateutil.parser  # pylint: disable=import-error
22*6777b538SAndroid Build Coastguard Workerimport jsonlines  # pylint: disable=import-error
23*6777b538SAndroid Build Coastguard Workerimport psutil  # pylint: disable=import-error
24*6777b538SAndroid Build Coastguard Worker
25*6777b538SAndroid Build Coastguard WorkerCHROMIUM_SRC_PATH = os.path.abspath(
26*6777b538SAndroid Build Coastguard Worker    os.path.join(os.path.dirname(__file__), '..', '..'))
27*6777b538SAndroid Build Coastguard Worker
28*6777b538SAndroid Build Coastguard Worker# Use the android test-runner's gtest results support library for generating
29*6777b538SAndroid Build Coastguard Worker# output json ourselves.
30*6777b538SAndroid Build Coastguard Workersys.path.insert(0, os.path.join(CHROMIUM_SRC_PATH, 'build', 'android'))
31*6777b538SAndroid Build Coastguard Workerfrom pylib.base import base_test_result  # pylint: disable=import-error
32*6777b538SAndroid Build Coastguard Workerfrom pylib.results import json_results  # pylint: disable=import-error
33*6777b538SAndroid Build Coastguard Worker
34*6777b538SAndroid Build Coastguard Workersys.path.insert(0, os.path.join(CHROMIUM_SRC_PATH, 'build', 'util'))
35*6777b538SAndroid Build Coastguard Worker# TODO(crbug.com/1421441): Re-enable the 'no-name-in-module' check.
36*6777b538SAndroid Build Coastguard Workerfrom lib.results import result_sink  # pylint: disable=import-error,no-name-in-module
37*6777b538SAndroid Build Coastguard Worker
38*6777b538SAndroid Build Coastguard Workerimport subprocess  # pylint: disable=import-error,wrong-import-order
39*6777b538SAndroid Build Coastguard Worker
40*6777b538SAndroid Build Coastguard WorkerDEFAULT_CROS_CACHE = os.path.abspath(
41*6777b538SAndroid Build Coastguard Worker    os.path.join(CHROMIUM_SRC_PATH, 'build', 'cros_cache'))
42*6777b538SAndroid Build Coastguard WorkerCHROMITE_PATH = os.path.abspath(
43*6777b538SAndroid Build Coastguard Worker    os.path.join(CHROMIUM_SRC_PATH, 'third_party', 'chromite'))
44*6777b538SAndroid Build Coastguard WorkerCROS_RUN_TEST_PATH = os.path.abspath(
45*6777b538SAndroid Build Coastguard Worker    os.path.join(CHROMITE_PATH, 'bin', 'cros_run_test'))
46*6777b538SAndroid Build Coastguard Worker
47*6777b538SAndroid Build Coastguard WorkerLACROS_LAUNCHER_SCRIPT_PATH = os.path.abspath(
48*6777b538SAndroid Build Coastguard Worker    os.path.join(CHROMIUM_SRC_PATH, 'build', 'lacros',
49*6777b538SAndroid Build Coastguard Worker                 'mojo_connection_lacros_launcher.py'))
50*6777b538SAndroid Build Coastguard Worker
51*6777b538SAndroid Build Coastguard Worker# This is a special hostname that resolves to a different DUT in the lab
52*6777b538SAndroid Build Coastguard Worker# depending on which lab machine you're on.
53*6777b538SAndroid Build Coastguard WorkerLAB_DUT_HOSTNAME = 'variable_chromeos_device_hostname'
54*6777b538SAndroid Build Coastguard Worker
55*6777b538SAndroid Build Coastguard WorkerSYSTEM_LOG_LOCATIONS = [
56*6777b538SAndroid Build Coastguard Worker    '/home/chronos/crash/',
57*6777b538SAndroid Build Coastguard Worker    '/var/log/chrome/',
58*6777b538SAndroid Build Coastguard Worker    '/var/log/messages',
59*6777b538SAndroid Build Coastguard Worker    '/var/log/ui/',
60*6777b538SAndroid Build Coastguard Worker    '/var/log/lacros/',
61*6777b538SAndroid Build Coastguard Worker]
62*6777b538SAndroid Build Coastguard Worker
63*6777b538SAndroid Build Coastguard WorkerTAST_DEBUG_DOC = 'https://bit.ly/2LgvIXz'
64*6777b538SAndroid Build Coastguard Worker
65*6777b538SAndroid Build Coastguard Worker
66*6777b538SAndroid Build Coastguard Workerclass TestFormatError(Exception):
67*6777b538SAndroid Build Coastguard Worker  pass
68*6777b538SAndroid Build Coastguard Worker
69*6777b538SAndroid Build Coastguard Worker
70*6777b538SAndroid Build Coastguard Workerclass RemoteTest:
71*6777b538SAndroid Build Coastguard Worker
72*6777b538SAndroid Build Coastguard Worker  # This is a basic shell script that can be appended to in order to invoke the
73*6777b538SAndroid Build Coastguard Worker  # test on the device.
74*6777b538SAndroid Build Coastguard Worker  BASIC_SHELL_SCRIPT = [
75*6777b538SAndroid Build Coastguard Worker      '#!/bin/sh',
76*6777b538SAndroid Build Coastguard Worker
77*6777b538SAndroid Build Coastguard Worker      # /home and /tmp are mounted with "noexec" in the device, but some of our
78*6777b538SAndroid Build Coastguard Worker      # tools and tests use those dirs as a workspace (eg: vpython downloads
79*6777b538SAndroid Build Coastguard Worker      # python binaries to ~/.vpython-root and /tmp/vpython_bootstrap).
80*6777b538SAndroid Build Coastguard Worker      # /usr/local/tmp doesn't have this restriction, so change the location of
81*6777b538SAndroid Build Coastguard Worker      # the home and temp dirs for the duration of the test.
82*6777b538SAndroid Build Coastguard Worker      'export HOME=/usr/local/tmp',
83*6777b538SAndroid Build Coastguard Worker      'export TMPDIR=/usr/local/tmp',
84*6777b538SAndroid Build Coastguard Worker  ]
85*6777b538SAndroid Build Coastguard Worker
86*6777b538SAndroid Build Coastguard Worker  def __init__(self, args, unknown_args):
87*6777b538SAndroid Build Coastguard Worker    self._additional_args = unknown_args
88*6777b538SAndroid Build Coastguard Worker    self._path_to_outdir = args.path_to_outdir
89*6777b538SAndroid Build Coastguard Worker    self._test_launcher_summary_output = args.test_launcher_summary_output
90*6777b538SAndroid Build Coastguard Worker    self._logs_dir = args.logs_dir
91*6777b538SAndroid Build Coastguard Worker    self._use_vm = args.use_vm
92*6777b538SAndroid Build Coastguard Worker    self._rdb_client = result_sink.TryInitClient()
93*6777b538SAndroid Build Coastguard Worker
94*6777b538SAndroid Build Coastguard Worker    self._retries = 0
95*6777b538SAndroid Build Coastguard Worker    self._timeout = None
96*6777b538SAndroid Build Coastguard Worker    self._test_launcher_shard_index = args.test_launcher_shard_index
97*6777b538SAndroid Build Coastguard Worker    self._test_launcher_total_shards = args.test_launcher_total_shards
98*6777b538SAndroid Build Coastguard Worker
99*6777b538SAndroid Build Coastguard Worker    # The location on disk of a shell script that can be optionally used to
100*6777b538SAndroid Build Coastguard Worker    # invoke the test on the device. If it's not set, we assume self._test_cmd
101*6777b538SAndroid Build Coastguard Worker    # contains the test invocation.
102*6777b538SAndroid Build Coastguard Worker    self._on_device_script = None
103*6777b538SAndroid Build Coastguard Worker
104*6777b538SAndroid Build Coastguard Worker    self._test_cmd = [
105*6777b538SAndroid Build Coastguard Worker        CROS_RUN_TEST_PATH,
106*6777b538SAndroid Build Coastguard Worker        '--board',
107*6777b538SAndroid Build Coastguard Worker        args.board,
108*6777b538SAndroid Build Coastguard Worker        '--cache-dir',
109*6777b538SAndroid Build Coastguard Worker        args.cros_cache,
110*6777b538SAndroid Build Coastguard Worker    ]
111*6777b538SAndroid Build Coastguard Worker    if args.use_vm:
112*6777b538SAndroid Build Coastguard Worker      self._test_cmd += [
113*6777b538SAndroid Build Coastguard Worker          '--start',
114*6777b538SAndroid Build Coastguard Worker          # Don't persist any filesystem changes after the VM shutsdown.
115*6777b538SAndroid Build Coastguard Worker          '--copy-on-write',
116*6777b538SAndroid Build Coastguard Worker      ]
117*6777b538SAndroid Build Coastguard Worker    else:
118*6777b538SAndroid Build Coastguard Worker      if args.fetch_cros_hostname:
119*6777b538SAndroid Build Coastguard Worker        self._test_cmd += ['--device', get_cros_hostname()]
120*6777b538SAndroid Build Coastguard Worker      else:
121*6777b538SAndroid Build Coastguard Worker        self._test_cmd += [
122*6777b538SAndroid Build Coastguard Worker            '--device', args.device if args.device else LAB_DUT_HOSTNAME
123*6777b538SAndroid Build Coastguard Worker        ]
124*6777b538SAndroid Build Coastguard Worker
125*6777b538SAndroid Build Coastguard Worker    if args.logs_dir:
126*6777b538SAndroid Build Coastguard Worker      for log in SYSTEM_LOG_LOCATIONS:
127*6777b538SAndroid Build Coastguard Worker        self._test_cmd += ['--results-src', log]
128*6777b538SAndroid Build Coastguard Worker      self._test_cmd += [
129*6777b538SAndroid Build Coastguard Worker          '--results-dest-dir',
130*6777b538SAndroid Build Coastguard Worker          os.path.join(args.logs_dir, 'system_logs')
131*6777b538SAndroid Build Coastguard Worker      ]
132*6777b538SAndroid Build Coastguard Worker    if args.flash:
133*6777b538SAndroid Build Coastguard Worker      self._test_cmd += ['--flash']
134*6777b538SAndroid Build Coastguard Worker      if args.public_image:
135*6777b538SAndroid Build Coastguard Worker        self._test_cmd += ['--public-image']
136*6777b538SAndroid Build Coastguard Worker
137*6777b538SAndroid Build Coastguard Worker    self._test_env = setup_env()
138*6777b538SAndroid Build Coastguard Worker
139*6777b538SAndroid Build Coastguard Worker  @property
140*6777b538SAndroid Build Coastguard Worker  def suite_name(self):
141*6777b538SAndroid Build Coastguard Worker    raise NotImplementedError('Child classes need to define suite name.')
142*6777b538SAndroid Build Coastguard Worker
143*6777b538SAndroid Build Coastguard Worker  @property
144*6777b538SAndroid Build Coastguard Worker  def test_cmd(self):
145*6777b538SAndroid Build Coastguard Worker    return self._test_cmd
146*6777b538SAndroid Build Coastguard Worker
147*6777b538SAndroid Build Coastguard Worker  def write_test_script_to_disk(self, script_contents):
148*6777b538SAndroid Build Coastguard Worker    # Since we're using an on_device_script to invoke the test, we'll need to
149*6777b538SAndroid Build Coastguard Worker    # set cwd.
150*6777b538SAndroid Build Coastguard Worker    self._test_cmd += [
151*6777b538SAndroid Build Coastguard Worker        '--remote-cmd',
152*6777b538SAndroid Build Coastguard Worker        '--cwd',
153*6777b538SAndroid Build Coastguard Worker        os.path.relpath(self._path_to_outdir, CHROMIUM_SRC_PATH),
154*6777b538SAndroid Build Coastguard Worker    ]
155*6777b538SAndroid Build Coastguard Worker    logging.info('Running the following command on the device:')
156*6777b538SAndroid Build Coastguard Worker    logging.info('\n%s', '\n'.join(script_contents))
157*6777b538SAndroid Build Coastguard Worker    fd, tmp_path = tempfile.mkstemp(suffix='.sh', dir=self._path_to_outdir)
158*6777b538SAndroid Build Coastguard Worker    os.fchmod(fd, 0o755)
159*6777b538SAndroid Build Coastguard Worker    with os.fdopen(fd, 'w') as f:
160*6777b538SAndroid Build Coastguard Worker      f.write('\n'.join(script_contents) + '\n')
161*6777b538SAndroid Build Coastguard Worker    return tmp_path
162*6777b538SAndroid Build Coastguard Worker
163*6777b538SAndroid Build Coastguard Worker  def run_test(self):
164*6777b538SAndroid Build Coastguard Worker    # Traps SIGTERM and kills all child processes of cros_run_test when it's
165*6777b538SAndroid Build Coastguard Worker    # caught. This will allow us to capture logs from the device if a test hangs
166*6777b538SAndroid Build Coastguard Worker    # and gets timeout-killed by swarming. See also:
167*6777b538SAndroid Build Coastguard Worker    # https://chromium.googlesource.com/infra/luci/luci-py/+/main/appengine/swarming/doc/Bot.md#graceful-termination_aka-the-sigterm-and-sigkill-dance
168*6777b538SAndroid Build Coastguard Worker    test_proc = None
169*6777b538SAndroid Build Coastguard Worker
170*6777b538SAndroid Build Coastguard Worker    def _kill_child_procs(trapped_signal, _):
171*6777b538SAndroid Build Coastguard Worker      logging.warning('Received signal %d. Killing child processes of test.',
172*6777b538SAndroid Build Coastguard Worker                      trapped_signal)
173*6777b538SAndroid Build Coastguard Worker      if not test_proc or not test_proc.pid:
174*6777b538SAndroid Build Coastguard Worker        # This shouldn't happen?
175*6777b538SAndroid Build Coastguard Worker        logging.error('Test process not running.')
176*6777b538SAndroid Build Coastguard Worker        return
177*6777b538SAndroid Build Coastguard Worker      for child in psutil.Process(test_proc.pid).children():
178*6777b538SAndroid Build Coastguard Worker        logging.warning('Killing process %s', child)
179*6777b538SAndroid Build Coastguard Worker        child.kill()
180*6777b538SAndroid Build Coastguard Worker
181*6777b538SAndroid Build Coastguard Worker    signal.signal(signal.SIGTERM, _kill_child_procs)
182*6777b538SAndroid Build Coastguard Worker
183*6777b538SAndroid Build Coastguard Worker    for i in range(self._retries + 1):
184*6777b538SAndroid Build Coastguard Worker      logging.info('########################################')
185*6777b538SAndroid Build Coastguard Worker      logging.info('Test attempt #%d', i)
186*6777b538SAndroid Build Coastguard Worker      logging.info('########################################')
187*6777b538SAndroid Build Coastguard Worker      test_proc = subprocess.Popen(
188*6777b538SAndroid Build Coastguard Worker          self._test_cmd,
189*6777b538SAndroid Build Coastguard Worker          stdout=sys.stdout,
190*6777b538SAndroid Build Coastguard Worker          stderr=sys.stderr,
191*6777b538SAndroid Build Coastguard Worker          env=self._test_env)
192*6777b538SAndroid Build Coastguard Worker      try:
193*6777b538SAndroid Build Coastguard Worker        test_proc.wait(timeout=self._timeout)
194*6777b538SAndroid Build Coastguard Worker      except subprocess.TimeoutExpired:  # pylint: disable=no-member
195*6777b538SAndroid Build Coastguard Worker        logging.error('Test timed out. Sending SIGTERM.')
196*6777b538SAndroid Build Coastguard Worker        # SIGTERM the proc and wait 10s for it to close.
197*6777b538SAndroid Build Coastguard Worker        test_proc.terminate()
198*6777b538SAndroid Build Coastguard Worker        try:
199*6777b538SAndroid Build Coastguard Worker          test_proc.wait(timeout=10)
200*6777b538SAndroid Build Coastguard Worker        except subprocess.TimeoutExpired:  # pylint: disable=no-member
201*6777b538SAndroid Build Coastguard Worker          # If it hasn't closed in 10s, SIGKILL it.
202*6777b538SAndroid Build Coastguard Worker          logging.error('Test did not exit in time. Sending SIGKILL.')
203*6777b538SAndroid Build Coastguard Worker          test_proc.kill()
204*6777b538SAndroid Build Coastguard Worker          test_proc.wait()
205*6777b538SAndroid Build Coastguard Worker      logging.info('Test exitted with %d.', test_proc.returncode)
206*6777b538SAndroid Build Coastguard Worker      if test_proc.returncode == 0:
207*6777b538SAndroid Build Coastguard Worker        break
208*6777b538SAndroid Build Coastguard Worker
209*6777b538SAndroid Build Coastguard Worker    self.post_run(test_proc.returncode)
210*6777b538SAndroid Build Coastguard Worker    # Allow post_run to override test proc return code. (Useful when the host
211*6777b538SAndroid Build Coastguard Worker    # side Tast bin returns 0 even for failed tests.)
212*6777b538SAndroid Build Coastguard Worker    return test_proc.returncode
213*6777b538SAndroid Build Coastguard Worker
214*6777b538SAndroid Build Coastguard Worker  def post_run(self, _):
215*6777b538SAndroid Build Coastguard Worker    if self._on_device_script:
216*6777b538SAndroid Build Coastguard Worker      os.remove(self._on_device_script)
217*6777b538SAndroid Build Coastguard Worker
218*6777b538SAndroid Build Coastguard Worker  @staticmethod
219*6777b538SAndroid Build Coastguard Worker  def get_artifacts(path):
220*6777b538SAndroid Build Coastguard Worker    """Crawls a given directory for file artifacts to attach to a test.
221*6777b538SAndroid Build Coastguard Worker
222*6777b538SAndroid Build Coastguard Worker    Args:
223*6777b538SAndroid Build Coastguard Worker      path: Path to a directory to search for artifacts.
224*6777b538SAndroid Build Coastguard Worker    Returns:
225*6777b538SAndroid Build Coastguard Worker      A dict mapping name of the artifact to its absolute filepath.
226*6777b538SAndroid Build Coastguard Worker    """
227*6777b538SAndroid Build Coastguard Worker    artifacts = {}
228*6777b538SAndroid Build Coastguard Worker    for dirpath, _, filenames in os.walk(path):
229*6777b538SAndroid Build Coastguard Worker      for f in filenames:
230*6777b538SAndroid Build Coastguard Worker        artifact_path = os.path.join(dirpath, f)
231*6777b538SAndroid Build Coastguard Worker        artifact_id = os.path.relpath(artifact_path, path)
232*6777b538SAndroid Build Coastguard Worker        # Some artifacts will have non-Latin characters in the filename, eg:
233*6777b538SAndroid Build Coastguard Worker        # 'ui_tree_Chinese Pinyin-你好.txt'. ResultDB's API rejects such
234*6777b538SAndroid Build Coastguard Worker        # characters as an artifact ID, so force the file name down into ascii.
235*6777b538SAndroid Build Coastguard Worker        # For more info, see:
236*6777b538SAndroid Build Coastguard Worker        # https://source.chromium.org/chromium/infra/infra/+/main:go/src/go.chromium.org/luci/resultdb/proto/v1/artifact.proto;drc=3bff13b8037ca76ec19f9810033d914af7ec67cb;l=46
237*6777b538SAndroid Build Coastguard Worker        artifact_id = artifact_id.encode('ascii', 'replace').decode()
238*6777b538SAndroid Build Coastguard Worker        artifact_id = artifact_id.replace('\\', '?')
239*6777b538SAndroid Build Coastguard Worker        artifacts[artifact_id] = {
240*6777b538SAndroid Build Coastguard Worker            'filePath': artifact_path,
241*6777b538SAndroid Build Coastguard Worker        }
242*6777b538SAndroid Build Coastguard Worker    return artifacts
243*6777b538SAndroid Build Coastguard Worker
244*6777b538SAndroid Build Coastguard Worker
245*6777b538SAndroid Build Coastguard Workerclass TastTest(RemoteTest):
246*6777b538SAndroid Build Coastguard Worker
247*6777b538SAndroid Build Coastguard Worker  def __init__(self, args, unknown_args):
248*6777b538SAndroid Build Coastguard Worker    super().__init__(args, unknown_args)
249*6777b538SAndroid Build Coastguard Worker
250*6777b538SAndroid Build Coastguard Worker    self._suite_name = args.suite_name
251*6777b538SAndroid Build Coastguard Worker    self._tast_vars = args.tast_vars
252*6777b538SAndroid Build Coastguard Worker    self._tast_retries = args.tast_retries
253*6777b538SAndroid Build Coastguard Worker    self._tests = args.tests
254*6777b538SAndroid Build Coastguard Worker    # The CQ passes in '--gtest_filter' when specifying tests to skip. Store it
255*6777b538SAndroid Build Coastguard Worker    # here and parse it later to integrate it into Tast executions.
256*6777b538SAndroid Build Coastguard Worker    self._gtest_style_filter = args.gtest_filter
257*6777b538SAndroid Build Coastguard Worker    self._attr_expr = args.attr_expr
258*6777b538SAndroid Build Coastguard Worker    self._should_strip = args.strip_chrome
259*6777b538SAndroid Build Coastguard Worker    self._deploy_lacros = args.deploy_lacros
260*6777b538SAndroid Build Coastguard Worker    self._deploy_chrome = args.deploy_chrome
261*6777b538SAndroid Build Coastguard Worker
262*6777b538SAndroid Build Coastguard Worker    if not self._logs_dir:
263*6777b538SAndroid Build Coastguard Worker      # The host-side Tast bin returns 0 when tests fail, so we need to capture
264*6777b538SAndroid Build Coastguard Worker      # and parse its json results to reliably determine if tests fail.
265*6777b538SAndroid Build Coastguard Worker      raise TestFormatError(
266*6777b538SAndroid Build Coastguard Worker          'When using the host-side Tast bin, "--logs-dir" must be passed in '
267*6777b538SAndroid Build Coastguard Worker          'order to parse its results.')
268*6777b538SAndroid Build Coastguard Worker
269*6777b538SAndroid Build Coastguard Worker    # If the first test filter is negative, it should be safe to assume all of
270*6777b538SAndroid Build Coastguard Worker    # them are, so just test the first filter.
271*6777b538SAndroid Build Coastguard Worker    if self._gtest_style_filter and self._gtest_style_filter[0] == '-':
272*6777b538SAndroid Build Coastguard Worker      raise TestFormatError('Negative test filters not supported for Tast.')
273*6777b538SAndroid Build Coastguard Worker
274*6777b538SAndroid Build Coastguard Worker  @property
275*6777b538SAndroid Build Coastguard Worker  def suite_name(self):
276*6777b538SAndroid Build Coastguard Worker    return self._suite_name
277*6777b538SAndroid Build Coastguard Worker
278*6777b538SAndroid Build Coastguard Worker  def build_test_command(self):
279*6777b538SAndroid Build Coastguard Worker    unsupported_args = [
280*6777b538SAndroid Build Coastguard Worker        '--test-launcher-retry-limit',
281*6777b538SAndroid Build Coastguard Worker        '--test-launcher-batch-limit',
282*6777b538SAndroid Build Coastguard Worker        '--gtest_repeat',
283*6777b538SAndroid Build Coastguard Worker    ]
284*6777b538SAndroid Build Coastguard Worker    for unsupported_arg in unsupported_args:
285*6777b538SAndroid Build Coastguard Worker      if any(arg.startswith(unsupported_arg) for arg in self._additional_args):
286*6777b538SAndroid Build Coastguard Worker        logging.info(
287*6777b538SAndroid Build Coastguard Worker            '%s not supported for Tast tests. The arg will be ignored.',
288*6777b538SAndroid Build Coastguard Worker            unsupported_arg)
289*6777b538SAndroid Build Coastguard Worker        self._additional_args = [
290*6777b538SAndroid Build Coastguard Worker            arg for arg in self._additional_args
291*6777b538SAndroid Build Coastguard Worker            if not arg.startswith(unsupported_arg)
292*6777b538SAndroid Build Coastguard Worker        ]
293*6777b538SAndroid Build Coastguard Worker
294*6777b538SAndroid Build Coastguard Worker    # Lacros deployment mounts itself by default.
295*6777b538SAndroid Build Coastguard Worker    if self._deploy_lacros:
296*6777b538SAndroid Build Coastguard Worker      self._test_cmd.extend([
297*6777b538SAndroid Build Coastguard Worker          '--deploy-lacros', '--lacros-launcher-script',
298*6777b538SAndroid Build Coastguard Worker          LACROS_LAUNCHER_SCRIPT_PATH
299*6777b538SAndroid Build Coastguard Worker      ])
300*6777b538SAndroid Build Coastguard Worker      if self._deploy_chrome:
301*6777b538SAndroid Build Coastguard Worker        self._test_cmd.extend(['--deploy', '--mount'])
302*6777b538SAndroid Build Coastguard Worker    else:
303*6777b538SAndroid Build Coastguard Worker      self._test_cmd.extend(['--deploy', '--mount'])
304*6777b538SAndroid Build Coastguard Worker    self._test_cmd += [
305*6777b538SAndroid Build Coastguard Worker        '--build-dir',
306*6777b538SAndroid Build Coastguard Worker        os.path.relpath(self._path_to_outdir, CHROMIUM_SRC_PATH)
307*6777b538SAndroid Build Coastguard Worker    ] + self._additional_args
308*6777b538SAndroid Build Coastguard Worker
309*6777b538SAndroid Build Coastguard Worker    # Capture tast's results in the logs dir as well.
310*6777b538SAndroid Build Coastguard Worker    if self._logs_dir:
311*6777b538SAndroid Build Coastguard Worker      self._test_cmd += [
312*6777b538SAndroid Build Coastguard Worker          '--results-dir',
313*6777b538SAndroid Build Coastguard Worker          self._logs_dir,
314*6777b538SAndroid Build Coastguard Worker      ]
315*6777b538SAndroid Build Coastguard Worker    self._test_cmd += [
316*6777b538SAndroid Build Coastguard Worker        '--tast-total-shards=%d' % self._test_launcher_total_shards,
317*6777b538SAndroid Build Coastguard Worker        '--tast-shard-index=%d' % self._test_launcher_shard_index,
318*6777b538SAndroid Build Coastguard Worker    ]
319*6777b538SAndroid Build Coastguard Worker    # If we're using a test filter, replace the contents of the Tast
320*6777b538SAndroid Build Coastguard Worker    # conditional with a long list of "name:test" expressions, one for each
321*6777b538SAndroid Build Coastguard Worker    # test in the filter.
322*6777b538SAndroid Build Coastguard Worker    if self._gtest_style_filter:
323*6777b538SAndroid Build Coastguard Worker      if self._attr_expr or self._tests:
324*6777b538SAndroid Build Coastguard Worker        logging.warning(
325*6777b538SAndroid Build Coastguard Worker            'Presence of --gtest_filter will cause the specified Tast expr'
326*6777b538SAndroid Build Coastguard Worker            ' or test list to be ignored.')
327*6777b538SAndroid Build Coastguard Worker      names = []
328*6777b538SAndroid Build Coastguard Worker      for test in self._gtest_style_filter.split(':'):
329*6777b538SAndroid Build Coastguard Worker        names.append('"name:%s"' % test)
330*6777b538SAndroid Build Coastguard Worker      self._attr_expr = '(' + ' || '.join(names) + ')'
331*6777b538SAndroid Build Coastguard Worker
332*6777b538SAndroid Build Coastguard Worker    if self._attr_expr:
333*6777b538SAndroid Build Coastguard Worker      # Don't use shlex.quote() here. Something funky happens with the arg
334*6777b538SAndroid Build Coastguard Worker      # as it gets passed down from cros_run_test to tast. (Tast picks up the
335*6777b538SAndroid Build Coastguard Worker      # escaping single quotes and complains that the attribute expression
336*6777b538SAndroid Build Coastguard Worker      # "must be within parentheses".)
337*6777b538SAndroid Build Coastguard Worker      self._test_cmd.append('--tast=%s' % self._attr_expr)
338*6777b538SAndroid Build Coastguard Worker    else:
339*6777b538SAndroid Build Coastguard Worker      self._test_cmd.append('--tast')
340*6777b538SAndroid Build Coastguard Worker      self._test_cmd.extend(self._tests)
341*6777b538SAndroid Build Coastguard Worker
342*6777b538SAndroid Build Coastguard Worker    for v in self._tast_vars or []:
343*6777b538SAndroid Build Coastguard Worker      self._test_cmd.extend(['--tast-var', v])
344*6777b538SAndroid Build Coastguard Worker
345*6777b538SAndroid Build Coastguard Worker    if self._tast_retries:
346*6777b538SAndroid Build Coastguard Worker      self._test_cmd.append('--tast-retries=%d' % self._tast_retries)
347*6777b538SAndroid Build Coastguard Worker
348*6777b538SAndroid Build Coastguard Worker    # Mounting ash-chrome gives it enough disk space to not need stripping,
349*6777b538SAndroid Build Coastguard Worker    # but only for one not instrumented with code coverage.
350*6777b538SAndroid Build Coastguard Worker    # Lacros uses --nostrip by default, so there is no need to specify.
351*6777b538SAndroid Build Coastguard Worker    if not self._deploy_lacros and not self._should_strip:
352*6777b538SAndroid Build Coastguard Worker      self._test_cmd.append('--nostrip')
353*6777b538SAndroid Build Coastguard Worker
354*6777b538SAndroid Build Coastguard Worker  def post_run(self, return_code):
355*6777b538SAndroid Build Coastguard Worker    tast_results_path = os.path.join(self._logs_dir, 'streamed_results.jsonl')
356*6777b538SAndroid Build Coastguard Worker    if not os.path.exists(tast_results_path):
357*6777b538SAndroid Build Coastguard Worker      logging.error(
358*6777b538SAndroid Build Coastguard Worker          'Tast results not found at %s. Falling back to generic result '
359*6777b538SAndroid Build Coastguard Worker          'reporting.', tast_results_path)
360*6777b538SAndroid Build Coastguard Worker      return super().post_run(return_code)
361*6777b538SAndroid Build Coastguard Worker
362*6777b538SAndroid Build Coastguard Worker    # See the link below for the format of the results:
363*6777b538SAndroid Build Coastguard Worker    # https://godoc.org/chromium.googlesource.com/chromiumos/platform/tast.git/src/chromiumos/cmd/tast/run#TestResult
364*6777b538SAndroid Build Coastguard Worker    with jsonlines.open(tast_results_path) as reader:
365*6777b538SAndroid Build Coastguard Worker      tast_results = collections.deque(reader)
366*6777b538SAndroid Build Coastguard Worker
367*6777b538SAndroid Build Coastguard Worker    suite_results = base_test_result.TestRunResults()
368*6777b538SAndroid Build Coastguard Worker    for test in tast_results:
369*6777b538SAndroid Build Coastguard Worker      errors = test['errors']
370*6777b538SAndroid Build Coastguard Worker      start, end = test['start'], test['end']
371*6777b538SAndroid Build Coastguard Worker      # Use dateutil to parse the timestamps since datetime can't handle
372*6777b538SAndroid Build Coastguard Worker      # nanosecond precision.
373*6777b538SAndroid Build Coastguard Worker      duration = dateutil.parser.parse(end) - dateutil.parser.parse(start)
374*6777b538SAndroid Build Coastguard Worker      # If the duration is negative, Tast has likely reported an incorrect
375*6777b538SAndroid Build Coastguard Worker      # duration. See https://issuetracker.google.com/issues/187973541. Round
376*6777b538SAndroid Build Coastguard Worker      # up to 0 in that case to avoid confusing RDB.
377*6777b538SAndroid Build Coastguard Worker      duration_ms = max(duration.total_seconds() * 1000, 0)
378*6777b538SAndroid Build Coastguard Worker      if bool(test['skipReason']):
379*6777b538SAndroid Build Coastguard Worker        result = base_test_result.ResultType.SKIP
380*6777b538SAndroid Build Coastguard Worker      elif errors:
381*6777b538SAndroid Build Coastguard Worker        result = base_test_result.ResultType.FAIL
382*6777b538SAndroid Build Coastguard Worker      else:
383*6777b538SAndroid Build Coastguard Worker        result = base_test_result.ResultType.PASS
384*6777b538SAndroid Build Coastguard Worker      primary_error_message = None
385*6777b538SAndroid Build Coastguard Worker      error_log = ''
386*6777b538SAndroid Build Coastguard Worker      if errors:
387*6777b538SAndroid Build Coastguard Worker        # See the link below for the format of these errors:
388*6777b538SAndroid Build Coastguard Worker        # https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/platform/tast/src/chromiumos/tast/cmd/tast/internal/run/resultsjson/resultsjson.go
389*6777b538SAndroid Build Coastguard Worker        primary_error_message = errors[0]['reason']
390*6777b538SAndroid Build Coastguard Worker        for err in errors:
391*6777b538SAndroid Build Coastguard Worker          error_log += err['stack'] + '\n'
392*6777b538SAndroid Build Coastguard Worker      debug_link = ("If you're unsure why this test failed, consult the steps "
393*6777b538SAndroid Build Coastguard Worker                    'outlined <a href="%s">here</a>.' % TAST_DEBUG_DOC)
394*6777b538SAndroid Build Coastguard Worker      base_result = base_test_result.BaseTestResult(
395*6777b538SAndroid Build Coastguard Worker          test['name'], result, duration=duration_ms, log=error_log)
396*6777b538SAndroid Build Coastguard Worker      suite_results.AddResult(base_result)
397*6777b538SAndroid Build Coastguard Worker      self._maybe_handle_perf_results(test['name'])
398*6777b538SAndroid Build Coastguard Worker
399*6777b538SAndroid Build Coastguard Worker      if self._rdb_client:
400*6777b538SAndroid Build Coastguard Worker        # Walk the contents of the test's "outDir" and atttach any file found
401*6777b538SAndroid Build Coastguard Worker        # inside as an RDB 'artifact'. (This could include system logs, screen
402*6777b538SAndroid Build Coastguard Worker        # shots, etc.)
403*6777b538SAndroid Build Coastguard Worker        artifacts = self.get_artifacts(test['outDir'])
404*6777b538SAndroid Build Coastguard Worker        html_artifact = debug_link
405*6777b538SAndroid Build Coastguard Worker        if result == base_test_result.ResultType.SKIP:
406*6777b538SAndroid Build Coastguard Worker          html_artifact = 'Test was skipped because: ' + test['skipReason']
407*6777b538SAndroid Build Coastguard Worker        self._rdb_client.Post(
408*6777b538SAndroid Build Coastguard Worker            test['name'],
409*6777b538SAndroid Build Coastguard Worker            result,
410*6777b538SAndroid Build Coastguard Worker            duration_ms,
411*6777b538SAndroid Build Coastguard Worker            error_log,
412*6777b538SAndroid Build Coastguard Worker            None,
413*6777b538SAndroid Build Coastguard Worker            artifacts=artifacts,
414*6777b538SAndroid Build Coastguard Worker            failure_reason=primary_error_message,
415*6777b538SAndroid Build Coastguard Worker            html_artifact=html_artifact)
416*6777b538SAndroid Build Coastguard Worker
417*6777b538SAndroid Build Coastguard Worker    if self._rdb_client and self._logs_dir:
418*6777b538SAndroid Build Coastguard Worker      # Attach artifacts from the device that don't apply to a single test.
419*6777b538SAndroid Build Coastguard Worker      artifacts = self.get_artifacts(
420*6777b538SAndroid Build Coastguard Worker          os.path.join(self._logs_dir, 'system_logs'))
421*6777b538SAndroid Build Coastguard Worker      artifacts.update(
422*6777b538SAndroid Build Coastguard Worker          self.get_artifacts(os.path.join(self._logs_dir, 'crashes')))
423*6777b538SAndroid Build Coastguard Worker      self._rdb_client.ReportInvocationLevelArtifacts(artifacts)
424*6777b538SAndroid Build Coastguard Worker
425*6777b538SAndroid Build Coastguard Worker    if self._test_launcher_summary_output:
426*6777b538SAndroid Build Coastguard Worker      with open(self._test_launcher_summary_output, 'w') as f:
427*6777b538SAndroid Build Coastguard Worker        json.dump(json_results.GenerateResultsDict([suite_results]), f)
428*6777b538SAndroid Build Coastguard Worker
429*6777b538SAndroid Build Coastguard Worker    if not suite_results.DidRunPass():
430*6777b538SAndroid Build Coastguard Worker      return 1
431*6777b538SAndroid Build Coastguard Worker    if return_code:
432*6777b538SAndroid Build Coastguard Worker      logging.warning(
433*6777b538SAndroid Build Coastguard Worker          'No failed tests found, but exit code of %d was returned from '
434*6777b538SAndroid Build Coastguard Worker          'cros_run_test.', return_code)
435*6777b538SAndroid Build Coastguard Worker      return return_code
436*6777b538SAndroid Build Coastguard Worker    return 0
437*6777b538SAndroid Build Coastguard Worker
438*6777b538SAndroid Build Coastguard Worker  def _maybe_handle_perf_results(self, test_name):
439*6777b538SAndroid Build Coastguard Worker    """Prepares any perf results from |test_name| for process_perf_results.
440*6777b538SAndroid Build Coastguard Worker
441*6777b538SAndroid Build Coastguard Worker    - process_perf_results looks for top level directories containing a
442*6777b538SAndroid Build Coastguard Worker      perf_results.json file and a test_results.json file. The directory names
443*6777b538SAndroid Build Coastguard Worker      are used as the benchmark names.
444*6777b538SAndroid Build Coastguard Worker    - If a perf_results.json or results-chart.json file exists in the
445*6777b538SAndroid Build Coastguard Worker      |test_name| results directory, a top level directory is created and the
446*6777b538SAndroid Build Coastguard Worker      perf results file is copied to perf_results.json.
447*6777b538SAndroid Build Coastguard Worker    - A trivial test_results.json file is also created to indicate that the test
448*6777b538SAndroid Build Coastguard Worker      succeeded (this function would not be called otherwise).
449*6777b538SAndroid Build Coastguard Worker    - When process_perf_results is run, it will find the expected files in the
450*6777b538SAndroid Build Coastguard Worker      named directory and upload the benchmark results.
451*6777b538SAndroid Build Coastguard Worker    """
452*6777b538SAndroid Build Coastguard Worker
453*6777b538SAndroid Build Coastguard Worker    perf_results = os.path.join(self._logs_dir, 'tests', test_name,
454*6777b538SAndroid Build Coastguard Worker                                'perf_results.json')
455*6777b538SAndroid Build Coastguard Worker    # TODO(stevenjb): Remove check for crosbolt results-chart.json file.
456*6777b538SAndroid Build Coastguard Worker    if not os.path.exists(perf_results):
457*6777b538SAndroid Build Coastguard Worker      perf_results = os.path.join(self._logs_dir, 'tests', test_name,
458*6777b538SAndroid Build Coastguard Worker                                  'results-chart.json')
459*6777b538SAndroid Build Coastguard Worker    if os.path.exists(perf_results):
460*6777b538SAndroid Build Coastguard Worker      benchmark_dir = os.path.join(self._logs_dir, test_name)
461*6777b538SAndroid Build Coastguard Worker      if not os.path.isdir(benchmark_dir):
462*6777b538SAndroid Build Coastguard Worker        os.makedirs(benchmark_dir)
463*6777b538SAndroid Build Coastguard Worker      shutil.copyfile(perf_results,
464*6777b538SAndroid Build Coastguard Worker                      os.path.join(benchmark_dir, 'perf_results.json'))
465*6777b538SAndroid Build Coastguard Worker      # process_perf_results.py expects a test_results.json file.
466*6777b538SAndroid Build Coastguard Worker      test_results = {'valid': True, 'failures': []}
467*6777b538SAndroid Build Coastguard Worker      with open(os.path.join(benchmark_dir, 'test_results.json'), 'w') as out:
468*6777b538SAndroid Build Coastguard Worker        json.dump(test_results, out)
469*6777b538SAndroid Build Coastguard Worker
470*6777b538SAndroid Build Coastguard Worker
471*6777b538SAndroid Build Coastguard Workerclass GTestTest(RemoteTest):
472*6777b538SAndroid Build Coastguard Worker
473*6777b538SAndroid Build Coastguard Worker  # The following list corresponds to paths that should not be copied over to
474*6777b538SAndroid Build Coastguard Worker  # the device during tests. In other words, these files are only ever used on
475*6777b538SAndroid Build Coastguard Worker  # the host.
476*6777b538SAndroid Build Coastguard Worker  _FILE_IGNORELIST = [
477*6777b538SAndroid Build Coastguard Worker      re.compile(r'.*build/android.*'),
478*6777b538SAndroid Build Coastguard Worker      re.compile(r'.*build/chromeos.*'),
479*6777b538SAndroid Build Coastguard Worker      re.compile(r'.*build/cros_cache.*'),
480*6777b538SAndroid Build Coastguard Worker      # The following matches anything under //testing/ that isn't under
481*6777b538SAndroid Build Coastguard Worker      # //testing/buildbot/filters/.
482*6777b538SAndroid Build Coastguard Worker      re.compile(r'.*testing/(?!buildbot/filters).*'),
483*6777b538SAndroid Build Coastguard Worker      re.compile(r'.*third_party/chromite.*'),
484*6777b538SAndroid Build Coastguard Worker  ]
485*6777b538SAndroid Build Coastguard Worker
486*6777b538SAndroid Build Coastguard Worker  def __init__(self, args, unknown_args):
487*6777b538SAndroid Build Coastguard Worker    super().__init__(args, unknown_args)
488*6777b538SAndroid Build Coastguard Worker
489*6777b538SAndroid Build Coastguard Worker    self._test_cmd = ['vpython3'] + self._test_cmd
490*6777b538SAndroid Build Coastguard Worker    if not args.clean:
491*6777b538SAndroid Build Coastguard Worker      self._test_cmd += ['--no-clean']
492*6777b538SAndroid Build Coastguard Worker
493*6777b538SAndroid Build Coastguard Worker    self._test_exe = args.test_exe
494*6777b538SAndroid Build Coastguard Worker    self._runtime_deps_path = args.runtime_deps_path
495*6777b538SAndroid Build Coastguard Worker    self._vpython_dir = args.vpython_dir
496*6777b538SAndroid Build Coastguard Worker
497*6777b538SAndroid Build Coastguard Worker    self._on_device_script = None
498*6777b538SAndroid Build Coastguard Worker    self._env_vars = args.env_var
499*6777b538SAndroid Build Coastguard Worker    self._stop_ui = args.stop_ui
500*6777b538SAndroid Build Coastguard Worker    self._trace_dir = args.trace_dir
501*6777b538SAndroid Build Coastguard Worker    self._run_test_sudo_helper = args.run_test_sudo_helper
502*6777b538SAndroid Build Coastguard Worker    self._set_selinux_label = args.set_selinux_label
503*6777b538SAndroid Build Coastguard Worker
504*6777b538SAndroid Build Coastguard Worker  @property
505*6777b538SAndroid Build Coastguard Worker  def suite_name(self):
506*6777b538SAndroid Build Coastguard Worker    return self._test_exe
507*6777b538SAndroid Build Coastguard Worker
508*6777b538SAndroid Build Coastguard Worker  def build_test_command(self):
509*6777b538SAndroid Build Coastguard Worker    # To keep things easy for us, ensure both types of output locations are
510*6777b538SAndroid Build Coastguard Worker    # the same.
511*6777b538SAndroid Build Coastguard Worker    if self._test_launcher_summary_output and self._logs_dir:
512*6777b538SAndroid Build Coastguard Worker      json_out_dir = os.path.dirname(self._test_launcher_summary_output) or '.'
513*6777b538SAndroid Build Coastguard Worker      if os.path.abspath(json_out_dir) != os.path.abspath(self._logs_dir):
514*6777b538SAndroid Build Coastguard Worker        raise TestFormatError(
515*6777b538SAndroid Build Coastguard Worker            '--test-launcher-summary-output and --logs-dir must point to '
516*6777b538SAndroid Build Coastguard Worker            'the same directory.')
517*6777b538SAndroid Build Coastguard Worker
518*6777b538SAndroid Build Coastguard Worker    if self._test_launcher_summary_output:
519*6777b538SAndroid Build Coastguard Worker      result_dir, result_file = os.path.split(
520*6777b538SAndroid Build Coastguard Worker          self._test_launcher_summary_output)
521*6777b538SAndroid Build Coastguard Worker      # If args.test_launcher_summary_output is a file in cwd, result_dir will
522*6777b538SAndroid Build Coastguard Worker      # be an empty string, so replace it with '.' when this is the case so
523*6777b538SAndroid Build Coastguard Worker      # cros_run_test can correctly handle it.
524*6777b538SAndroid Build Coastguard Worker      if not result_dir:
525*6777b538SAndroid Build Coastguard Worker        result_dir = '.'
526*6777b538SAndroid Build Coastguard Worker      device_result_file = '/tmp/%s' % result_file
527*6777b538SAndroid Build Coastguard Worker      self._test_cmd += [
528*6777b538SAndroid Build Coastguard Worker          '--results-src',
529*6777b538SAndroid Build Coastguard Worker          device_result_file,
530*6777b538SAndroid Build Coastguard Worker          '--results-dest-dir',
531*6777b538SAndroid Build Coastguard Worker          result_dir,
532*6777b538SAndroid Build Coastguard Worker      ]
533*6777b538SAndroid Build Coastguard Worker
534*6777b538SAndroid Build Coastguard Worker    if self._trace_dir and self._logs_dir:
535*6777b538SAndroid Build Coastguard Worker      trace_path = os.path.dirname(self._trace_dir) or '.'
536*6777b538SAndroid Build Coastguard Worker      if os.path.abspath(trace_path) != os.path.abspath(self._logs_dir):
537*6777b538SAndroid Build Coastguard Worker        raise TestFormatError(
538*6777b538SAndroid Build Coastguard Worker            '--trace-dir and --logs-dir must point to the same directory.')
539*6777b538SAndroid Build Coastguard Worker
540*6777b538SAndroid Build Coastguard Worker    if self._trace_dir:
541*6777b538SAndroid Build Coastguard Worker      trace_path, trace_dirname = os.path.split(self._trace_dir)
542*6777b538SAndroid Build Coastguard Worker      device_trace_dir = '/tmp/%s' % trace_dirname
543*6777b538SAndroid Build Coastguard Worker      self._test_cmd += [
544*6777b538SAndroid Build Coastguard Worker          '--results-src',
545*6777b538SAndroid Build Coastguard Worker          device_trace_dir,
546*6777b538SAndroid Build Coastguard Worker          '--results-dest-dir',
547*6777b538SAndroid Build Coastguard Worker          trace_path,
548*6777b538SAndroid Build Coastguard Worker      ]
549*6777b538SAndroid Build Coastguard Worker
550*6777b538SAndroid Build Coastguard Worker    # Build the shell script that will be used on the device to invoke the test.
551*6777b538SAndroid Build Coastguard Worker    # Stored here as a list of lines.
552*6777b538SAndroid Build Coastguard Worker    device_test_script_contents = self.BASIC_SHELL_SCRIPT[:]
553*6777b538SAndroid Build Coastguard Worker    for var_name, var_val in self._env_vars:
554*6777b538SAndroid Build Coastguard Worker      device_test_script_contents += ['export %s=%s' % (var_name, var_val)]
555*6777b538SAndroid Build Coastguard Worker
556*6777b538SAndroid Build Coastguard Worker    if self._vpython_dir:
557*6777b538SAndroid Build Coastguard Worker      vpython_path = os.path.join(self._path_to_outdir, self._vpython_dir,
558*6777b538SAndroid Build Coastguard Worker                                  'vpython3')
559*6777b538SAndroid Build Coastguard Worker      cpython_path = os.path.join(self._path_to_outdir, self._vpython_dir,
560*6777b538SAndroid Build Coastguard Worker                                  'bin', 'python3')
561*6777b538SAndroid Build Coastguard Worker      if not os.path.exists(vpython_path) or not os.path.exists(cpython_path):
562*6777b538SAndroid Build Coastguard Worker        raise TestFormatError(
563*6777b538SAndroid Build Coastguard Worker            '--vpython-dir must point to a dir with both '
564*6777b538SAndroid Build Coastguard Worker            'infra/3pp/tools/cpython3 and infra/tools/luci/vpython installed.')
565*6777b538SAndroid Build Coastguard Worker      vpython_spec_path = os.path.relpath(
566*6777b538SAndroid Build Coastguard Worker          os.path.join(CHROMIUM_SRC_PATH, '.vpython3'), self._path_to_outdir)
567*6777b538SAndroid Build Coastguard Worker      # Initialize the vpython cache. This can take 10-20s, and some tests
568*6777b538SAndroid Build Coastguard Worker      # can't afford to wait that long on the first invocation.
569*6777b538SAndroid Build Coastguard Worker      device_test_script_contents.extend([
570*6777b538SAndroid Build Coastguard Worker          'export PATH=$PWD/%s:$PWD/%s/bin/:$PATH' %
571*6777b538SAndroid Build Coastguard Worker          (self._vpython_dir, self._vpython_dir),
572*6777b538SAndroid Build Coastguard Worker          'vpython3 -vpython-spec %s -vpython-tool install' %
573*6777b538SAndroid Build Coastguard Worker          (vpython_spec_path),
574*6777b538SAndroid Build Coastguard Worker      ])
575*6777b538SAndroid Build Coastguard Worker
576*6777b538SAndroid Build Coastguard Worker    test_invocation = ('LD_LIBRARY_PATH=./ ./%s --test-launcher-shard-index=%d '
577*6777b538SAndroid Build Coastguard Worker                       '--test-launcher-total-shards=%d' %
578*6777b538SAndroid Build Coastguard Worker                       (self._test_exe, self._test_launcher_shard_index,
579*6777b538SAndroid Build Coastguard Worker                        self._test_launcher_total_shards))
580*6777b538SAndroid Build Coastguard Worker    if self._test_launcher_summary_output:
581*6777b538SAndroid Build Coastguard Worker      test_invocation += ' --test-launcher-summary-output=%s' % (
582*6777b538SAndroid Build Coastguard Worker          device_result_file)
583*6777b538SAndroid Build Coastguard Worker
584*6777b538SAndroid Build Coastguard Worker    if self._trace_dir:
585*6777b538SAndroid Build Coastguard Worker      device_test_script_contents.extend([
586*6777b538SAndroid Build Coastguard Worker          'rm -rf %s' % device_trace_dir,
587*6777b538SAndroid Build Coastguard Worker          'sudo -E -u chronos -- /bin/bash -c "mkdir -p %s"' % device_trace_dir,
588*6777b538SAndroid Build Coastguard Worker      ])
589*6777b538SAndroid Build Coastguard Worker      test_invocation += ' --trace-dir=%s' % device_trace_dir
590*6777b538SAndroid Build Coastguard Worker
591*6777b538SAndroid Build Coastguard Worker    if self._run_test_sudo_helper:
592*6777b538SAndroid Build Coastguard Worker      device_test_script_contents.extend([
593*6777b538SAndroid Build Coastguard Worker          'TEST_SUDO_HELPER_PATH=$(mktemp)',
594*6777b538SAndroid Build Coastguard Worker          './test_sudo_helper.py --socket-path=${TEST_SUDO_HELPER_PATH} &',
595*6777b538SAndroid Build Coastguard Worker          'TEST_SUDO_HELPER_PID=$!'
596*6777b538SAndroid Build Coastguard Worker      ])
597*6777b538SAndroid Build Coastguard Worker      test_invocation += (
598*6777b538SAndroid Build Coastguard Worker          ' --test-sudo-helper-socket-path=${TEST_SUDO_HELPER_PATH}')
599*6777b538SAndroid Build Coastguard Worker
600*6777b538SAndroid Build Coastguard Worker    # Append the selinux labels. The 'setfiles' command takes a file with each
601*6777b538SAndroid Build Coastguard Worker    # line consisting of "<file-regex> <file-type> <new-label>", where '--' is
602*6777b538SAndroid Build Coastguard Worker    # the type of a regular file.
603*6777b538SAndroid Build Coastguard Worker    if self._set_selinux_label:
604*6777b538SAndroid Build Coastguard Worker      for label_pair in self._set_selinux_label:
605*6777b538SAndroid Build Coastguard Worker        filename, label = label_pair.split('=', 1)
606*6777b538SAndroid Build Coastguard Worker        specfile = filename + '.specfile'
607*6777b538SAndroid Build Coastguard Worker        device_test_script_contents.extend([
608*6777b538SAndroid Build Coastguard Worker            'echo %s -- %s > %s' % (filename, label, specfile),
609*6777b538SAndroid Build Coastguard Worker            'setfiles -F %s %s' % (specfile, filename),
610*6777b538SAndroid Build Coastguard Worker        ])
611*6777b538SAndroid Build Coastguard Worker
612*6777b538SAndroid Build Coastguard Worker    if self._additional_args:
613*6777b538SAndroid Build Coastguard Worker      test_invocation += ' %s' % ' '.join(self._additional_args)
614*6777b538SAndroid Build Coastguard Worker
615*6777b538SAndroid Build Coastguard Worker    if self._stop_ui:
616*6777b538SAndroid Build Coastguard Worker      device_test_script_contents += [
617*6777b538SAndroid Build Coastguard Worker          'stop ui',
618*6777b538SAndroid Build Coastguard Worker      ]
619*6777b538SAndroid Build Coastguard Worker      # Send a user activity ping to powerd to ensure the display is on.
620*6777b538SAndroid Build Coastguard Worker      device_test_script_contents += [
621*6777b538SAndroid Build Coastguard Worker          'dbus-send --system --type=method_call'
622*6777b538SAndroid Build Coastguard Worker          ' --dest=org.chromium.PowerManager /org/chromium/PowerManager'
623*6777b538SAndroid Build Coastguard Worker          ' org.chromium.PowerManager.HandleUserActivity int32:0'
624*6777b538SAndroid Build Coastguard Worker      ]
625*6777b538SAndroid Build Coastguard Worker      # The UI service on the device owns the chronos user session, so shutting
626*6777b538SAndroid Build Coastguard Worker      # it down as chronos kills the entire execution of the test. So we'll have
627*6777b538SAndroid Build Coastguard Worker      # to run as root up until the test invocation.
628*6777b538SAndroid Build Coastguard Worker      test_invocation = (
629*6777b538SAndroid Build Coastguard Worker          'sudo -E -u chronos -- /bin/bash -c "%s"' % test_invocation)
630*6777b538SAndroid Build Coastguard Worker      # And we'll need to chown everything since cros_run_test's "--as-chronos"
631*6777b538SAndroid Build Coastguard Worker      # option normally does that for us.
632*6777b538SAndroid Build Coastguard Worker      device_test_script_contents.append('chown -R chronos: ../..')
633*6777b538SAndroid Build Coastguard Worker    else:
634*6777b538SAndroid Build Coastguard Worker      self._test_cmd += [
635*6777b538SAndroid Build Coastguard Worker          # Some tests fail as root, so run as the less privileged user
636*6777b538SAndroid Build Coastguard Worker          # 'chronos'.
637*6777b538SAndroid Build Coastguard Worker          '--as-chronos',
638*6777b538SAndroid Build Coastguard Worker      ]
639*6777b538SAndroid Build Coastguard Worker
640*6777b538SAndroid Build Coastguard Worker    device_test_script_contents.append(test_invocation)
641*6777b538SAndroid Build Coastguard Worker    device_test_script_contents.append('TEST_RETURN_CODE=$?')
642*6777b538SAndroid Build Coastguard Worker
643*6777b538SAndroid Build Coastguard Worker    # (Re)start ui after all tests are done. This is for developer convenienve.
644*6777b538SAndroid Build Coastguard Worker    # Without this, the device would remain in a black screen which looks like
645*6777b538SAndroid Build Coastguard Worker    # powered off.
646*6777b538SAndroid Build Coastguard Worker    if self._stop_ui:
647*6777b538SAndroid Build Coastguard Worker      device_test_script_contents += [
648*6777b538SAndroid Build Coastguard Worker          'start ui',
649*6777b538SAndroid Build Coastguard Worker      ]
650*6777b538SAndroid Build Coastguard Worker
651*6777b538SAndroid Build Coastguard Worker    # Stop the crosier helper.
652*6777b538SAndroid Build Coastguard Worker    if self._run_test_sudo_helper:
653*6777b538SAndroid Build Coastguard Worker      device_test_script_contents.extend([
654*6777b538SAndroid Build Coastguard Worker          'pkill -P $TEST_SUDO_HELPER_PID',
655*6777b538SAndroid Build Coastguard Worker          'kill $TEST_SUDO_HELPER_PID',
656*6777b538SAndroid Build Coastguard Worker          'unlink ${TEST_SUDO_HELPER_PATH}',
657*6777b538SAndroid Build Coastguard Worker      ])
658*6777b538SAndroid Build Coastguard Worker
659*6777b538SAndroid Build Coastguard Worker    # This command should always be the last bash commandline so infra can
660*6777b538SAndroid Build Coastguard Worker    # correctly get the error code from test invocations.
661*6777b538SAndroid Build Coastguard Worker    device_test_script_contents.append('exit $TEST_RETURN_CODE')
662*6777b538SAndroid Build Coastguard Worker
663*6777b538SAndroid Build Coastguard Worker    self._on_device_script = self.write_test_script_to_disk(
664*6777b538SAndroid Build Coastguard Worker        device_test_script_contents)
665*6777b538SAndroid Build Coastguard Worker
666*6777b538SAndroid Build Coastguard Worker    runtime_files = [os.path.relpath(self._on_device_script)]
667*6777b538SAndroid Build Coastguard Worker    runtime_files += self._read_runtime_files()
668*6777b538SAndroid Build Coastguard Worker    if self._vpython_dir:
669*6777b538SAndroid Build Coastguard Worker      # --vpython-dir is relative to the out dir, but --files expects paths
670*6777b538SAndroid Build Coastguard Worker      # relative to src dir, so fix the path up a bit.
671*6777b538SAndroid Build Coastguard Worker      runtime_files.append(
672*6777b538SAndroid Build Coastguard Worker          os.path.relpath(
673*6777b538SAndroid Build Coastguard Worker              os.path.abspath(
674*6777b538SAndroid Build Coastguard Worker                  os.path.join(self._path_to_outdir, self._vpython_dir)),
675*6777b538SAndroid Build Coastguard Worker              CHROMIUM_SRC_PATH))
676*6777b538SAndroid Build Coastguard Worker
677*6777b538SAndroid Build Coastguard Worker    for f in runtime_files:
678*6777b538SAndroid Build Coastguard Worker      self._test_cmd.extend(['--files', f])
679*6777b538SAndroid Build Coastguard Worker
680*6777b538SAndroid Build Coastguard Worker    self._test_cmd += [
681*6777b538SAndroid Build Coastguard Worker        '--',
682*6777b538SAndroid Build Coastguard Worker        './' + os.path.relpath(self._on_device_script, self._path_to_outdir)
683*6777b538SAndroid Build Coastguard Worker    ]
684*6777b538SAndroid Build Coastguard Worker
685*6777b538SAndroid Build Coastguard Worker  def _read_runtime_files(self):
686*6777b538SAndroid Build Coastguard Worker    if not self._runtime_deps_path:
687*6777b538SAndroid Build Coastguard Worker      return []
688*6777b538SAndroid Build Coastguard Worker
689*6777b538SAndroid Build Coastguard Worker    abs_runtime_deps_path = os.path.abspath(
690*6777b538SAndroid Build Coastguard Worker        os.path.join(self._path_to_outdir, self._runtime_deps_path))
691*6777b538SAndroid Build Coastguard Worker    with open(abs_runtime_deps_path) as runtime_deps_file:
692*6777b538SAndroid Build Coastguard Worker      files = [l.strip() for l in runtime_deps_file if l]
693*6777b538SAndroid Build Coastguard Worker    rel_file_paths = []
694*6777b538SAndroid Build Coastguard Worker    for f in files:
695*6777b538SAndroid Build Coastguard Worker      rel_file_path = os.path.relpath(
696*6777b538SAndroid Build Coastguard Worker          os.path.abspath(os.path.join(self._path_to_outdir, f)))
697*6777b538SAndroid Build Coastguard Worker      if not any(regex.match(rel_file_path) for regex in self._FILE_IGNORELIST):
698*6777b538SAndroid Build Coastguard Worker        rel_file_paths.append(rel_file_path)
699*6777b538SAndroid Build Coastguard Worker    return rel_file_paths
700*6777b538SAndroid Build Coastguard Worker
701*6777b538SAndroid Build Coastguard Worker  def post_run(self, _):
702*6777b538SAndroid Build Coastguard Worker    if self._on_device_script:
703*6777b538SAndroid Build Coastguard Worker      os.remove(self._on_device_script)
704*6777b538SAndroid Build Coastguard Worker
705*6777b538SAndroid Build Coastguard Worker    if self._test_launcher_summary_output and self._rdb_client:
706*6777b538SAndroid Build Coastguard Worker      logging.error('Native ResultDB integration is not supported for GTests. '
707*6777b538SAndroid Build Coastguard Worker                    'Upload results via result_adapter instead. '
708*6777b538SAndroid Build Coastguard Worker                    'See crbug.com/1330441.')
709*6777b538SAndroid Build Coastguard Worker
710*6777b538SAndroid Build Coastguard Worker
711*6777b538SAndroid Build Coastguard Workerdef device_test(args, unknown_args):
712*6777b538SAndroid Build Coastguard Worker  # cros_run_test has trouble with relative paths that go up directories,
713*6777b538SAndroid Build Coastguard Worker  # so cd to src/, which should be the root of all data deps.
714*6777b538SAndroid Build Coastguard Worker  os.chdir(CHROMIUM_SRC_PATH)
715*6777b538SAndroid Build Coastguard Worker
716*6777b538SAndroid Build Coastguard Worker  # TODO: Remove the above when depot_tool's pylint is updated to include the
717*6777b538SAndroid Build Coastguard Worker  # fix to https://github.com/PyCQA/pylint/issues/710.
718*6777b538SAndroid Build Coastguard Worker  if args.test_type == 'tast':
719*6777b538SAndroid Build Coastguard Worker    test = TastTest(args, unknown_args)
720*6777b538SAndroid Build Coastguard Worker  else:
721*6777b538SAndroid Build Coastguard Worker    test = GTestTest(args, unknown_args)
722*6777b538SAndroid Build Coastguard Worker
723*6777b538SAndroid Build Coastguard Worker  test.build_test_command()
724*6777b538SAndroid Build Coastguard Worker  logging.info('Running the following command on the device:')
725*6777b538SAndroid Build Coastguard Worker  logging.info(' '.join(test.test_cmd))
726*6777b538SAndroid Build Coastguard Worker
727*6777b538SAndroid Build Coastguard Worker  return test.run_test()
728*6777b538SAndroid Build Coastguard Worker
729*6777b538SAndroid Build Coastguard Worker
730*6777b538SAndroid Build Coastguard Workerdef host_cmd(args, cmd_args):
731*6777b538SAndroid Build Coastguard Worker  if not cmd_args:
732*6777b538SAndroid Build Coastguard Worker    raise TestFormatError('Must specify command to run on the host.')
733*6777b538SAndroid Build Coastguard Worker  if args.deploy_chrome and not args.path_to_outdir:
734*6777b538SAndroid Build Coastguard Worker    raise TestFormatError(
735*6777b538SAndroid Build Coastguard Worker        '--path-to-outdir must be specified if --deploy-chrome is passed.')
736*6777b538SAndroid Build Coastguard Worker
737*6777b538SAndroid Build Coastguard Worker  cros_run_test_cmd = [
738*6777b538SAndroid Build Coastguard Worker      CROS_RUN_TEST_PATH,
739*6777b538SAndroid Build Coastguard Worker      '--board',
740*6777b538SAndroid Build Coastguard Worker      args.board,
741*6777b538SAndroid Build Coastguard Worker      '--cache-dir',
742*6777b538SAndroid Build Coastguard Worker      os.path.join(CHROMIUM_SRC_PATH, args.cros_cache),
743*6777b538SAndroid Build Coastguard Worker  ]
744*6777b538SAndroid Build Coastguard Worker  if args.use_vm:
745*6777b538SAndroid Build Coastguard Worker    cros_run_test_cmd += [
746*6777b538SAndroid Build Coastguard Worker        '--start',
747*6777b538SAndroid Build Coastguard Worker        # Don't persist any filesystem changes after the VM shutsdown.
748*6777b538SAndroid Build Coastguard Worker        '--copy-on-write',
749*6777b538SAndroid Build Coastguard Worker    ]
750*6777b538SAndroid Build Coastguard Worker  else:
751*6777b538SAndroid Build Coastguard Worker    if args.fetch_cros_hostname:
752*6777b538SAndroid Build Coastguard Worker      cros_run_test_cmd += ['--device', get_cros_hostname()]
753*6777b538SAndroid Build Coastguard Worker    else:
754*6777b538SAndroid Build Coastguard Worker      cros_run_test_cmd += [
755*6777b538SAndroid Build Coastguard Worker          '--device', args.device if args.device else LAB_DUT_HOSTNAME
756*6777b538SAndroid Build Coastguard Worker      ]
757*6777b538SAndroid Build Coastguard Worker  if args.verbose:
758*6777b538SAndroid Build Coastguard Worker    cros_run_test_cmd.append('--debug')
759*6777b538SAndroid Build Coastguard Worker  if args.flash:
760*6777b538SAndroid Build Coastguard Worker    cros_run_test_cmd.append('--flash')
761*6777b538SAndroid Build Coastguard Worker    if args.public_image:
762*6777b538SAndroid Build Coastguard Worker      cros_run_test_cmd += ['--public-image']
763*6777b538SAndroid Build Coastguard Worker
764*6777b538SAndroid Build Coastguard Worker  if args.logs_dir:
765*6777b538SAndroid Build Coastguard Worker    for log in SYSTEM_LOG_LOCATIONS:
766*6777b538SAndroid Build Coastguard Worker      cros_run_test_cmd += ['--results-src', log]
767*6777b538SAndroid Build Coastguard Worker    cros_run_test_cmd += [
768*6777b538SAndroid Build Coastguard Worker        '--results-dest-dir',
769*6777b538SAndroid Build Coastguard Worker        os.path.join(args.logs_dir, 'system_logs')
770*6777b538SAndroid Build Coastguard Worker    ]
771*6777b538SAndroid Build Coastguard Worker
772*6777b538SAndroid Build Coastguard Worker  test_env = setup_env()
773*6777b538SAndroid Build Coastguard Worker  if args.deploy_chrome or args.deploy_lacros:
774*6777b538SAndroid Build Coastguard Worker    if args.deploy_lacros:
775*6777b538SAndroid Build Coastguard Worker      cros_run_test_cmd.extend([
776*6777b538SAndroid Build Coastguard Worker          '--deploy-lacros', '--lacros-launcher-script',
777*6777b538SAndroid Build Coastguard Worker          LACROS_LAUNCHER_SCRIPT_PATH
778*6777b538SAndroid Build Coastguard Worker      ])
779*6777b538SAndroid Build Coastguard Worker      if args.deploy_chrome:
780*6777b538SAndroid Build Coastguard Worker        # Mounting ash-chrome gives it enough disk space to not need stripping
781*6777b538SAndroid Build Coastguard Worker        # most of the time.
782*6777b538SAndroid Build Coastguard Worker        cros_run_test_cmd.extend(['--deploy', '--mount'])
783*6777b538SAndroid Build Coastguard Worker    else:
784*6777b538SAndroid Build Coastguard Worker      # Mounting ash-chrome gives it enough disk space to not need stripping
785*6777b538SAndroid Build Coastguard Worker      # most of the time.
786*6777b538SAndroid Build Coastguard Worker      cros_run_test_cmd.extend(['--deploy', '--mount'])
787*6777b538SAndroid Build Coastguard Worker
788*6777b538SAndroid Build Coastguard Worker    if not args.strip_chrome:
789*6777b538SAndroid Build Coastguard Worker      cros_run_test_cmd.append('--nostrip')
790*6777b538SAndroid Build Coastguard Worker
791*6777b538SAndroid Build Coastguard Worker    cros_run_test_cmd += [
792*6777b538SAndroid Build Coastguard Worker        '--build-dir',
793*6777b538SAndroid Build Coastguard Worker        os.path.join(CHROMIUM_SRC_PATH, args.path_to_outdir)
794*6777b538SAndroid Build Coastguard Worker    ]
795*6777b538SAndroid Build Coastguard Worker
796*6777b538SAndroid Build Coastguard Worker  cros_run_test_cmd += [
797*6777b538SAndroid Build Coastguard Worker      '--host-cmd',
798*6777b538SAndroid Build Coastguard Worker      '--',
799*6777b538SAndroid Build Coastguard Worker  ] + cmd_args
800*6777b538SAndroid Build Coastguard Worker
801*6777b538SAndroid Build Coastguard Worker  logging.info('Running the following command:')
802*6777b538SAndroid Build Coastguard Worker  logging.info(' '.join(cros_run_test_cmd))
803*6777b538SAndroid Build Coastguard Worker
804*6777b538SAndroid Build Coastguard Worker  return subprocess.call(
805*6777b538SAndroid Build Coastguard Worker      cros_run_test_cmd, stdout=sys.stdout, stderr=sys.stderr, env=test_env)
806*6777b538SAndroid Build Coastguard Worker
807*6777b538SAndroid Build Coastguard Worker
808*6777b538SAndroid Build Coastguard Workerdef get_cros_hostname_from_bot_id(bot_id):
809*6777b538SAndroid Build Coastguard Worker  """Parse hostname from a chromeos-swarming bot id."""
810*6777b538SAndroid Build Coastguard Worker  for prefix in ['cros-', 'crossk-']:
811*6777b538SAndroid Build Coastguard Worker    if bot_id.startswith(prefix):
812*6777b538SAndroid Build Coastguard Worker      return bot_id[len(prefix):]
813*6777b538SAndroid Build Coastguard Worker  return bot_id
814*6777b538SAndroid Build Coastguard Worker
815*6777b538SAndroid Build Coastguard Worker
816*6777b538SAndroid Build Coastguard Workerdef get_cros_hostname():
817*6777b538SAndroid Build Coastguard Worker  """Fetch bot_id from env var and parse hostname."""
818*6777b538SAndroid Build Coastguard Worker
819*6777b538SAndroid Build Coastguard Worker  # In chromeos-swarming, we can extract hostname from bot ID, since
820*6777b538SAndroid Build Coastguard Worker  # bot ID is formatted as "{prefix}{hostname}".
821*6777b538SAndroid Build Coastguard Worker  bot_id = os.environ.get('SWARMING_BOT_ID')
822*6777b538SAndroid Build Coastguard Worker  if bot_id:
823*6777b538SAndroid Build Coastguard Worker    return get_cros_hostname_from_bot_id(bot_id)
824*6777b538SAndroid Build Coastguard Worker
825*6777b538SAndroid Build Coastguard Worker  logging.warning(
826*6777b538SAndroid Build Coastguard Worker      'Attempted to read from SWARMING_BOT_ID env var and it was'
827*6777b538SAndroid Build Coastguard Worker      ' not defined. Will set %s as device instead.', LAB_DUT_HOSTNAME)
828*6777b538SAndroid Build Coastguard Worker  return LAB_DUT_HOSTNAME
829*6777b538SAndroid Build Coastguard Worker
830*6777b538SAndroid Build Coastguard Worker
831*6777b538SAndroid Build Coastguard Workerdef setup_env():
832*6777b538SAndroid Build Coastguard Worker  """Returns a copy of the current env with some needed vars added."""
833*6777b538SAndroid Build Coastguard Worker  env = os.environ.copy()
834*6777b538SAndroid Build Coastguard Worker  # Some chromite scripts expect chromite/bin to be on PATH.
835*6777b538SAndroid Build Coastguard Worker  env['PATH'] = env['PATH'] + ':' + os.path.join(CHROMITE_PATH, 'bin')
836*6777b538SAndroid Build Coastguard Worker  # deploy_chrome needs a set of GN args used to build chrome to determine if
837*6777b538SAndroid Build Coastguard Worker  # certain libraries need to be pushed to the device. It looks for the args via
838*6777b538SAndroid Build Coastguard Worker  # an env var. To trigger the default deploying behavior, give it a dummy set
839*6777b538SAndroid Build Coastguard Worker  # of args.
840*6777b538SAndroid Build Coastguard Worker  # TODO(crbug.com/823996): Make the GN-dependent deps controllable via cmd
841*6777b538SAndroid Build Coastguard Worker  # line args.
842*6777b538SAndroid Build Coastguard Worker  if not env.get('GN_ARGS'):
843*6777b538SAndroid Build Coastguard Worker    env['GN_ARGS'] = 'enable_nacl = true'
844*6777b538SAndroid Build Coastguard Worker  if not env.get('USE'):
845*6777b538SAndroid Build Coastguard Worker    env['USE'] = 'highdpi'
846*6777b538SAndroid Build Coastguard Worker  return env
847*6777b538SAndroid Build Coastguard Worker
848*6777b538SAndroid Build Coastguard Worker
849*6777b538SAndroid Build Coastguard Workerdef add_common_args(*parsers):
850*6777b538SAndroid Build Coastguard Worker  for parser in parsers:
851*6777b538SAndroid Build Coastguard Worker    parser.add_argument('--verbose', '-v', action='store_true')
852*6777b538SAndroid Build Coastguard Worker    parser.add_argument(
853*6777b538SAndroid Build Coastguard Worker        '--board', type=str, required=True, help='Type of CrOS device.')
854*6777b538SAndroid Build Coastguard Worker    parser.add_argument(
855*6777b538SAndroid Build Coastguard Worker        '--deploy-chrome',
856*6777b538SAndroid Build Coastguard Worker        action='store_true',
857*6777b538SAndroid Build Coastguard Worker        help='Will deploy a locally built ash-chrome binary to the device '
858*6777b538SAndroid Build Coastguard Worker        'before running the host-cmd.')
859*6777b538SAndroid Build Coastguard Worker    parser.add_argument(
860*6777b538SAndroid Build Coastguard Worker        '--deploy-lacros', action='store_true', help='Deploy a lacros-chrome.')
861*6777b538SAndroid Build Coastguard Worker    parser.add_argument(
862*6777b538SAndroid Build Coastguard Worker        '--cros-cache',
863*6777b538SAndroid Build Coastguard Worker        type=str,
864*6777b538SAndroid Build Coastguard Worker        default=DEFAULT_CROS_CACHE,
865*6777b538SAndroid Build Coastguard Worker        help='Path to cros cache.')
866*6777b538SAndroid Build Coastguard Worker    parser.add_argument(
867*6777b538SAndroid Build Coastguard Worker        '--path-to-outdir',
868*6777b538SAndroid Build Coastguard Worker        type=str,
869*6777b538SAndroid Build Coastguard Worker        required=True,
870*6777b538SAndroid Build Coastguard Worker        help='Path to output directory, all of whose contents will be '
871*6777b538SAndroid Build Coastguard Worker        'deployed to the device.')
872*6777b538SAndroid Build Coastguard Worker    parser.add_argument(
873*6777b538SAndroid Build Coastguard Worker        '--runtime-deps-path',
874*6777b538SAndroid Build Coastguard Worker        type=str,
875*6777b538SAndroid Build Coastguard Worker        help='Runtime data dependency file from GN.')
876*6777b538SAndroid Build Coastguard Worker    parser.add_argument(
877*6777b538SAndroid Build Coastguard Worker        '--vpython-dir',
878*6777b538SAndroid Build Coastguard Worker        type=str,
879*6777b538SAndroid Build Coastguard Worker        help='Location on host of a directory containing a vpython binary to '
880*6777b538SAndroid Build Coastguard Worker        'deploy to the device before the test starts. The location of '
881*6777b538SAndroid Build Coastguard Worker        'this dir will be added onto PATH in the device. WARNING: The '
882*6777b538SAndroid Build Coastguard Worker        'arch of the device might not match the arch of the host, so '
883*6777b538SAndroid Build Coastguard Worker        'avoid using "${platform}" when downloading vpython via CIPD.')
884*6777b538SAndroid Build Coastguard Worker    parser.add_argument(
885*6777b538SAndroid Build Coastguard Worker        '--logs-dir',
886*6777b538SAndroid Build Coastguard Worker        type=str,
887*6777b538SAndroid Build Coastguard Worker        dest='logs_dir',
888*6777b538SAndroid Build Coastguard Worker        help='Will copy everything under /var/log/ from the device after the '
889*6777b538SAndroid Build Coastguard Worker        'test into the specified dir.')
890*6777b538SAndroid Build Coastguard Worker    # Shard args are parsed here since we might also specify them via env vars.
891*6777b538SAndroid Build Coastguard Worker    parser.add_argument(
892*6777b538SAndroid Build Coastguard Worker        '--test-launcher-shard-index',
893*6777b538SAndroid Build Coastguard Worker        type=int,
894*6777b538SAndroid Build Coastguard Worker        default=os.environ.get('GTEST_SHARD_INDEX', 0),
895*6777b538SAndroid Build Coastguard Worker        help='Index of the external shard to run.')
896*6777b538SAndroid Build Coastguard Worker    parser.add_argument(
897*6777b538SAndroid Build Coastguard Worker        '--test-launcher-total-shards',
898*6777b538SAndroid Build Coastguard Worker        type=int,
899*6777b538SAndroid Build Coastguard Worker        default=os.environ.get('GTEST_TOTAL_SHARDS', 1),
900*6777b538SAndroid Build Coastguard Worker        help='Total number of external shards.')
901*6777b538SAndroid Build Coastguard Worker    parser.add_argument(
902*6777b538SAndroid Build Coastguard Worker        '--flash',
903*6777b538SAndroid Build Coastguard Worker        action='store_true',
904*6777b538SAndroid Build Coastguard Worker        help='Will flash the device to the current SDK version before running '
905*6777b538SAndroid Build Coastguard Worker        'the test.')
906*6777b538SAndroid Build Coastguard Worker    parser.add_argument(
907*6777b538SAndroid Build Coastguard Worker        '--no-flash',
908*6777b538SAndroid Build Coastguard Worker        action='store_false',
909*6777b538SAndroid Build Coastguard Worker        dest='flash',
910*6777b538SAndroid Build Coastguard Worker        help='Will not flash the device before running the test.')
911*6777b538SAndroid Build Coastguard Worker    parser.add_argument(
912*6777b538SAndroid Build Coastguard Worker        '--public-image',
913*6777b538SAndroid Build Coastguard Worker        action='store_true',
914*6777b538SAndroid Build Coastguard Worker        help='Will flash a public "full" image to the device.')
915*6777b538SAndroid Build Coastguard Worker    parser.add_argument(
916*6777b538SAndroid Build Coastguard Worker        '--magic-vm-cache',
917*6777b538SAndroid Build Coastguard Worker        help='Path to the magic CrOS VM cache dir. See the comment above '
918*6777b538SAndroid Build Coastguard Worker             '"magic_cros_vm_cache" in mixins.pyl for more info.')
919*6777b538SAndroid Build Coastguard Worker
920*6777b538SAndroid Build Coastguard Worker    vm_or_device_group = parser.add_mutually_exclusive_group()
921*6777b538SAndroid Build Coastguard Worker    vm_or_device_group.add_argument(
922*6777b538SAndroid Build Coastguard Worker        '--use-vm',
923*6777b538SAndroid Build Coastguard Worker        action='store_true',
924*6777b538SAndroid Build Coastguard Worker        help='Will run the test in the VM instead of a device.')
925*6777b538SAndroid Build Coastguard Worker    vm_or_device_group.add_argument(
926*6777b538SAndroid Build Coastguard Worker        '--device',
927*6777b538SAndroid Build Coastguard Worker        type=str,
928*6777b538SAndroid Build Coastguard Worker        help='Hostname (or IP) of device to run the test on. This arg is not '
929*6777b538SAndroid Build Coastguard Worker        'required if --use-vm is set.')
930*6777b538SAndroid Build Coastguard Worker    vm_or_device_group.add_argument(
931*6777b538SAndroid Build Coastguard Worker        '--fetch-cros-hostname',
932*6777b538SAndroid Build Coastguard Worker        action='store_true',
933*6777b538SAndroid Build Coastguard Worker        help='Will extract device hostname from the SWARMING_BOT_ID env var if '
934*6777b538SAndroid Build Coastguard Worker        'running on ChromeOS Swarming.')
935*6777b538SAndroid Build Coastguard Worker
936*6777b538SAndroid Build Coastguard Workerdef main():
937*6777b538SAndroid Build Coastguard Worker  parser = argparse.ArgumentParser()
938*6777b538SAndroid Build Coastguard Worker  subparsers = parser.add_subparsers(dest='test_type')
939*6777b538SAndroid Build Coastguard Worker  # Host-side test args.
940*6777b538SAndroid Build Coastguard Worker  host_cmd_parser = subparsers.add_parser(
941*6777b538SAndroid Build Coastguard Worker      'host-cmd',
942*6777b538SAndroid Build Coastguard Worker      help='Runs a host-side test. Pass the host-side command to run after '
943*6777b538SAndroid Build Coastguard Worker      '"--". If --use-vm is passed, hostname and port for the device '
944*6777b538SAndroid Build Coastguard Worker      'will be 127.0.0.1:9222.')
945*6777b538SAndroid Build Coastguard Worker  host_cmd_parser.set_defaults(func=host_cmd)
946*6777b538SAndroid Build Coastguard Worker  host_cmd_parser.add_argument(
947*6777b538SAndroid Build Coastguard Worker      '--strip-chrome',
948*6777b538SAndroid Build Coastguard Worker      action='store_true',
949*6777b538SAndroid Build Coastguard Worker      help='Strips symbols from ash-chrome or lacros-chrome before deploying '
950*6777b538SAndroid Build Coastguard Worker      ' to the device.')
951*6777b538SAndroid Build Coastguard Worker
952*6777b538SAndroid Build Coastguard Worker  gtest_parser = subparsers.add_parser(
953*6777b538SAndroid Build Coastguard Worker      'gtest', help='Runs a device-side gtest.')
954*6777b538SAndroid Build Coastguard Worker  gtest_parser.set_defaults(func=device_test)
955*6777b538SAndroid Build Coastguard Worker  gtest_parser.add_argument(
956*6777b538SAndroid Build Coastguard Worker      '--test-exe',
957*6777b538SAndroid Build Coastguard Worker      type=str,
958*6777b538SAndroid Build Coastguard Worker      required=True,
959*6777b538SAndroid Build Coastguard Worker      help='Path to test executable to run inside the device.')
960*6777b538SAndroid Build Coastguard Worker
961*6777b538SAndroid Build Coastguard Worker  # GTest args. Some are passed down to the test binary in the device. Others
962*6777b538SAndroid Build Coastguard Worker  # are parsed here since they might need tweaking or special handling.
963*6777b538SAndroid Build Coastguard Worker  gtest_parser.add_argument(
964*6777b538SAndroid Build Coastguard Worker      '--test-launcher-summary-output',
965*6777b538SAndroid Build Coastguard Worker      type=str,
966*6777b538SAndroid Build Coastguard Worker      help='When set, will pass the same option down to the test and retrieve '
967*6777b538SAndroid Build Coastguard Worker      'its result file at the specified location.')
968*6777b538SAndroid Build Coastguard Worker  gtest_parser.add_argument(
969*6777b538SAndroid Build Coastguard Worker      '--stop-ui',
970*6777b538SAndroid Build Coastguard Worker      action='store_true',
971*6777b538SAndroid Build Coastguard Worker      help='Will stop the UI service in the device before running the test. '
972*6777b538SAndroid Build Coastguard Worker      'Also start the UI service after all tests are done.')
973*6777b538SAndroid Build Coastguard Worker  gtest_parser.add_argument(
974*6777b538SAndroid Build Coastguard Worker      '--trace-dir',
975*6777b538SAndroid Build Coastguard Worker      type=str,
976*6777b538SAndroid Build Coastguard Worker      help='When set, will pass down to the test to generate the trace and '
977*6777b538SAndroid Build Coastguard Worker      'retrieve the trace files to the specified location.')
978*6777b538SAndroid Build Coastguard Worker  gtest_parser.add_argument(
979*6777b538SAndroid Build Coastguard Worker      '--env-var',
980*6777b538SAndroid Build Coastguard Worker      nargs=2,
981*6777b538SAndroid Build Coastguard Worker      action='append',
982*6777b538SAndroid Build Coastguard Worker      default=[],
983*6777b538SAndroid Build Coastguard Worker      help='Env var to set on the device for the duration of the test. '
984*6777b538SAndroid Build Coastguard Worker      'Expected format is "--env-var SOME_VAR_NAME some_var_value". Specify '
985*6777b538SAndroid Build Coastguard Worker      'multiple times for more than one var.')
986*6777b538SAndroid Build Coastguard Worker  gtest_parser.add_argument(
987*6777b538SAndroid Build Coastguard Worker      '--run-test-sudo-helper',
988*6777b538SAndroid Build Coastguard Worker      action='store_true',
989*6777b538SAndroid Build Coastguard Worker      help='When set, will run test_sudo_helper before the test and stop it '
990*6777b538SAndroid Build Coastguard Worker      'after test finishes.')
991*6777b538SAndroid Build Coastguard Worker  gtest_parser.add_argument(
992*6777b538SAndroid Build Coastguard Worker      "--no-clean",
993*6777b538SAndroid Build Coastguard Worker      action="store_false",
994*6777b538SAndroid Build Coastguard Worker      dest="clean",
995*6777b538SAndroid Build Coastguard Worker      default=True,
996*6777b538SAndroid Build Coastguard Worker      help="Do not clean up the deployed files after running the test. "
997*6777b538SAndroid Build Coastguard Worker      "Only supported for --remote-cmd tests")
998*6777b538SAndroid Build Coastguard Worker  gtest_parser.add_argument(
999*6777b538SAndroid Build Coastguard Worker      '--set-selinux-label',
1000*6777b538SAndroid Build Coastguard Worker      action='append',
1001*6777b538SAndroid Build Coastguard Worker      default=[],
1002*6777b538SAndroid Build Coastguard Worker      help='Set the selinux label for a file before running. The format is:\n'
1003*6777b538SAndroid Build Coastguard Worker      '  --set-selinux-label=<filename>=<label>\n'
1004*6777b538SAndroid Build Coastguard Worker      'So:\n'
1005*6777b538SAndroid Build Coastguard Worker      '  --set-selinux-label=my_test=u:r:cros_foo_label:s0\n'
1006*6777b538SAndroid Build Coastguard Worker      'You can specify it more than one time to set multiple files tags.')
1007*6777b538SAndroid Build Coastguard Worker
1008*6777b538SAndroid Build Coastguard Worker  # Tast test args.
1009*6777b538SAndroid Build Coastguard Worker  # pylint: disable=line-too-long
1010*6777b538SAndroid Build Coastguard Worker  tast_test_parser = subparsers.add_parser(
1011*6777b538SAndroid Build Coastguard Worker      'tast',
1012*6777b538SAndroid Build Coastguard Worker      help='Runs a device-side set of Tast tests. For more details, see: '
1013*6777b538SAndroid Build Coastguard Worker      'https://chromium.googlesource.com/chromiumos/platform/tast/+/main/docs/running_tests.md'
1014*6777b538SAndroid Build Coastguard Worker  )
1015*6777b538SAndroid Build Coastguard Worker  tast_test_parser.set_defaults(func=device_test)
1016*6777b538SAndroid Build Coastguard Worker  tast_test_parser.add_argument(
1017*6777b538SAndroid Build Coastguard Worker      '--suite-name',
1018*6777b538SAndroid Build Coastguard Worker      type=str,
1019*6777b538SAndroid Build Coastguard Worker      required=True,
1020*6777b538SAndroid Build Coastguard Worker      help='Name to apply to the set of Tast tests to run. This has no effect '
1021*6777b538SAndroid Build Coastguard Worker      'on what is executed, but is used mainly for test results reporting '
1022*6777b538SAndroid Build Coastguard Worker      'and tracking (eg: flakiness dashboard).')
1023*6777b538SAndroid Build Coastguard Worker  tast_test_parser.add_argument(
1024*6777b538SAndroid Build Coastguard Worker      '--test-launcher-summary-output',
1025*6777b538SAndroid Build Coastguard Worker      type=str,
1026*6777b538SAndroid Build Coastguard Worker      help='Generates a simple GTest-style JSON result file for the test run.')
1027*6777b538SAndroid Build Coastguard Worker  tast_test_parser.add_argument(
1028*6777b538SAndroid Build Coastguard Worker      '--attr-expr',
1029*6777b538SAndroid Build Coastguard Worker      type=str,
1030*6777b538SAndroid Build Coastguard Worker      help='A boolean expression whose matching tests will run '
1031*6777b538SAndroid Build Coastguard Worker      '(eg: ("dep:chrome")).')
1032*6777b538SAndroid Build Coastguard Worker  tast_test_parser.add_argument(
1033*6777b538SAndroid Build Coastguard Worker      '--strip-chrome',
1034*6777b538SAndroid Build Coastguard Worker      action='store_true',
1035*6777b538SAndroid Build Coastguard Worker      help='Strips symbols from ash-chrome before deploying to the device.')
1036*6777b538SAndroid Build Coastguard Worker  tast_test_parser.add_argument(
1037*6777b538SAndroid Build Coastguard Worker      '--tast-var',
1038*6777b538SAndroid Build Coastguard Worker      action='append',
1039*6777b538SAndroid Build Coastguard Worker      dest='tast_vars',
1040*6777b538SAndroid Build Coastguard Worker      help='Runtime variables for Tast tests, and the format are expected to '
1041*6777b538SAndroid Build Coastguard Worker      'be "key=value" pairs.')
1042*6777b538SAndroid Build Coastguard Worker  tast_test_parser.add_argument(
1043*6777b538SAndroid Build Coastguard Worker      '--tast-retries',
1044*6777b538SAndroid Build Coastguard Worker      type=int,
1045*6777b538SAndroid Build Coastguard Worker      dest='tast_retries',
1046*6777b538SAndroid Build Coastguard Worker      help='Number of retries for failed Tast tests on the same DUT.')
1047*6777b538SAndroid Build Coastguard Worker  tast_test_parser.add_argument(
1048*6777b538SAndroid Build Coastguard Worker      '--test',
1049*6777b538SAndroid Build Coastguard Worker      '-t',
1050*6777b538SAndroid Build Coastguard Worker      action='append',
1051*6777b538SAndroid Build Coastguard Worker      dest='tests',
1052*6777b538SAndroid Build Coastguard Worker      help='A Tast test to run in the device (eg: "login.Chrome").')
1053*6777b538SAndroid Build Coastguard Worker  tast_test_parser.add_argument(
1054*6777b538SAndroid Build Coastguard Worker      '--gtest_filter',
1055*6777b538SAndroid Build Coastguard Worker      type=str,
1056*6777b538SAndroid Build Coastguard Worker      help="Similar to GTest's arg of the same name, this will filter out the "
1057*6777b538SAndroid Build Coastguard Worker      "specified tests from the Tast run. However, due to the nature of Tast's "
1058*6777b538SAndroid Build Coastguard Worker      'cmd-line API, this will overwrite the value(s) of "--test" above.')
1059*6777b538SAndroid Build Coastguard Worker
1060*6777b538SAndroid Build Coastguard Worker  add_common_args(gtest_parser, tast_test_parser, host_cmd_parser)
1061*6777b538SAndroid Build Coastguard Worker  args, unknown_args = parser.parse_known_args()
1062*6777b538SAndroid Build Coastguard Worker  # Re-add N-1 -v/--verbose flags to the args we'll pass to whatever we are
1063*6777b538SAndroid Build Coastguard Worker  # running. The assumption is that only one verbosity incrase would be meant
1064*6777b538SAndroid Build Coastguard Worker  # for this script since it's a boolean value instead of increasing verbosity
1065*6777b538SAndroid Build Coastguard Worker  # with more instances.
1066*6777b538SAndroid Build Coastguard Worker  verbose_flags = [a for a in sys.argv if a in ('-v', '--verbose')]
1067*6777b538SAndroid Build Coastguard Worker  if verbose_flags:
1068*6777b538SAndroid Build Coastguard Worker    unknown_args += verbose_flags[1:]
1069*6777b538SAndroid Build Coastguard Worker
1070*6777b538SAndroid Build Coastguard Worker  logging.basicConfig(level=logging.DEBUG if args.verbose else logging.WARN)
1071*6777b538SAndroid Build Coastguard Worker
1072*6777b538SAndroid Build Coastguard Worker  if not args.use_vm and not args.device and not args.fetch_cros_hostname:
1073*6777b538SAndroid Build Coastguard Worker    logging.warning(
1074*6777b538SAndroid Build Coastguard Worker        'The test runner is now assuming running in the lab environment, if '
1075*6777b538SAndroid Build Coastguard Worker        'this is unintentional, please re-invoke the test runner with the '
1076*6777b538SAndroid Build Coastguard Worker        '"--use-vm" arg if using a VM, otherwise use the "--device=<DUT>" arg '
1077*6777b538SAndroid Build Coastguard Worker        'to specify a DUT.')
1078*6777b538SAndroid Build Coastguard Worker
1079*6777b538SAndroid Build Coastguard Worker    # If we're not running on a VM, but haven't specified a hostname, assume
1080*6777b538SAndroid Build Coastguard Worker    # we're on a lab bot and are trying to run a test on a lab DUT. See if the
1081*6777b538SAndroid Build Coastguard Worker    # magic lab DUT hostname resolves to anything. (It will in the lab and will
1082*6777b538SAndroid Build Coastguard Worker    # not on dev machines.)
1083*6777b538SAndroid Build Coastguard Worker    try:
1084*6777b538SAndroid Build Coastguard Worker      socket.getaddrinfo(LAB_DUT_HOSTNAME, None)
1085*6777b538SAndroid Build Coastguard Worker    except socket.gaierror:
1086*6777b538SAndroid Build Coastguard Worker      logging.error('The default lab DUT hostname of %s is unreachable.',
1087*6777b538SAndroid Build Coastguard Worker                    LAB_DUT_HOSTNAME)
1088*6777b538SAndroid Build Coastguard Worker      return 1
1089*6777b538SAndroid Build Coastguard Worker
1090*6777b538SAndroid Build Coastguard Worker  if args.flash and args.public_image:
1091*6777b538SAndroid Build Coastguard Worker    # The flashing tools depend on being unauthenticated with GS when flashing
1092*6777b538SAndroid Build Coastguard Worker    # public images, so make sure the env var GS uses to locate its creds is
1093*6777b538SAndroid Build Coastguard Worker    # unset in that case.
1094*6777b538SAndroid Build Coastguard Worker    os.environ.pop('BOTO_CONFIG', None)
1095*6777b538SAndroid Build Coastguard Worker
1096*6777b538SAndroid Build Coastguard Worker  if args.magic_vm_cache:
1097*6777b538SAndroid Build Coastguard Worker    full_vm_cache_path = os.path.join(CHROMIUM_SRC_PATH, args.magic_vm_cache)
1098*6777b538SAndroid Build Coastguard Worker    if os.path.exists(full_vm_cache_path):
1099*6777b538SAndroid Build Coastguard Worker      with open(os.path.join(full_vm_cache_path, 'swarming.txt'), 'w') as f:
1100*6777b538SAndroid Build Coastguard Worker        f.write('non-empty file to make swarming persist this cache')
1101*6777b538SAndroid Build Coastguard Worker
1102*6777b538SAndroid Build Coastguard Worker  return args.func(args, unknown_args)
1103*6777b538SAndroid Build Coastguard Worker
1104*6777b538SAndroid Build Coastguard Worker
1105*6777b538SAndroid Build Coastguard Workerif __name__ == '__main__':
1106*6777b538SAndroid Build Coastguard Worker  sys.exit(main())
1107