blob: 71df5c07668fcf1ff021bd4e1c48ecfaba0d932c [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')
Kuang-che Wu958d7d02019-05-30 17:58:21 +0800110 group.add_argument(
111 '--cts_runonce', action='store_true', help='run test only once')
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800112
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):
167 if 'file descriptor passing not supported' in open(ssh_path).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 Wuf0ad9ac2019-09-06 13:05:04 +0800171 # test_that may use this ssh key and ssh complains its permission is too open.
Kuang-che Wu4a81ea72019-10-05 15:35:17 +0800172 # chmod every time just before run test_that because the permission may change
Kuang-che Wuf0ad9ac2019-09-06 13:05:04 +0800173 # after some git operations.
174 util.check_call(
175 'chmod',
176 'o-r,g-r',
177 'src/scripts/mod_for_test_scripts/ssh_keys/testing_rsa',
178 cwd=opts.chromeos_root)
179
Kuang-che Wu6c5a5b22019-01-17 18:09:50 +0800180 if opts.reinstall:
Kuang-che Wu44278142019-03-04 11:33:57 +0800181 util.ssh_cmd(opts.dut, 'rm', '-rf', '/usr/local/autotest')
Kuang-che Wu6c5a5b22019-01-17 18:09:50 +0800182
Kuang-che Wuda3abfe2019-03-21 14:48:12 +0800183 if opts.reboot_before_test:
184 cros_util.reboot(opts.dut)
185
Kuang-che Wu6c5a5b22019-01-17 18:09:50 +0800186
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800187def run_test(opts):
188 """Runs an autotest test.
189
190 Args:
191 opts: An argparse.Namespace to hold command line arguments.
192
193 Returns:
194 path of test result (outside chroot)
195 """
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800196 prebuilt_autotest_dir = os.path.join(cros_util.chromeos_root_inside_chroot,
197 cros_util.prebuilt_autotest_dir)
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800198 # Set results dir inside source tree, so it's easier to access them outside
199 # chroot.
200 results_dir = os.path.join(cros_util.chromeos_root_inside_chroot,
201 'tmp/autotest_results_tmp')
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800202 if opts.prebuilt:
203 test_that_bin = os.path.join(prebuilt_autotest_dir,
204 'site_utils/test_that.py')
205 else:
206 test_that_bin = '/usr/bin/test_that'
Kuang-che Wuf3d03ca2019-03-11 17:31:40 +0800207 cmd = [
208 test_that_bin, opts.dut, opts.test_name, '--debug', '--results_dir',
209 results_dir
210 ]
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800211 if opts.prebuilt:
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800212 cmd += ['--autotest_dir', prebuilt_autotest_dir]
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800213
214 args = get_additional_test_args(opts.test_name)
215 if opts.args:
216 if args:
Kuang-che Wu74768d32018-09-07 12:03:24 +0800217 logger.info(
218 'default test_that args `%s` is overridden by '
219 'command line option `%s`', args, opts.args)
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800220 cmd += ['--args', opts.args]
221 elif args:
222 cmd += ['--args', args]
223
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800224 try:
Kuang-che Wud4603d72018-11-29 17:51:21 +0800225 output = cros_util.cros_sdk(
226 opts.chromeos_root, *cmd, chrome_root=opts.chrome_root)
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800227 except subprocess.CalledProcessError as e:
228 output = e.output
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800229
230 m = re.search(r'Finished running tests. Results can be found in (\S+)',
231 output)
232 if not m:
233 logger.error('result dir is unknown')
234 return None
235 assert m.group(1) == results_dir
236 return results_dir.replace(cros_util.chromeos_root_inside_chroot,
237 opts.chromeos_root)
238
239
240def gather_test_result(opts, result_dir):
241 result_log_path = os.path.join(result_dir, 'test_report.log')
242 result_log = open(result_log_path).read()
243
244 passed, values = parse_test_report_log(result_log, opts.metric)
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800245 if opts.metric and not values:
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800246 values = []
247 for root, _, files in os.walk(result_dir):
248 for filename in files:
249 if filename != 'results-chart.json':
250 continue
251 full_path = os.path.join(root, filename)
Kuang-che Wu4f0dc232019-05-10 18:28:06 +0800252 values = catapult_util.get_benchmark_values(full_path, opts.metric)
253 return passed, values
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800254
255 return passed, values
256
257
Kuang-che Wufe1e88a2019-09-10 21:52:25 +0800258@cli.fatal_error_handler
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800259def main(args=None):
260 common.init()
261 parser = create_argument_parser()
262 opts = parser.parse_args(args)
263 common.config_logging(opts)
264
265 if not cros_util.is_dut(opts.dut):
Kuang-che Wu4a75f952019-03-26 17:22:42 +0800266 logger.error('%r is not a valid DUT address', opts.dut)
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800267 return FATAL
Kuang-che Wuc8c495d2019-08-19 17:48:58 +0800268 dut_os_version = cros_util.query_dut_short_version(opts.dut)
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800269
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800270 is_cts = (
271 opts.cts_revision or opts.cts_abi or opts.cts_prefix or opts.cts_module or
Kuang-che Wu958d7d02019-05-30 17:58:21 +0800272 opts.cts_test or opts.cts_runonce or opts.cts_timeout)
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800273 if is_cts:
274 if opts.test_name or opts.metric or opts.args:
275 parser.error(
276 'do not specify --test_name, --metric, --args for CTS/GTS tests')
Kuang-che Wub91b1512019-05-29 15:16:29 +0800277 if not opts.cts_prefix:
278 parser.error('--cts_prefix should be specified for CTS/GTS tests')
279 if not opts.cts_module:
280 parser.error('--cts_module should be specified for CTS/GTS tests')
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800281 opts.test_name = '%s.tradefed-run-test' % opts.cts_prefix
282 opts.args = 'module=%s test=%s' % (opts.cts_module, opts.cts_test)
283 if opts.cts_revision:
284 opts.args += ' revision=%s' % opts.cts_revision
285 if opts.cts_abi:
286 opts.args += ' abi=%s' % opts.cts_abi
Kuang-che Wu958d7d02019-05-30 17:58:21 +0800287 if opts.cts_runonce:
288 opts.args += ' max_retry=0'
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800289 if opts.cts_timeout:
290 opts.args += ' timeout=%s' % opts.cts_timeout
291 else:
292 if not opts.test_name:
293 parser.error('argument --test_name is required')
294
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800295 # Verify command line options.
296 if opts.metric:
Kuang-che Wu0a4304a2019-01-19 01:32:11 +0800297 if opts.fail_to_pass:
298 logger.error('--fail_to_pass is not for benchmark test (--metric)')
299 return FATAL
Kuang-che Wud4603d72018-11-29 17:51:21 +0800300 if opts.test_name.startswith('telemetry_'):
301 if not opts.chrome_root:
302 logger.error('--chrome_root is mandatory for telemetry tests')
303 return FATAL
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800304
Kuang-che Wu6c5a5b22019-01-17 18:09:50 +0800305 try:
306 prepare_to_run_test(opts)
307 except Exception:
308 logger.exception('failed when prepare, assume it is temporary; SKIP')
309 return SKIP
Kuang-che Wue47162d2018-10-29 17:24:04 +0800310
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800311 result_dir = run_test(opts)
312 if not result_dir:
Kuang-che Wudd802672018-08-10 19:40:14 +0800313 return FATAL
314
Kuang-che Wuc986b1d2019-04-15 16:45:20 +0800315 try:
316 passed, values = gather_test_result(opts, result_dir)
317 except Exception:
318 logger.exception('failed to parse test result')
319 return FATAL
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800320
Kuang-che Wuc8c495d2019-08-19 17:48:58 +0800321 # Sanity check. The OS version should not change.
Kuang-che Wu523bdf22019-08-20 12:11:09 +0800322 assert dut_os_version == cros_util.query_dut_short_version(opts.dut), \
323 'Someone else reflashed the DUT. ' \
324 'DUT locking is not respected? b/126141102'
Kuang-che Wuc8c495d2019-08-19 17:48:58 +0800325
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800326 if opts.metric:
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800327 if not values:
328 logger.warning('no values found; SKIP')
329 return SKIP
330
331 print('BISECT_RESULT_VALUES=', ' '.join(map(str, values)))
Kuang-che Wu81cde452019-04-08 16:56:51 +0800332 logger.info('values=%s', values)
333 # The exit code doesn't matter.
334 return OLD
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800335 else:
Kuang-che Wu0a4304a2019-01-19 01:32:11 +0800336 if opts.fail_to_pass:
337 if passed:
338 logger.info('passed')
339 return NEW
340 logger.info('failed')
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800341 return OLD
Kuang-che Wu0a4304a2019-01-19 01:32:11 +0800342 else:
343 if passed:
344 logger.info('passed')
345 return OLD
346 logger.info('failed')
347 return NEW
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800348
349
350if __name__ == '__main__':
351 sys.exit(EXIT_CODE_MAP[main()])