blob: e4acafb1b05e445bc6647d647d816b33ed87a745 [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
Kuang-che Wue9fc9e92020-09-11 20:28:13 +080020import textwrap
Kuang-che Wub9705bd2018-06-28 17:59:18 +080021
Kuang-che Wu4f0dc232019-05-10 18:28:06 +080022from bisect_kit import catapult_util
Kuang-che Wub9705bd2018-06-28 17:59:18 +080023from bisect_kit import cli
24from bisect_kit import common
25from bisect_kit import configure
Kuang-che Wu2ac9a922020-09-03 16:50:12 +080026from bisect_kit import cros_lab_util
Kuang-che Wub9705bd2018-06-28 17:59:18 +080027from bisect_kit import cros_util
28from bisect_kit import util
29
30logger = logging.getLogger(__name__)
31
32OLD = 'old'
33NEW = 'new'
34SKIP = 'skip'
35FATAL = 'fatal'
36
37EXIT_CODE_MAP = {
Kuang-che Wu0476d1f2019-03-04 19:27:01 +080038 OLD: cli.EXIT_CODE_OLD,
39 NEW: cli.EXIT_CODE_NEW,
40 SKIP: cli.EXIT_CODE_SKIP,
41 FATAL: cli.EXIT_CODE_FATAL,
Kuang-che Wub9705bd2018-06-28 17:59:18 +080042}
43
44
45def create_argument_parser():
46 parser = argparse.ArgumentParser(description=__doc__)
Kuang-che Wufe1e88a2019-09-10 21:52:25 +080047 cli.patching_argparser_exit(parser)
Kuang-che Wub9705bd2018-06-28 17:59:18 +080048 common.add_common_arguments(parser)
49 parser.add_argument(
50 'dut',
51 nargs='?',
52 type=cli.argtype_notempty,
53 metavar='DUT',
54 default=configure.get('DUT', ''))
55 parser.add_argument(
56 '--chromeos_root',
57 type=cli.argtype_dir_path,
58 metavar='CHROMEOS_ROOT',
59 default=configure.get('CHROMEOS_ROOT', ''),
60 help='ChromeOS tree root')
61 parser.add_argument(
Kuang-che Wud4603d72018-11-29 17:51:21 +080062 '--chrome_root',
63 metavar='CHROME_ROOT',
64 type=cli.argtype_dir_path,
65 default=configure.get('CHROME_ROOT'),
66 help='Chrome tree root; necessary for telemetry tests')
67 parser.add_argument(
Kuang-che Wub9705bd2018-06-28 17:59:18 +080068 '--prebuilt',
69 action='store_true',
70 help='Run autotest using existing prebuilt package if specified; '
71 'otherwise use the default one')
72 parser.add_argument(
73 '--reinstall',
74 action='store_true',
75 help='Remove existing autotest folder on the DUT first')
Kuang-che Wuda3abfe2019-03-21 14:48:12 +080076 parser.add_argument(
77 '--reboot_before_test',
78 action='store_true',
79 help='Reboot before test run')
Kuang-che Wu85c613c2019-01-09 15:46:11 +080080
81 group = parser.add_argument_group(title='Options for normal autotest tests')
82 group.add_argument(
83 '--test_name', help='Test name, like "video_VideoDecodeAccelerator.h264"')
84 group.add_argument(
Kuang-che Wu0a4304a2019-01-19 01:32:11 +080085 '--fail_to_pass',
86 action='store_true',
87 help='For functional tests: old behavior is FAIL and new behavior is '
88 'PASS; If not specified, default = old behavior is PASS and new '
89 'behavior is FAIL')
90 group.add_argument(
Kuang-che Wu85c613c2019-01-09 15:46:11 +080091 '--metric',
Kuang-che Wud1b74152020-05-20 08:46:46 +080092 help='Metric name of performance test; example: '
93 '"cheets_SystemRawImageSize"')
Kuang-che Wu85c613c2019-01-09 15:46:11 +080094 group.add_argument(
Kuang-che Wub9705bd2018-06-28 17:59:18 +080095 '--args',
96 help='Extra args passed to "test_that --args"; Overrides the default')
97
Kuang-che Wu85c613c2019-01-09 15:46:11 +080098 group = parser.add_argument_group(title='Options for CTS/GTS tests')
99 group.add_argument('--cts_revision', help='CTS revision, like "9.0_r3"')
Kuang-che Wu63f836a2019-02-21 16:33:32 +0000100 group.add_argument('--cts_abi', choices=['arm', 'x86'])
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800101 group.add_argument(
102 '--cts_prefix',
103 help='Prefix of autotest test name, '
104 'like cheets_CTS_N, cheets_CTS_P, cheets_GTS')
105 group.add_argument(
106 '--cts_module', help='CTS/GTS module name, like "CtsCameraTestCases"')
107 group.add_argument(
108 '--cts_test',
109 help='CTS/GTS test name, like '
110 '"android.hardware.cts.CameraTest#testDisplayOrientation"')
111 group.add_argument('--cts_timeout', type=float, help='timeout, in seconds')
112
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800113 return parser
114
115
116def parse_test_report_log(result_log, metric):
117 """Parses autotest result log.
118
119 Args:
120 result_log: content of test_report.log
121 metric: what metric to capture if not None
122
123 Returns:
124 passed, values:
125 passed: True if test run successfully
126 values: captured metric values; None if test failed or metric is None
127 """
128 m = re.search(r'Total PASS: (\d+)/(\d+)', result_log)
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800129 passed = (m and m.group(1) == m.group(2))
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800130
131 if not metric:
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800132 return passed, None
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800133
134 values = []
135 for line in result_log.splitlines():
136 m = re.match(r'^(\S+)\s+(\w+)(?:\{\d+\})?\s+(\d+\.\d+)$', line)
137 if not m:
138 continue
139 if m.group(2) == metric:
140 values.append(float(m.group(3)))
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800141 return passed, values
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800142
143
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800144def get_additional_test_args(test_name):
145 """Gets extra arguments to specific test.
146
147 Some tests may require special arguments to run.
148
149 Args:
150 test_name: test name
151
152 Returns:
153 arguments (str)
154 """
155 if test_name.startswith('telemetry_'):
156 return 'local=True'
157 return ''
158
159
Kuang-che Wu6c5a5b22019-01-17 18:09:50 +0800160def prepare_to_run_test(opts):
161 # Some versions of ChromeOS SDK is broken and ship bad 'ssh' executable. This
Kuang-che Wu9fcf1082019-03-04 11:24:04 +0800162 # works around the issue. See crbug/906289 for detail.
163 # TODO(kcwu): remove this workaround once we no longer support bisecting
164 # versions earlier than R73-11445.0.0.
165 ssh_path = os.path.join(opts.chromeos_root, 'chroot/usr/bin/ssh')
Kuang-che Wu4fbd2d32019-03-07 01:07:57 +0800166 if os.path.exists(ssh_path):
Kuang-che Wud3a4e842019-12-11 12:15:23 +0800167 with open(ssh_path, 'rb') as f:
Kuang-che Wua5723492019-11-25 20:59:34 +0800168 if b'file descriptor passing not supported' in f.read():
169 cros_util.cros_sdk(opts.chromeos_root, 'sudo', 'emerge',
170 'net-misc/openssh')
Kuang-che Wu6c5a5b22019-01-17 18:09:50 +0800171
Kuang-che Wue9fc9e92020-09-11 20:28:13 +0800172 # Special handling for audio tests (b/136136270).
173 if opts.prebuilt:
174 autotest_dir = os.path.join(opts.chromeos_root,
175 cros_util.prebuilt_autotest_dir)
176 else:
177 autotest_dir = os.path.join(opts.chromeos_root,
178 'src/third_party/autotest/files')
179 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 shadow_config_path = os.path.join(autotest_dir, 'shadow_config.ini')
189 if not os.path.exists(shadow_config_path):
190 with open(shadow_config_path, 'w') as f:
191 f.write(
192 textwrap.dedent("""
193 [CROS]
194 enable_ssh_tunnel_for_servo: True
195 enable_ssh_tunnel_for_chameleon: True
196 enable_ssh_connection_for_devserver: True
197 enable_ssh_tunnel_for_moblab: True
198 """))
199
Kuang-che Wuf0ad9ac2019-09-06 13:05:04 +0800200 # test_that may use this ssh key and ssh complains its permission is too open.
Kuang-che Wu4a81ea72019-10-05 15:35:17 +0800201 # chmod every time just before run test_that because the permission may change
Kuang-che Wuf0ad9ac2019-09-06 13:05:04 +0800202 # after some git operations.
203 util.check_call(
204 'chmod',
205 'o-r,g-r',
206 'src/scripts/mod_for_test_scripts/ssh_keys/testing_rsa',
207 cwd=opts.chromeos_root)
208
Kuang-che Wu6c5a5b22019-01-17 18:09:50 +0800209 if opts.reinstall:
Kuang-che Wu44278142019-03-04 11:33:57 +0800210 util.ssh_cmd(opts.dut, 'rm', '-rf', '/usr/local/autotest')
Kuang-che Wu6c5a5b22019-01-17 18:09:50 +0800211
Kuang-che Wuda3abfe2019-03-21 14:48:12 +0800212 if opts.reboot_before_test:
Kuang-che Wu2ac9a922020-09-03 16:50:12 +0800213 cros_util.reboot(
214 opts.dut, force_reboot_callback=cros_lab_util.reboot_via_servo)
Kuang-che Wuda3abfe2019-03-21 14:48:12 +0800215
Kuang-che Wu6c5a5b22019-01-17 18:09:50 +0800216
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800217def run_test(opts):
218 """Runs an autotest test.
219
220 Args:
221 opts: An argparse.Namespace to hold command line arguments.
222
223 Returns:
224 path of test result (outside chroot)
225 """
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800226 prebuilt_autotest_dir = os.path.join(cros_util.chromeos_root_inside_chroot,
227 cros_util.prebuilt_autotest_dir)
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800228 # Set results dir inside source tree, so it's easier to access them outside
229 # chroot.
230 results_dir = os.path.join(cros_util.chromeos_root_inside_chroot,
231 'tmp/autotest_results_tmp')
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800232 if opts.prebuilt:
233 test_that_bin = os.path.join(prebuilt_autotest_dir,
234 'site_utils/test_that.py')
235 else:
236 test_that_bin = '/usr/bin/test_that'
Kuang-che Wuf3d03ca2019-03-11 17:31:40 +0800237 cmd = [
238 test_that_bin, opts.dut, opts.test_name, '--debug', '--results_dir',
Kuang-che Wu0c9b7942019-10-30 16:55:39 +0800239 results_dir
Kuang-che Wuf3d03ca2019-03-11 17:31:40 +0800240 ]
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800241 if opts.prebuilt:
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800242 cmd += ['--autotest_dir', prebuilt_autotest_dir]
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800243
244 args = get_additional_test_args(opts.test_name)
245 if opts.args:
246 if args:
Kuang-che Wu74768d32018-09-07 12:03:24 +0800247 logger.info(
248 'default test_that args `%s` is overridden by '
249 'command line option `%s`', args, opts.args)
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800250 cmd += ['--args', opts.args]
251 elif args:
252 cmd += ['--args', args]
253
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800254 try:
Kuang-che Wud4603d72018-11-29 17:51:21 +0800255 output = cros_util.cros_sdk(
256 opts.chromeos_root, *cmd, chrome_root=opts.chrome_root)
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800257 except subprocess.CalledProcessError as e:
Kuang-che Wu62677012020-07-13 14:25:18 +0800258 if e.output is None:
259 logger.error('cros_sdk failed before test started')
260 return None
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800261 output = e.output
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800262
263 m = re.search(r'Finished running tests. Results can be found in (\S+)',
264 output)
265 if not m:
266 logger.error('result dir is unknown')
267 return None
268 assert m.group(1) == results_dir
269 return results_dir.replace(cros_util.chromeos_root_inside_chroot,
270 opts.chromeos_root)
271
272
273def gather_test_result(opts, result_dir):
274 result_log_path = os.path.join(result_dir, 'test_report.log')
Kuang-che Wua5723492019-11-25 20:59:34 +0800275 with open(result_log_path) as f:
276 result_log = f.read()
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800277
278 passed, values = parse_test_report_log(result_log, opts.metric)
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800279 if opts.metric and not values:
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800280 values = []
281 for root, _, files in os.walk(result_dir):
282 for filename in files:
283 if filename != 'results-chart.json':
284 continue
285 full_path = os.path.join(root, filename)
Kuang-che Wu4f0dc232019-05-10 18:28:06 +0800286 values = catapult_util.get_benchmark_values(full_path, opts.metric)
287 return passed, values
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800288
289 return passed, values
290
291
Kuang-che Wufe1e88a2019-09-10 21:52:25 +0800292@cli.fatal_error_handler
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800293def main(args=None):
294 common.init()
295 parser = create_argument_parser()
296 opts = parser.parse_args(args)
297 common.config_logging(opts)
298
299 if not cros_util.is_dut(opts.dut):
Kuang-che Wu4a75f952019-03-26 17:22:42 +0800300 logger.error('%r is not a valid DUT address', opts.dut)
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800301 return FATAL
Kuang-che Wuc8c495d2019-08-19 17:48:58 +0800302 dut_os_version = cros_util.query_dut_short_version(opts.dut)
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800303
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800304 is_cts = (
305 opts.cts_revision or opts.cts_abi or opts.cts_prefix or opts.cts_module or
Kuang-che Wue07bff52019-10-29 20:50:45 +0800306 opts.cts_test or opts.cts_timeout)
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800307 if is_cts:
308 if opts.test_name or opts.metric or opts.args:
309 parser.error(
310 'do not specify --test_name, --metric, --args for CTS/GTS tests')
Kuang-che Wub91b1512019-05-29 15:16:29 +0800311 if not opts.cts_prefix:
312 parser.error('--cts_prefix should be specified for CTS/GTS tests')
313 if not opts.cts_module:
314 parser.error('--cts_module should be specified for CTS/GTS tests')
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800315 opts.test_name = '%s.tradefed-run-test' % opts.cts_prefix
Kuang-che Wue07bff52019-10-29 20:50:45 +0800316 opts.args = 'module=%s test=%s max_retry=0' % (opts.cts_module,
317 opts.cts_test)
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800318 if opts.cts_revision:
319 opts.args += ' revision=%s' % opts.cts_revision
320 if opts.cts_abi:
321 opts.args += ' abi=%s' % opts.cts_abi
322 if opts.cts_timeout:
323 opts.args += ' timeout=%s' % opts.cts_timeout
324 else:
325 if not opts.test_name:
326 parser.error('argument --test_name is required')
327
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800328 # Verify command line options.
329 if opts.metric:
Kuang-che Wu0a4304a2019-01-19 01:32:11 +0800330 if opts.fail_to_pass:
331 logger.error('--fail_to_pass is not for benchmark test (--metric)')
332 return FATAL
Kuang-che Wud4603d72018-11-29 17:51:21 +0800333 if opts.test_name.startswith('telemetry_'):
334 if not opts.chrome_root:
335 logger.error('--chrome_root is mandatory for telemetry tests')
336 return FATAL
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800337
Kuang-che Wu6c5a5b22019-01-17 18:09:50 +0800338 try:
339 prepare_to_run_test(opts)
340 except Exception:
341 logger.exception('failed when prepare, assume it is temporary; SKIP')
342 return SKIP
Kuang-che Wue47162d2018-10-29 17:24:04 +0800343
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800344 result_dir = run_test(opts)
345 if not result_dir:
Kuang-che Wudd802672018-08-10 19:40:14 +0800346 return FATAL
347
Kuang-che Wuc986b1d2019-04-15 16:45:20 +0800348 try:
349 passed, values = gather_test_result(opts, result_dir)
350 except Exception:
351 logger.exception('failed to parse test result')
352 return FATAL
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800353
Kuang-che Wuc8c495d2019-08-19 17:48:58 +0800354 # Sanity check. The OS version should not change.
Kuang-che Wu523bdf22019-08-20 12:11:09 +0800355 assert dut_os_version == cros_util.query_dut_short_version(opts.dut), \
356 'Someone else reflashed the DUT. ' \
357 'DUT locking is not respected? b/126141102'
Kuang-che Wuc8c495d2019-08-19 17:48:58 +0800358
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800359 if opts.metric:
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800360 if not values:
361 logger.warning('no values found; SKIP')
362 return SKIP
363
Kuang-che Wuc89f2a22019-11-26 15:30:50 +0800364 print('BISECT_RESULT_VALUES=', ' '.join(str(v) for v in values))
Kuang-che Wu81cde452019-04-08 16:56:51 +0800365 logger.info('values=%s', values)
366 # The exit code doesn't matter.
367 return OLD
Kuang-che Wu084eef22020-03-11 18:29:48 +0800368
369 if opts.fail_to_pass:
370 if passed:
371 logger.info('passed')
Kuang-che Wu0a4304a2019-01-19 01:32:11 +0800372 return NEW
Kuang-che Wu084eef22020-03-11 18:29:48 +0800373 logger.info('failed')
374 return OLD
375 if passed:
376 logger.info('passed')
377 return OLD
378 logger.info('failed')
379 return NEW
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800380
381
382if __name__ == '__main__':
383 sys.exit(EXIT_CODE_MAP[main()])