blob: 06ff6fc60acfb03b64f89453d9b9b7d56a4b3f62 [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__)
45 common.add_common_arguments(parser)
46 parser.add_argument(
47 'dut',
48 nargs='?',
49 type=cli.argtype_notempty,
50 metavar='DUT',
51 default=configure.get('DUT', ''))
52 parser.add_argument(
53 '--chromeos_root',
54 type=cli.argtype_dir_path,
55 metavar='CHROMEOS_ROOT',
56 default=configure.get('CHROMEOS_ROOT', ''),
57 help='ChromeOS tree root')
58 parser.add_argument(
Kuang-che Wud4603d72018-11-29 17:51:21 +080059 '--chrome_root',
60 metavar='CHROME_ROOT',
61 type=cli.argtype_dir_path,
62 default=configure.get('CHROME_ROOT'),
63 help='Chrome tree root; necessary for telemetry tests')
64 parser.add_argument(
Kuang-che Wub9705bd2018-06-28 17:59:18 +080065 '--prebuilt',
66 action='store_true',
67 help='Run autotest using existing prebuilt package if specified; '
68 'otherwise use the default one')
69 parser.add_argument(
70 '--reinstall',
71 action='store_true',
72 help='Remove existing autotest folder on the DUT first')
Kuang-che Wuda3abfe2019-03-21 14:48:12 +080073 parser.add_argument(
74 '--reboot_before_test',
75 action='store_true',
76 help='Reboot before test run')
Kuang-che Wu85c613c2019-01-09 15:46:11 +080077
78 group = parser.add_argument_group(title='Options for normal autotest tests')
79 group.add_argument(
80 '--test_name', help='Test name, like "video_VideoDecodeAccelerator.h264"')
81 group.add_argument(
Kuang-che Wu0a4304a2019-01-19 01:32:11 +080082 '--fail_to_pass',
83 action='store_true',
84 help='For functional tests: old behavior is FAIL and new behavior is '
85 'PASS; If not specified, default = old behavior is PASS and new '
86 'behavior is FAIL')
87 group.add_argument(
Kuang-che Wu85c613c2019-01-09 15:46:11 +080088 '--metric',
89 help=
90 'Metric name of performance test; example: "cheets_SystemRawImageSize"')
91 group.add_argument(
Kuang-che Wub9705bd2018-06-28 17:59:18 +080092 '--args',
93 help='Extra args passed to "test_that --args"; Overrides the default')
94
Kuang-che Wu85c613c2019-01-09 15:46:11 +080095 group = parser.add_argument_group(title='Options for CTS/GTS tests')
96 group.add_argument('--cts_revision', help='CTS revision, like "9.0_r3"')
Kuang-che Wu63f836a2019-02-21 16:33:32 +000097 group.add_argument('--cts_abi', choices=['arm', 'x86'])
Kuang-che Wu85c613c2019-01-09 15:46:11 +080098 group.add_argument(
99 '--cts_prefix',
100 help='Prefix of autotest test name, '
101 'like cheets_CTS_N, cheets_CTS_P, cheets_GTS')
102 group.add_argument(
103 '--cts_module', help='CTS/GTS module name, like "CtsCameraTestCases"')
104 group.add_argument(
105 '--cts_test',
106 help='CTS/GTS test name, like '
107 '"android.hardware.cts.CameraTest#testDisplayOrientation"')
108 group.add_argument('--cts_timeout', type=float, help='timeout, in seconds')
Kuang-che Wu958d7d02019-05-30 17:58:21 +0800109 group.add_argument(
110 '--cts_runonce', action='store_true', help='run test only once')
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800111
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):
166 if 'file descriptor passing not supported' in open(ssh_path).read():
167 cros_util.cros_sdk(opts.chromeos_root, 'sudo', 'emerge',
168 'net-misc/openssh')
Kuang-che Wu6c5a5b22019-01-17 18:09:50 +0800169
170 if opts.reinstall:
Kuang-che Wu44278142019-03-04 11:33:57 +0800171 util.ssh_cmd(opts.dut, 'rm', '-rf', '/usr/local/autotest')
Kuang-che Wu6c5a5b22019-01-17 18:09:50 +0800172
Kuang-che Wuda3abfe2019-03-21 14:48:12 +0800173 if opts.reboot_before_test:
174 cros_util.reboot(opts.dut)
175
Kuang-che Wu6c5a5b22019-01-17 18:09:50 +0800176
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800177def run_test(opts):
178 """Runs an autotest test.
179
180 Args:
181 opts: An argparse.Namespace to hold command line arguments.
182
183 Returns:
184 path of test result (outside chroot)
185 """
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800186 prebuilt_autotest_dir = os.path.join(cros_util.chromeos_root_inside_chroot,
187 cros_util.prebuilt_autotest_dir)
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800188 # Set results dir inside source tree, so it's easier to access them outside
189 # chroot.
190 results_dir = os.path.join(cros_util.chromeos_root_inside_chroot,
191 'tmp/autotest_results_tmp')
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800192 if opts.prebuilt:
193 test_that_bin = os.path.join(prebuilt_autotest_dir,
194 'site_utils/test_that.py')
195 else:
196 test_that_bin = '/usr/bin/test_that'
Kuang-che Wuf3d03ca2019-03-11 17:31:40 +0800197 cmd = [
198 test_that_bin, opts.dut, opts.test_name, '--debug', '--results_dir',
199 results_dir
200 ]
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800201 if opts.prebuilt:
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800202 cmd += ['--autotest_dir', prebuilt_autotest_dir]
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800203
204 args = get_additional_test_args(opts.test_name)
205 if opts.args:
206 if args:
Kuang-che Wu74768d32018-09-07 12:03:24 +0800207 logger.info(
208 'default test_that args `%s` is overridden by '
209 'command line option `%s`', args, opts.args)
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800210 cmd += ['--args', opts.args]
211 elif args:
212 cmd += ['--args', args]
213
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800214 try:
Kuang-che Wud4603d72018-11-29 17:51:21 +0800215 output = cros_util.cros_sdk(
216 opts.chromeos_root, *cmd, chrome_root=opts.chrome_root)
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800217 except subprocess.CalledProcessError as e:
218 output = e.output
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800219
220 m = re.search(r'Finished running tests. Results can be found in (\S+)',
221 output)
222 if not m:
223 logger.error('result dir is unknown')
224 return None
225 assert m.group(1) == results_dir
226 return results_dir.replace(cros_util.chromeos_root_inside_chroot,
227 opts.chromeos_root)
228
229
230def gather_test_result(opts, result_dir):
231 result_log_path = os.path.join(result_dir, 'test_report.log')
232 result_log = open(result_log_path).read()
233
234 passed, values = parse_test_report_log(result_log, opts.metric)
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800235 if opts.metric and not values:
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800236 values = []
237 for root, _, files in os.walk(result_dir):
238 for filename in files:
239 if filename != 'results-chart.json':
240 continue
241 full_path = os.path.join(root, filename)
Kuang-che Wu4f0dc232019-05-10 18:28:06 +0800242 values = catapult_util.get_benchmark_values(full_path, opts.metric)
243 return passed, values
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800244
245 return passed, values
246
247
248def main(args=None):
249 common.init()
250 parser = create_argument_parser()
251 opts = parser.parse_args(args)
252 common.config_logging(opts)
253
254 if not cros_util.is_dut(opts.dut):
Kuang-che Wu4a75f952019-03-26 17:22:42 +0800255 logger.error('%r is not a valid DUT address', opts.dut)
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800256 return FATAL
Kuang-che Wuc8c495d2019-08-19 17:48:58 +0800257 dut_os_version = cros_util.query_dut_short_version(opts.dut)
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800258
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800259 is_cts = (
260 opts.cts_revision or opts.cts_abi or opts.cts_prefix or opts.cts_module or
Kuang-che Wu958d7d02019-05-30 17:58:21 +0800261 opts.cts_test or opts.cts_runonce or opts.cts_timeout)
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800262 if is_cts:
263 if opts.test_name or opts.metric or opts.args:
264 parser.error(
265 'do not specify --test_name, --metric, --args for CTS/GTS tests')
Kuang-che Wub91b1512019-05-29 15:16:29 +0800266 if not opts.cts_prefix:
267 parser.error('--cts_prefix should be specified for CTS/GTS tests')
268 if not opts.cts_module:
269 parser.error('--cts_module should be specified for CTS/GTS tests')
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800270 opts.test_name = '%s.tradefed-run-test' % opts.cts_prefix
271 opts.args = 'module=%s test=%s' % (opts.cts_module, opts.cts_test)
272 if opts.cts_revision:
273 opts.args += ' revision=%s' % opts.cts_revision
274 if opts.cts_abi:
275 opts.args += ' abi=%s' % opts.cts_abi
Kuang-che Wu958d7d02019-05-30 17:58:21 +0800276 if opts.cts_runonce:
277 opts.args += ' max_retry=0'
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800278 if opts.cts_timeout:
279 opts.args += ' timeout=%s' % opts.cts_timeout
280 else:
281 if not opts.test_name:
282 parser.error('argument --test_name is required')
283
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800284 # Verify command line options.
285 if opts.metric:
Kuang-che Wu0a4304a2019-01-19 01:32:11 +0800286 if opts.fail_to_pass:
287 logger.error('--fail_to_pass is not for benchmark test (--metric)')
288 return FATAL
Kuang-che Wud4603d72018-11-29 17:51:21 +0800289 if opts.test_name.startswith('telemetry_'):
290 if not opts.chrome_root:
291 logger.error('--chrome_root is mandatory for telemetry tests')
292 return FATAL
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800293
Kuang-che Wu6c5a5b22019-01-17 18:09:50 +0800294 try:
295 prepare_to_run_test(opts)
296 except Exception:
297 logger.exception('failed when prepare, assume it is temporary; SKIP')
298 return SKIP
Kuang-che Wue47162d2018-10-29 17:24:04 +0800299
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800300 result_dir = run_test(opts)
301 if not result_dir:
Kuang-che Wudd802672018-08-10 19:40:14 +0800302 return FATAL
303
Kuang-che Wuc986b1d2019-04-15 16:45:20 +0800304 try:
305 passed, values = gather_test_result(opts, result_dir)
306 except Exception:
307 logger.exception('failed to parse test result')
308 return FATAL
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800309
Kuang-che Wuc8c495d2019-08-19 17:48:58 +0800310 # Sanity check. The OS version should not change.
311 assert dut_os_version == cros_util.query_dut_short_version(opts.dut)
312
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800313 if opts.metric:
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800314 if not values:
315 logger.warning('no values found; SKIP')
316 return SKIP
317
318 print('BISECT_RESULT_VALUES=', ' '.join(map(str, values)))
Kuang-che Wu81cde452019-04-08 16:56:51 +0800319 logger.info('values=%s', values)
320 # The exit code doesn't matter.
321 return OLD
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800322 else:
Kuang-che Wu0a4304a2019-01-19 01:32:11 +0800323 if opts.fail_to_pass:
324 if passed:
325 logger.info('passed')
326 return NEW
327 logger.info('failed')
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800328 return OLD
Kuang-che Wu0a4304a2019-01-19 01:32:11 +0800329 else:
330 if passed:
331 logger.info('passed')
332 return OLD
333 logger.info('failed')
334 return NEW
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800335
336
337if __name__ == '__main__':
338 sys.exit(EXIT_CODE_MAP[main()])