blob: 2f7a8032f7ae9336d491705761cf0417bbe0a591 [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
9only once. For example, if you overwrote chrome's unittest binary, your new
Kuang-che Wu927231f2018-07-24 14:21:56 +080010binary will be persistent across autotest runs. Add --reinstall if you want
Kuang-che Wub9705bd2018-06-28 17:59:18 +080011clean autotest install.
12"""
13from __future__ import print_function
14import argparse
15import json
16import logging
17import os
18import re
19import subprocess
20import sys
21
22from 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 = {
36 OLD: 0,
37 NEW: 1,
38 SKIP: 125,
39 FATAL: 126,
40}
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 Wu85c613c2019-01-09 15:46:11 +080073
74 group = parser.add_argument_group(title='Options for normal autotest tests')
75 group.add_argument(
76 '--test_name', help='Test name, like "video_VideoDecodeAccelerator.h264"')
77 group.add_argument(
Kuang-che Wu0a4304a2019-01-19 01:32:11 +080078 '--fail_to_pass',
79 action='store_true',
80 help='For functional tests: old behavior is FAIL and new behavior is '
81 'PASS; If not specified, default = old behavior is PASS and new '
82 'behavior is FAIL')
83 group.add_argument(
Kuang-che Wu85c613c2019-01-09 15:46:11 +080084 '--metric',
85 help=
86 'Metric name of performance test; example: "cheets_SystemRawImageSize"')
87 group.add_argument(
88 '--old_value',
89 type=float,
90 help='For performance test, old value of given metric')
91 group.add_argument(
92 '--new_value',
93 type=float,
94 help='For performance test, new value of given metric')
95 group.add_argument(
Kuang-che Wub9705bd2018-06-28 17:59:18 +080096 '--args',
97 help='Extra args passed to "test_that --args"; Overrides the default')
98
Kuang-che Wu85c613c2019-01-09 15:46:11 +080099 group = parser.add_argument_group(title='Options for CTS/GTS tests')
100 group.add_argument('--cts_revision', help='CTS revision, like "9.0_r3"')
101 group.add_argument('--cts_abi', choices=['arm', 'x86'])
102 group.add_argument(
103 '--cts_prefix',
104 help='Prefix of autotest test name, '
105 'like cheets_CTS_N, cheets_CTS_P, cheets_GTS')
106 group.add_argument(
107 '--cts_module', help='CTS/GTS module name, like "CtsCameraTestCases"')
108 group.add_argument(
109 '--cts_test',
110 help='CTS/GTS test name, like '
111 '"android.hardware.cts.CameraTest#testDisplayOrientation"')
112 group.add_argument('--cts_timeout', type=float, help='timeout, in seconds')
113
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800114 return parser
115
116
117def parse_test_report_log(result_log, metric):
118 """Parses autotest result log.
119
120 Args:
121 result_log: content of test_report.log
122 metric: what metric to capture if not None
123
124 Returns:
125 passed, values:
126 passed: True if test run successfully
127 values: captured metric values; None if test failed or metric is None
128 """
129 m = re.search(r'Total PASS: (\d+)/(\d+)', result_log)
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800130 passed = (m and m.group(1) == m.group(2))
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800131
132 if not metric:
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800133 return passed, None
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800134
135 values = []
136 for line in result_log.splitlines():
137 m = re.match(r'^(\S+)\s+(\w+)(?:\{\d+\})?\s+(\d+\.\d+)$', line)
138 if not m:
139 continue
140 if m.group(2) == metric:
141 values.append(float(m.group(3)))
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800142 return passed, values
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800143
144
145def parse_test_result_chart(json_path, metric):
146 data = json.load(open(json_path))
Kuang-che Wu3331caf2018-09-06 19:47:02 +0800147
148 # format 1, telemetry
149 if 'charts' in data:
150 summary = data['charts'][metric]['summary']
151
152 # format 2, autotest without graph
153 elif metric in data:
154 summary = data[metric]['summary']
155
156 # format 3, autotest with graph
157 elif metric.count('.') == 1:
158 name, subname = metric.split('.')
159 summary = data[name][subname]
160
161 else:
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800162 logger.error('metric "%s" not in %s', metric, json_path)
Kuang-che Wudd802672018-08-10 19:40:14 +0800163 return []
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800164
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800165 if 'values' in summary:
166 return summary['values']
167 return [summary['value']]
168
169
170def get_additional_test_args(test_name):
171 """Gets extra arguments to specific test.
172
173 Some tests may require special arguments to run.
174
175 Args:
176 test_name: test name
177
178 Returns:
179 arguments (str)
180 """
181 if test_name.startswith('telemetry_'):
182 return 'local=True'
183 return ''
184
185
Kuang-che Wu6c5a5b22019-01-17 18:09:50 +0800186def prepare_to_run_test(opts):
187 # Some versions of ChromeOS SDK is broken and ship bad 'ssh' executable. This
188 # works around the issue before we fixed the issue.
189 # TODO(kcwu): fix crbug/899490
190 cros_util.cros_sdk(opts.chromeos_root, 'sudo', 'emerge', 'net-misc/openssh')
191
192 if opts.reinstall:
193 util.check_call('ssh', opts.dut, 'rm', '-rf', '/usr/local/autotest')
194
195
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800196def run_test(opts):
197 """Runs an autotest test.
198
199 Args:
200 opts: An argparse.Namespace to hold command line arguments.
201
202 Returns:
203 path of test result (outside chroot)
204 """
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800205 prebuilt_autotest_dir = os.path.join(cros_util.chromeos_root_inside_chroot,
206 cros_util.prebuilt_autotest_dir)
207 # Set results dir inside source tree, so it's easier to access them outside
208 # chroot.
209 results_dir = os.path.join(cros_util.chromeos_root_inside_chroot,
210 'tmp/autotest_results_tmp')
211 if opts.prebuilt:
212 test_that_bin = os.path.join(prebuilt_autotest_dir,
213 'site_utils/test_that.py')
214 else:
215 test_that_bin = '/usr/bin/test_that'
216 cmd = [test_that_bin, opts.dut, opts.test_name, '--results_dir', results_dir]
217 if opts.prebuilt:
218 cmd += ['--autotest_dir', prebuilt_autotest_dir]
219
220 args = get_additional_test_args(opts.test_name)
221 if opts.args:
222 if args:
Kuang-che Wu74768d32018-09-07 12:03:24 +0800223 logger.info(
224 'default test_that args `%s` is overridden by '
225 'command line option `%s`', args, opts.args)
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800226 cmd += ['--args', opts.args]
227 elif args:
228 cmd += ['--args', args]
229
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800230 try:
Kuang-che Wud4603d72018-11-29 17:51:21 +0800231 output = cros_util.cros_sdk(
232 opts.chromeos_root, *cmd, chrome_root=opts.chrome_root)
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800233 except subprocess.CalledProcessError as e:
234 output = e.output
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800235
236 m = re.search(r'Finished running tests. Results can be found in (\S+)',
237 output)
238 if not m:
239 logger.error('result dir is unknown')
240 return None
241 assert m.group(1) == results_dir
242 return results_dir.replace(cros_util.chromeos_root_inside_chroot,
243 opts.chromeos_root)
244
245
246def gather_test_result(opts, result_dir):
247 result_log_path = os.path.join(result_dir, 'test_report.log')
248 result_log = open(result_log_path).read()
249
250 passed, values = parse_test_report_log(result_log, opts.metric)
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800251 if opts.metric and not values:
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800252 values = []
253 for root, _, files in os.walk(result_dir):
254 for filename in files:
255 if filename != 'results-chart.json':
256 continue
257 full_path = os.path.join(root, filename)
258 values += parse_test_result_chart(full_path, opts.metric)
259
260 return passed, values
261
262
263def main(args=None):
264 common.init()
265 parser = create_argument_parser()
266 opts = parser.parse_args(args)
267 common.config_logging(opts)
268
269 if not cros_util.is_dut(opts.dut):
270 return FATAL
271
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800272 is_cts = (
273 opts.cts_revision or opts.cts_abi or opts.cts_prefix or opts.cts_module or
274 opts.cts_test or opts.cts_timeout)
275 if is_cts:
276 if opts.test_name or opts.metric or opts.args:
277 parser.error(
278 'do not specify --test_name, --metric, --args for CTS/GTS tests')
279 opts.test_name = '%s.tradefed-run-test' % opts.cts_prefix
280 opts.args = 'module=%s test=%s' % (opts.cts_module, opts.cts_test)
281 if opts.cts_revision:
282 opts.args += ' revision=%s' % opts.cts_revision
283 if opts.cts_abi:
284 opts.args += ' abi=%s' % opts.cts_abi
285 if opts.cts_timeout:
286 opts.args += ' timeout=%s' % opts.cts_timeout
287 else:
288 if not opts.test_name:
289 parser.error('argument --test_name is required')
290
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800291 # Verify command line options.
292 if opts.metric:
293 if opts.old_value is None:
294 logger.error('--old_value is not provided')
295 return FATAL
296 if opts.new_value is None:
297 logger.error('--new_value is not provided')
298 return FATAL
Kuang-che Wu0a4304a2019-01-19 01:32:11 +0800299 if opts.fail_to_pass:
300 logger.error('--fail_to_pass is not for benchmark test (--metric)')
301 return FATAL
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800302 else:
303 if opts.old_value is not None:
304 logger.error('--old_value is provided but --metric is not')
305 return FATAL
306 if opts.new_value is not None:
307 logger.error('--new_value is provided but --metric is not')
308 return FATAL
Kuang-che Wud4603d72018-11-29 17:51:21 +0800309 if opts.test_name.startswith('telemetry_'):
310 if not opts.chrome_root:
311 logger.error('--chrome_root is mandatory for telemetry tests')
312 return FATAL
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800313
Kuang-che Wu6c5a5b22019-01-17 18:09:50 +0800314 try:
315 prepare_to_run_test(opts)
316 except Exception:
317 logger.exception('failed when prepare, assume it is temporary; SKIP')
318 return SKIP
Kuang-che Wue47162d2018-10-29 17:24:04 +0800319
Kuang-che Wu171dcb62018-10-25 12:37:05 +0800320 result_dir = run_test(opts)
321 if not result_dir:
Kuang-che Wudd802672018-08-10 19:40:14 +0800322 return FATAL
323
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800324 passed, values = gather_test_result(opts, result_dir)
325
326 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)))
332 average = float(sum(values)) / len(values)
Kuang-che Wu689f1542018-08-20 17:45:58 +0800333 if abs(average - opts.old_value) < abs(average - opts.new_value):
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800334 logger.info('values=%s, average=%s; OLD', values, average)
335 return OLD
336 logger.info('values=%s, average=%s; NEW', values, average)
337 return NEW
338 else:
Kuang-che Wu0a4304a2019-01-19 01:32:11 +0800339 if opts.fail_to_pass:
340 if passed:
341 logger.info('passed')
342 return NEW
343 logger.info('failed')
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800344 return OLD
Kuang-che Wu0a4304a2019-01-19 01:32:11 +0800345 else:
346 if passed:
347 logger.info('passed')
348 return OLD
349 logger.info('failed')
350 return NEW
Kuang-che Wub9705bd2018-06-28 17:59:18 +0800351
352
353if __name__ == '__main__':
354 sys.exit(EXIT_CODE_MAP[main()])