blob: aa820ff1db3cdeef72d2f3a0ccbf38767a93038e [file] [log] [blame]
Kuang-che Wu875c89a2020-01-08 14:30:55 +08001#!/usr/bin/env python3
Kuang-che Wub9705bd2018-06-28 17:59:18 +08002# -*- coding: utf-8 -*-
3# Copyright 2018 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6"""Evaluate ChromeOS autotest.
7
8Note that by default 'test_that' will install dependency packages of autotest
Kuang-che Wufd2ea5e2019-06-10 20:40:17 +08009if the package checksum mismatch. If you want to override content of autotest
10package, e.g. chrome's test binary, please make sure the autotest version
11matches. Otherwise your test binary will be overwritten.
Kuang-che Wub9705bd2018-06-28 17:59:18 +080012"""
13from __future__ import print_function
14import argparse
Kuang-che Wub9705bd2018-06-28 17:59:18 +080015import logging
16import os
17import re
18import subprocess
19import sys
20
Kuang-che Wu4f0dc232019-05-10 18:28:06 +080021from bisect_kit import catapult_util
Kuang-che Wub9705bd2018-06-28 17:59:18 +080022from bisect_kit import cli
23from bisect_kit import common
24from bisect_kit import configure
Kuang-che Wu2ac9a922020-09-03 16:50:12 +080025from bisect_kit import cros_lab_util
Kuang-che Wub9705bd2018-06-28 17:59:18 +080026from bisect_kit import cros_util
27from bisect_kit import util
28
29logger = logging.getLogger(__name__)
30
31OLD = 'old'
32NEW = 'new'
33SKIP = 'skip'
34FATAL = 'fatal'
35
36EXIT_CODE_MAP = {
Kuang-che Wu0476d1f2019-03-04 19:27:01 +080037 OLD: cli.EXIT_CODE_OLD,
38 NEW: cli.EXIT_CODE_NEW,
39 SKIP: cli.EXIT_CODE_SKIP,
40 FATAL: cli.EXIT_CODE_FATAL,
Kuang-che Wub9705bd2018-06-28 17:59:18 +080041}
42
43
44def create_argument_parser():
Kuang-che Wud2d6e412021-01-28 16:26:41 +080045 parents = [common.common_argument_parser, common.session_optional_parser]
46 parser = argparse.ArgumentParser(description=__doc__, parents=parents)
Kuang-che Wufe1e88a2019-09-10 21:52:25 +080047 cli.patching_argparser_exit(parser)
Kuang-che Wub9705bd2018-06-28 17:59:18 +080048 parser.add_argument(
49 'dut',
50 nargs='?',
51 type=cli.argtype_notempty,
52 metavar='DUT',
53 default=configure.get('DUT', ''))
54 parser.add_argument(
55 '--chromeos_root',
56 type=cli.argtype_dir_path,
57 metavar='CHROMEOS_ROOT',
58 default=configure.get('CHROMEOS_ROOT', ''),
59 help='ChromeOS tree root')
60 parser.add_argument(
Kuang-che Wud4603d72018-11-29 17:51:21 +080061 '--chrome_root',
62 metavar='CHROME_ROOT',
63 type=cli.argtype_dir_path,
64 default=configure.get('CHROME_ROOT'),
65 help='Chrome tree root; necessary for telemetry tests')
66 parser.add_argument(
Kuang-che Wub9705bd2018-06-28 17:59:18 +080067 '--prebuilt',
68 action='store_true',
69 help='Run autotest using existing prebuilt package if specified; '
70 'otherwise use the default one')
71 parser.add_argument(
72 '--reinstall',
73 action='store_true',
74 help='Remove existing autotest folder on the DUT first')
Kuang-che Wuda3abfe2019-03-21 14:48:12 +080075 parser.add_argument(
76 '--reboot_before_test',
77 action='store_true',
78 help='Reboot before test run')
Kuang-che Wu85c613c2019-01-09 15:46:11 +080079
80 group = parser.add_argument_group(title='Options for normal autotest tests')
81 group.add_argument(
82 '--test_name', help='Test name, like "video_VideoDecodeAccelerator.h264"')
83 group.add_argument(
Kuang-che Wu0a4304a2019-01-19 01:32:11 +080084 '--fail_to_pass',
85 action='store_true',
86 help='For functional tests: old behavior is FAIL and new behavior is '
87 'PASS; If not specified, default = old behavior is PASS and new '
88 'behavior is FAIL')
89 group.add_argument(
Kuang-che Wu85c613c2019-01-09 15:46:11 +080090 '--metric',
Kuang-che Wud1b74152020-05-20 08:46:46 +080091 help='Metric name of performance test; example: '
92 '"cheets_SystemRawImageSize"')
Kuang-che Wu85c613c2019-01-09 15:46:11 +080093 group.add_argument(
Kuang-che Wub9705bd2018-06-28 17:59:18 +080094 '--args',
95 help='Extra args passed to "test_that --args"; Overrides the default')
96
Kuang-che Wu85c613c2019-01-09 15:46:11 +080097 group = parser.add_argument_group(title='Options for CTS/GTS tests')
98 group.add_argument('--cts_revision', help='CTS revision, like "9.0_r3"')
Kuang-che Wu63f836a2019-02-21 16:33:32 +000099 group.add_argument('--cts_abi', choices=['arm', 'x86'])
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800100 group.add_argument(
101 '--cts_prefix',
102 help='Prefix of autotest test name, '
103 'like cheets_CTS_N, cheets_CTS_P, cheets_GTS')
104 group.add_argument(
105 '--cts_module', help='CTS/GTS module name, like "CtsCameraTestCases"')
106 group.add_argument(
107 '--cts_test',
108 help='CTS/GTS test name, like '
109 '"android.hardware.cts.CameraTest#testDisplayOrientation"')
110 group.add_argument('--cts_timeout', type=float, help='timeout, in seconds')
111
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800112 return parser
113
114
115def parse_test_report_log(result_log, metric):
116 """Parses autotest result log.
117
118 Args:
119 result_log: content of test_report.log
120 metric: what metric to capture if not None
121
122 Returns:
123 passed, values:
124 passed: True if test run successfully
125 values: captured metric values; None if test failed or metric is None
126 """
127 m = re.search(r'Total PASS: (\d+)/(\d+)', result_log)
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800128 passed = (m and m.group(1) == m.group(2))
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800129
130 if not metric:
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800131 return passed, None
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800132
133 values = []
134 for line in result_log.splitlines():
135 m = re.match(r'^(\S+)\s+(\w+)(?:\{\d+\})?\s+(\d+\.\d+)$', line)
136 if not m:
137 continue
138 if m.group(2) == metric:
139 values.append(float(m.group(3)))
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800140 return passed, values
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800141
142
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800143def get_additional_test_args(test_name):
144 """Gets extra arguments to specific test.
145
146 Some tests may require special arguments to run.
147
148 Args:
149 test_name: test name
150
151 Returns:
152 arguments (str)
153 """
154 if test_name.startswith('telemetry_'):
155 return 'local=True'
156 return ''
157
158
Kuang-che Wu6c5a5b22019-01-17 18:09:50 +0800159def prepare_to_run_test(opts):
160 # Some versions of ChromeOS SDK is broken and ship bad 'ssh' executable. This
Kuang-che Wu9fcf1082019-03-04 11:24:04 +0800161 # works around the issue. See crbug/906289 for detail.
162 # TODO(kcwu): remove this workaround once we no longer support bisecting
163 # versions earlier than R73-11445.0.0.
164 ssh_path = os.path.join(opts.chromeos_root, 'chroot/usr/bin/ssh')
Kuang-che Wu4fbd2d32019-03-07 01:07:57 +0800165 if os.path.exists(ssh_path):
Kuang-che Wud3a4e842019-12-11 12:15:23 +0800166 with open(ssh_path, 'rb') as f:
Kuang-che Wua5723492019-11-25 20:59:34 +0800167 if b'file descriptor passing not supported' in f.read():
168 cros_util.cros_sdk(opts.chromeos_root, 'sudo', 'emerge',
169 'net-misc/openssh')
Kuang-che Wu6c5a5b22019-01-17 18:09:50 +0800170
Kuang-che Wue9fc9e92020-09-11 20:28:13 +0800171 # Special handling for audio tests (b/136136270).
172 if opts.prebuilt:
173 autotest_dir = os.path.join(opts.chromeos_root,
174 cros_util.prebuilt_autotest_dir)
175 else:
176 autotest_dir = os.path.join(opts.chromeos_root,
Kuang-che Wu721e8902021-03-19 12:18:53 +0800177 cros_util.in_tree_autotest_dir)
178 cros_util.override_autotest_config(autotest_dir)
Kuang-che Wue9fc9e92020-09-11 20:28:13 +0800179 sox_path = os.path.join(opts.chromeos_root, 'chroot/usr/bin/sox')
180 if not os.path.exists(sox_path):
Kuang-che Wucc65e4e2020-09-22 19:55:36 +0800181 try:
182 cros_util.cros_sdk(opts.chromeos_root, 'sudo', 'emerge', 'sox')
183 except subprocess.CalledProcessError:
184 # It's known that installing sox would fail for earlier version of
185 # chromeos (b/136136270), so ignore the failure.
186 logger.debug('Sox is only required by some audio tests. '
187 'Assume the failure of installing sox is harmless')
Kuang-che Wue9fc9e92020-09-11 20:28:13 +0800188
Kuang-che Wuf0ad9ac2019-09-06 13:05:04 +0800189 # test_that may use this ssh key and ssh complains its permission is too open.
Kuang-che Wu4a81ea72019-10-05 15:35:17 +0800190 # chmod every time just before run test_that because the permission may change
Kuang-che Wuf0ad9ac2019-09-06 13:05:04 +0800191 # after some git operations.
192 util.check_call(
193 'chmod',
194 'o-r,g-r',
195 'src/scripts/mod_for_test_scripts/ssh_keys/testing_rsa',
196 cwd=opts.chromeos_root)
197
Kuang-che Wu6c5a5b22019-01-17 18:09:50 +0800198 if opts.reinstall:
Kuang-che Wu44278142019-03-04 11:33:57 +0800199 util.ssh_cmd(opts.dut, 'rm', '-rf', '/usr/local/autotest')
Kuang-che Wu6c5a5b22019-01-17 18:09:50 +0800200
Kuang-che Wuda3abfe2019-03-21 14:48:12 +0800201 if opts.reboot_before_test:
Kuang-che Wu2ac9a922020-09-03 16:50:12 +0800202 cros_util.reboot(
203 opts.dut, force_reboot_callback=cros_lab_util.reboot_via_servo)
Kuang-che Wuda3abfe2019-03-21 14:48:12 +0800204
Kuang-che Wu6c5a5b22019-01-17 18:09:50 +0800205
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800206def run_test(opts):
207 """Runs an autotest test.
208
209 Args:
210 opts: An argparse.Namespace to hold command line arguments.
211
212 Returns:
213 path of test result (outside chroot)
214 """
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800215 prebuilt_autotest_dir = os.path.join(cros_util.chromeos_root_inside_chroot,
216 cros_util.prebuilt_autotest_dir)
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800217 # Set results dir inside source tree, so it's easier to access them outside
218 # chroot.
219 results_dir = os.path.join(cros_util.chromeos_root_inside_chroot,
220 'tmp/autotest_results_tmp')
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800221 if opts.prebuilt:
222 test_that_bin = os.path.join(prebuilt_autotest_dir,
223 'site_utils/test_that.py')
224 else:
225 test_that_bin = '/usr/bin/test_that'
Kuang-che Wuf3d03ca2019-03-11 17:31:40 +0800226 cmd = [
227 test_that_bin, opts.dut, opts.test_name, '--debug', '--results_dir',
Kuang-che Wu0c9b7942019-10-30 16:55:39 +0800228 results_dir
Kuang-che Wuf3d03ca2019-03-11 17:31:40 +0800229 ]
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800230 if opts.prebuilt:
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800231 cmd += ['--autotest_dir', prebuilt_autotest_dir]
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800232
233 args = get_additional_test_args(opts.test_name)
234 if opts.args:
235 if args:
Kuang-che Wu74768d32018-09-07 12:03:24 +0800236 logger.info(
237 'default test_that args `%s` is overridden by '
238 'command line option `%s`', args, opts.args)
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800239 cmd += ['--args', opts.args]
240 elif args:
241 cmd += ['--args', args]
242
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800243 try:
Kuang-che Wud4603d72018-11-29 17:51:21 +0800244 output = cros_util.cros_sdk(
245 opts.chromeos_root, *cmd, chrome_root=opts.chrome_root)
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800246 except subprocess.CalledProcessError as e:
Kuang-che Wu62677012020-07-13 14:25:18 +0800247 if e.output is None:
248 logger.error('cros_sdk failed before test started')
249 return None
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800250 output = e.output
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800251
252 m = re.search(r'Finished running tests. Results can be found in (\S+)',
253 output)
254 if not m:
255 logger.error('result dir is unknown')
256 return None
257 assert m.group(1) == results_dir
258 return results_dir.replace(cros_util.chromeos_root_inside_chroot,
259 opts.chromeos_root)
260
261
262def gather_test_result(opts, result_dir):
263 result_log_path = os.path.join(result_dir, 'test_report.log')
Kuang-che Wua5723492019-11-25 20:59:34 +0800264 with open(result_log_path) as f:
265 result_log = f.read()
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800266
267 passed, values = parse_test_report_log(result_log, opts.metric)
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800268 if opts.metric and not values:
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800269 values = []
270 for root, _, files in os.walk(result_dir):
271 for filename in files:
272 if filename != 'results-chart.json':
273 continue
274 full_path = os.path.join(root, filename)
Kuang-che Wu4f0dc232019-05-10 18:28:06 +0800275 values = catapult_util.get_benchmark_values(full_path, opts.metric)
276 return passed, values
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800277
278 return passed, values
279
280
Kuang-che Wufe1e88a2019-09-10 21:52:25 +0800281@cli.fatal_error_handler
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800282def main(args=None):
283 common.init()
284 parser = create_argument_parser()
285 opts = parser.parse_args(args)
286 common.config_logging(opts)
287
288 if not cros_util.is_dut(opts.dut):
Kuang-che Wu4a75f952019-03-26 17:22:42 +0800289 logger.error('%r is not a valid DUT address', opts.dut)
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800290 return FATAL
Kuang-che Wuc8c495d2019-08-19 17:48:58 +0800291 dut_os_version = cros_util.query_dut_short_version(opts.dut)
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800292
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800293 is_cts = (
294 opts.cts_revision or opts.cts_abi or opts.cts_prefix or opts.cts_module or
Kuang-che Wue07bff52019-10-29 20:50:45 +0800295 opts.cts_test or opts.cts_timeout)
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800296 if is_cts:
297 if opts.test_name or opts.metric or opts.args:
298 parser.error(
299 'do not specify --test_name, --metric, --args for CTS/GTS tests')
Kuang-che Wub91b1512019-05-29 15:16:29 +0800300 if not opts.cts_prefix:
301 parser.error('--cts_prefix should be specified for CTS/GTS tests')
302 if not opts.cts_module:
303 parser.error('--cts_module should be specified for CTS/GTS tests')
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800304 opts.test_name = '%s.tradefed-run-test' % opts.cts_prefix
Kuang-che Wue07bff52019-10-29 20:50:45 +0800305 opts.args = 'module=%s test=%s max_retry=0' % (opts.cts_module,
306 opts.cts_test)
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800307 if opts.cts_revision:
308 opts.args += ' revision=%s' % opts.cts_revision
309 if opts.cts_abi:
310 opts.args += ' abi=%s' % opts.cts_abi
311 if opts.cts_timeout:
312 opts.args += ' timeout=%s' % opts.cts_timeout
313 else:
314 if not opts.test_name:
315 parser.error('argument --test_name is required')
316
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800317 # Verify command line options.
318 if opts.metric:
Kuang-che Wu0a4304a2019-01-19 01:32:11 +0800319 if opts.fail_to_pass:
320 logger.error('--fail_to_pass is not for benchmark test (--metric)')
321 return FATAL
Kuang-che Wud4603d72018-11-29 17:51:21 +0800322 if opts.test_name.startswith('telemetry_'):
323 if not opts.chrome_root:
324 logger.error('--chrome_root is mandatory for telemetry tests')
325 return FATAL
Kuang-che Wua641cb12021-01-27 19:27:25 +0800326 if opts.prebuilt:
327 autotest_dir = os.path.join(opts.chromeos_root,
328 cros_util.prebuilt_autotest_dir)
329 if not os.path.exists(autotest_dir):
330 parser.error('--prebuilt: no autotest prebuilt installed (%s); '
331 'please run switch_autotest_prebuilt.py first' %
332 autotest_dir)
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800333
Kuang-che Wu6c5a5b22019-01-17 18:09:50 +0800334 try:
335 prepare_to_run_test(opts)
336 except Exception:
337 logger.exception('failed when prepare, assume it is temporary; SKIP')
338 return SKIP
Kuang-che Wue47162d2018-10-29 17:24:04 +0800339
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800340 result_dir = run_test(opts)
341 if not result_dir:
Kuang-che Wudd802672018-08-10 19:40:14 +0800342 return FATAL
343
Kuang-che Wuc986b1d2019-04-15 16:45:20 +0800344 try:
345 passed, values = gather_test_result(opts, result_dir)
346 except Exception:
347 logger.exception('failed to parse test result')
348 return FATAL
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800349
Kuang-che Wuc8c495d2019-08-19 17:48:58 +0800350 # Sanity check. The OS version should not change.
Kuang-che Wu523bdf22019-08-20 12:11:09 +0800351 assert dut_os_version == cros_util.query_dut_short_version(opts.dut), \
Kuang-che Wu59400312020-11-10 14:53:54 +0800352 'Someone else reflashed the DUT. DUT locking is not respected?'
Kuang-che Wuc8c495d2019-08-19 17:48:58 +0800353
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800354 if opts.metric:
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800355 if not values:
356 logger.warning('no values found; SKIP')
357 return SKIP
358
Kuang-che Wuc89f2a22019-11-26 15:30:50 +0800359 print('BISECT_RESULT_VALUES=', ' '.join(str(v) for v in values))
Kuang-che Wu81cde452019-04-08 16:56:51 +0800360 logger.info('values=%s', values)
361 # The exit code doesn't matter.
362 return OLD
Kuang-che Wu084eef22020-03-11 18:29:48 +0800363
364 if opts.fail_to_pass:
365 if passed:
366 logger.info('passed')
Kuang-che Wu0a4304a2019-01-19 01:32:11 +0800367 return NEW
Kuang-che Wu084eef22020-03-11 18:29:48 +0800368 logger.info('failed')
369 return OLD
370 if passed:
371 logger.info('passed')
372 return OLD
373 logger.info('failed')
374 return NEW
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800375
376
377if __name__ == '__main__':
378 sys.exit(EXIT_CODE_MAP[main()])