blob: 860277ebe6fa88ea16992f95361401cd12e6c766 [file] [log] [blame]
Kuang-che Wub9705bd2018-06-28 17:59:18 +08001#!/usr/bin/env python2
2# -*- 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
25from bisect_kit import cros_util
26from bisect_kit import util
27
28logger = logging.getLogger(__name__)
29
30OLD = 'old'
31NEW = 'new'
32SKIP = 'skip'
33FATAL = 'fatal'
34
35EXIT_CODE_MAP = {
Kuang-che Wu0476d1f2019-03-04 19:27:01 +080036 OLD: cli.EXIT_CODE_OLD,
37 NEW: cli.EXIT_CODE_NEW,
38 SKIP: cli.EXIT_CODE_SKIP,
39 FATAL: cli.EXIT_CODE_FATAL,
Kuang-che Wub9705bd2018-06-28 17:59:18 +080040}
41
42
43def create_argument_parser():
44 parser = argparse.ArgumentParser(description=__doc__)
Kuang-che Wufe1e88a2019-09-10 21:52:25 +080045 cli.patching_argparser_exit(parser)
Kuang-che Wub9705bd2018-06-28 17:59:18 +080046 common.add_common_arguments(parser)
47 parser.add_argument(
48 'dut',
49 nargs='?',
50 type=cli.argtype_notempty,
51 metavar='DUT',
52 default=configure.get('DUT', ''))
53 parser.add_argument(
54 '--chromeos_root',
55 type=cli.argtype_dir_path,
56 metavar='CHROMEOS_ROOT',
57 default=configure.get('CHROMEOS_ROOT', ''),
58 help='ChromeOS tree root')
59 parser.add_argument(
Kuang-che Wud4603d72018-11-29 17:51:21 +080060 '--chrome_root',
61 metavar='CHROME_ROOT',
62 type=cli.argtype_dir_path,
63 default=configure.get('CHROME_ROOT'),
64 help='Chrome tree root; necessary for telemetry tests')
65 parser.add_argument(
Kuang-che Wub9705bd2018-06-28 17:59:18 +080066 '--prebuilt',
67 action='store_true',
68 help='Run autotest using existing prebuilt package if specified; '
69 'otherwise use the default one')
70 parser.add_argument(
71 '--reinstall',
72 action='store_true',
73 help='Remove existing autotest folder on the DUT first')
Kuang-che Wuda3abfe2019-03-21 14:48:12 +080074 parser.add_argument(
75 '--reboot_before_test',
76 action='store_true',
77 help='Reboot before test run')
Kuang-che Wu85c613c2019-01-09 15:46:11 +080078
79 group = parser.add_argument_group(title='Options for normal autotest tests')
80 group.add_argument(
81 '--test_name', help='Test name, like "video_VideoDecodeAccelerator.h264"')
82 group.add_argument(
Kuang-che Wu0a4304a2019-01-19 01:32:11 +080083 '--fail_to_pass',
84 action='store_true',
85 help='For functional tests: old behavior is FAIL and new behavior is '
86 'PASS; If not specified, default = old behavior is PASS and new '
87 'behavior is FAIL')
88 group.add_argument(
Kuang-che Wu85c613c2019-01-09 15:46:11 +080089 '--metric',
90 help=
91 'Metric name of performance test; example: "cheets_SystemRawImageSize"')
92 group.add_argument(
Kuang-che Wub9705bd2018-06-28 17:59:18 +080093 '--args',
94 help='Extra args passed to "test_that --args"; Overrides the default')
95
Kuang-che Wu85c613c2019-01-09 15:46:11 +080096 group = parser.add_argument_group(title='Options for CTS/GTS tests')
97 group.add_argument('--cts_revision', help='CTS revision, like "9.0_r3"')
Kuang-che Wu63f836a2019-02-21 16:33:32 +000098 group.add_argument('--cts_abi', choices=['arm', 'x86'])
Kuang-che Wu85c613c2019-01-09 15:46:11 +080099 group.add_argument(
100 '--cts_prefix',
101 help='Prefix of autotest test name, '
102 'like cheets_CTS_N, cheets_CTS_P, cheets_GTS')
103 group.add_argument(
104 '--cts_module', help='CTS/GTS module name, like "CtsCameraTestCases"')
105 group.add_argument(
106 '--cts_test',
107 help='CTS/GTS test name, like '
108 '"android.hardware.cts.CameraTest#testDisplayOrientation"')
109 group.add_argument('--cts_timeout', type=float, help='timeout, in seconds')
110
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800111 return parser
112
113
114def parse_test_report_log(result_log, metric):
115 """Parses autotest result log.
116
117 Args:
118 result_log: content of test_report.log
119 metric: what metric to capture if not None
120
121 Returns:
122 passed, values:
123 passed: True if test run successfully
124 values: captured metric values; None if test failed or metric is None
125 """
126 m = re.search(r'Total PASS: (\d+)/(\d+)', result_log)
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800127 passed = (m and m.group(1) == m.group(2))
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800128
129 if not metric:
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800130 return passed, None
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800131
132 values = []
133 for line in result_log.splitlines():
134 m = re.match(r'^(\S+)\s+(\w+)(?:\{\d+\})?\s+(\d+\.\d+)$', line)
135 if not m:
136 continue
137 if m.group(2) == metric:
138 values.append(float(m.group(3)))
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800139 return passed, values
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800140
141
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800142def get_additional_test_args(test_name):
143 """Gets extra arguments to specific test.
144
145 Some tests may require special arguments to run.
146
147 Args:
148 test_name: test name
149
150 Returns:
151 arguments (str)
152 """
153 if test_name.startswith('telemetry_'):
154 return 'local=True'
155 return ''
156
157
Kuang-che Wu6c5a5b22019-01-17 18:09:50 +0800158def prepare_to_run_test(opts):
159 # Some versions of ChromeOS SDK is broken and ship bad 'ssh' executable. This
Kuang-che Wu9fcf1082019-03-04 11:24:04 +0800160 # works around the issue. See crbug/906289 for detail.
161 # TODO(kcwu): remove this workaround once we no longer support bisecting
162 # versions earlier than R73-11445.0.0.
163 ssh_path = os.path.join(opts.chromeos_root, 'chroot/usr/bin/ssh')
Kuang-che Wu4fbd2d32019-03-07 01:07:57 +0800164 if os.path.exists(ssh_path):
165 if 'file descriptor passing not supported' in open(ssh_path).read():
166 cros_util.cros_sdk(opts.chromeos_root, 'sudo', 'emerge',
167 'net-misc/openssh')
Kuang-che Wu6c5a5b22019-01-17 18:09:50 +0800168
Kuang-che Wuf0ad9ac2019-09-06 13:05:04 +0800169 # test_that may use this ssh key and ssh complains its permission is too open.
Kuang-che Wu4a81ea72019-10-05 15:35:17 +0800170 # chmod every time just before run test_that because the permission may change
Kuang-che Wuf0ad9ac2019-09-06 13:05:04 +0800171 # after some git operations.
172 util.check_call(
173 'chmod',
174 'o-r,g-r',
175 'src/scripts/mod_for_test_scripts/ssh_keys/testing_rsa',
176 cwd=opts.chromeos_root)
177
Kuang-che Wu6c5a5b22019-01-17 18:09:50 +0800178 if opts.reinstall:
Kuang-che Wu44278142019-03-04 11:33:57 +0800179 util.ssh_cmd(opts.dut, 'rm', '-rf', '/usr/local/autotest')
Kuang-che Wu6c5a5b22019-01-17 18:09:50 +0800180
Kuang-che Wuda3abfe2019-03-21 14:48:12 +0800181 if opts.reboot_before_test:
182 cros_util.reboot(opts.dut)
183
Kuang-che Wu6c5a5b22019-01-17 18:09:50 +0800184
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800185def run_test(opts):
186 """Runs an autotest test.
187
188 Args:
189 opts: An argparse.Namespace to hold command line arguments.
190
191 Returns:
192 path of test result (outside chroot)
193 """
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800194 prebuilt_autotest_dir = os.path.join(cros_util.chromeos_root_inside_chroot,
195 cros_util.prebuilt_autotest_dir)
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800196 # Set results dir inside source tree, so it's easier to access them outside
197 # chroot.
198 results_dir = os.path.join(cros_util.chromeos_root_inside_chroot,
199 'tmp/autotest_results_tmp')
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800200 if opts.prebuilt:
201 test_that_bin = os.path.join(prebuilt_autotest_dir,
202 'site_utils/test_that.py')
203 else:
204 test_that_bin = '/usr/bin/test_that'
Kuang-che Wuf3d03ca2019-03-11 17:31:40 +0800205 cmd = [
206 test_that_bin, opts.dut, opts.test_name, '--debug', '--results_dir',
Kuang-che Wu0c9b7942019-10-30 16:55:39 +0800207 results_dir
Kuang-che Wuf3d03ca2019-03-11 17:31:40 +0800208 ]
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800209 if opts.prebuilt:
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800210 cmd += ['--autotest_dir', prebuilt_autotest_dir]
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800211
212 args = get_additional_test_args(opts.test_name)
213 if opts.args:
214 if args:
Kuang-che Wu74768d32018-09-07 12:03:24 +0800215 logger.info(
216 'default test_that args `%s` is overridden by '
217 'command line option `%s`', args, opts.args)
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800218 cmd += ['--args', opts.args]
219 elif args:
220 cmd += ['--args', args]
221
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800222 try:
Kuang-che Wud4603d72018-11-29 17:51:21 +0800223 output = cros_util.cros_sdk(
224 opts.chromeos_root, *cmd, chrome_root=opts.chrome_root)
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800225 except subprocess.CalledProcessError as e:
226 output = e.output
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800227
228 m = re.search(r'Finished running tests. Results can be found in (\S+)',
229 output)
230 if not m:
231 logger.error('result dir is unknown')
232 return None
233 assert m.group(1) == results_dir
234 return results_dir.replace(cros_util.chromeos_root_inside_chroot,
235 opts.chromeos_root)
236
237
238def gather_test_result(opts, result_dir):
239 result_log_path = os.path.join(result_dir, 'test_report.log')
240 result_log = open(result_log_path).read()
241
242 passed, values = parse_test_report_log(result_log, opts.metric)
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800243 if opts.metric and not values:
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800244 values = []
245 for root, _, files in os.walk(result_dir):
246 for filename in files:
247 if filename != 'results-chart.json':
248 continue
249 full_path = os.path.join(root, filename)
Kuang-che Wu4f0dc232019-05-10 18:28:06 +0800250 values = catapult_util.get_benchmark_values(full_path, opts.metric)
251 return passed, values
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800252
253 return passed, values
254
255
Kuang-che Wufe1e88a2019-09-10 21:52:25 +0800256@cli.fatal_error_handler
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800257def main(args=None):
258 common.init()
259 parser = create_argument_parser()
260 opts = parser.parse_args(args)
261 common.config_logging(opts)
262
263 if not cros_util.is_dut(opts.dut):
Kuang-che Wu4a75f952019-03-26 17:22:42 +0800264 logger.error('%r is not a valid DUT address', opts.dut)
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800265 return FATAL
Kuang-che Wuc8c495d2019-08-19 17:48:58 +0800266 dut_os_version = cros_util.query_dut_short_version(opts.dut)
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800267
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800268 is_cts = (
269 opts.cts_revision or opts.cts_abi or opts.cts_prefix or opts.cts_module or
Kuang-che Wue07bff52019-10-29 20:50:45 +0800270 opts.cts_test or opts.cts_timeout)
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800271 if is_cts:
272 if opts.test_name or opts.metric or opts.args:
273 parser.error(
274 'do not specify --test_name, --metric, --args for CTS/GTS tests')
Kuang-che Wub91b1512019-05-29 15:16:29 +0800275 if not opts.cts_prefix:
276 parser.error('--cts_prefix should be specified for CTS/GTS tests')
277 if not opts.cts_module:
278 parser.error('--cts_module should be specified for CTS/GTS tests')
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800279 opts.test_name = '%s.tradefed-run-test' % opts.cts_prefix
Kuang-che Wue07bff52019-10-29 20:50:45 +0800280 opts.args = 'module=%s test=%s max_retry=0' % (opts.cts_module,
281 opts.cts_test)
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800282 if opts.cts_revision:
283 opts.args += ' revision=%s' % opts.cts_revision
284 if opts.cts_abi:
285 opts.args += ' abi=%s' % opts.cts_abi
286 if opts.cts_timeout:
287 opts.args += ' timeout=%s' % opts.cts_timeout
288 else:
289 if not opts.test_name:
290 parser.error('argument --test_name is required')
291
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800292 # Verify command line options.
293 if opts.metric:
Kuang-che Wu0a4304a2019-01-19 01:32:11 +0800294 if opts.fail_to_pass:
295 logger.error('--fail_to_pass is not for benchmark test (--metric)')
296 return FATAL
Kuang-che Wud4603d72018-11-29 17:51:21 +0800297 if opts.test_name.startswith('telemetry_'):
298 if not opts.chrome_root:
299 logger.error('--chrome_root is mandatory for telemetry tests')
300 return FATAL
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800301
Kuang-che Wu6c5a5b22019-01-17 18:09:50 +0800302 try:
303 prepare_to_run_test(opts)
304 except Exception:
305 logger.exception('failed when prepare, assume it is temporary; SKIP')
306 return SKIP
Kuang-che Wue47162d2018-10-29 17:24:04 +0800307
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800308 result_dir = run_test(opts)
309 if not result_dir:
Kuang-che Wudd802672018-08-10 19:40:14 +0800310 return FATAL
311
Kuang-che Wuc986b1d2019-04-15 16:45:20 +0800312 try:
313 passed, values = gather_test_result(opts, result_dir)
314 except Exception:
315 logger.exception('failed to parse test result')
316 return FATAL
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800317
Kuang-che Wuc8c495d2019-08-19 17:48:58 +0800318 # Sanity check. The OS version should not change.
Kuang-che Wu523bdf22019-08-20 12:11:09 +0800319 assert dut_os_version == cros_util.query_dut_short_version(opts.dut), \
320 'Someone else reflashed the DUT. ' \
321 'DUT locking is not respected? b/126141102'
Kuang-che Wuc8c495d2019-08-19 17:48:58 +0800322
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800323 if opts.metric:
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800324 if not values:
325 logger.warning('no values found; SKIP')
326 return SKIP
327
328 print('BISECT_RESULT_VALUES=', ' '.join(map(str, values)))
Kuang-che Wu81cde452019-04-08 16:56:51 +0800329 logger.info('values=%s', values)
330 # The exit code doesn't matter.
331 return OLD
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800332 else:
Kuang-che Wu0a4304a2019-01-19 01:32:11 +0800333 if opts.fail_to_pass:
334 if passed:
335 logger.info('passed')
336 return NEW
337 logger.info('failed')
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800338 return OLD
Kuang-che Wu0a4304a2019-01-19 01:32:11 +0800339 else:
340 if passed:
341 logger.info('passed')
342 return OLD
343 logger.info('failed')
344 return NEW
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800345
346
347if __name__ == '__main__':
348 sys.exit(EXIT_CODE_MAP[main()])