blob: 2ae07093fc9108e4d6b7fa4e4930e12679e66b80 [file] [log] [blame]
Kuang-che Wu875c89a2020-01-08 14:30:55 +08001#!/usr/bin/env python3
Kuang-che Wu32f27242019-05-16 17:34:50 +08002# -*- coding: utf-8 -*-
3# Copyright 2019 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 tast tests."""
7from __future__ import print_function
8import argparse
9import json
10import logging
11import os
12import re
Kuang-che Wu6c51d302019-06-19 09:07:20 +080013import shutil
Kuang-che Wu32f27242019-05-16 17:34:50 +080014import subprocess
15import sys
16
17from bisect_kit import catapult_util
18from bisect_kit import cli
19from bisect_kit import common
20from bisect_kit import configure
Kuang-che Wu2ac9a922020-09-03 16:50:12 +080021from bisect_kit import cros_lab_util
Kuang-che Wu32f27242019-05-16 17:34:50 +080022from bisect_kit import cros_util
Kuang-che Wu11713052019-05-30 16:21:54 +080023from bisect_kit import util
Kuang-che Wu32f27242019-05-16 17:34:50 +080024
25logger = logging.getLogger(__name__)
26
27OLD = 'old'
28NEW = 'new'
29SKIP = 'skip'
30FATAL = 'fatal'
31
32EXIT_CODE_MAP = {
33 OLD: cli.EXIT_CODE_OLD,
34 NEW: cli.EXIT_CODE_NEW,
35 SKIP: cli.EXIT_CODE_SKIP,
36 FATAL: cli.EXIT_CODE_FATAL,
37}
38
39
40def create_argument_parser():
41 parser = argparse.ArgumentParser(description=__doc__)
Kuang-che Wufe1e88a2019-09-10 21:52:25 +080042 cli.patching_argparser_exit(parser)
Kuang-che Wu32f27242019-05-16 17:34:50 +080043 common.add_common_arguments(parser)
44 parser.add_argument(
45 'dut',
46 nargs='?',
47 type=cli.argtype_notempty,
48 metavar='DUT',
49 default=configure.get('DUT', ''))
50 parser.add_argument(
51 '--chromeos_root',
52 type=cli.argtype_dir_path,
53 metavar='CHROMEOS_ROOT',
54 default=configure.get('CHROMEOS_ROOT', ''),
55 help='ChromeOS tree root')
56 parser.add_argument(
Kuang-che Wu5963ebf2020-10-21 09:01:04 +080057 '--prebuilt',
58 action='store_true',
59 help='Run tast using existing server prebuilt package if specified; '
60 'otherwise use the default one')
61 parser.add_argument(
Kuang-che Wu32f27242019-05-16 17:34:50 +080062 '--tast_build',
63 action='store_true',
64 help='Build tast test bundle (-build=true) if specified; '
65 'default is using prebuilt bundle on the DUT')
66 parser.add_argument(
Kuang-che Wu4fc0f1c2019-08-02 21:56:49 +080067 '--with_private_bundles',
68 action='store_true',
69 help='Whether search tests in private bundles or not')
70 parser.add_argument(
Kuang-che Wu32f27242019-05-16 17:34:50 +080071 '--reboot_before_test',
72 action='store_true',
73 help='Reboot before test run')
74
75 group = parser.add_argument_group(title='Options for normal autotest tests')
76 group.add_argument(
77 '--test_name',
78 required=True,
79 help='Test name, like "video_VideoDecodeAccelerator.h264"')
80 group.add_argument(
81 '--fail_to_pass',
82 action='store_true',
83 help='For functional tests: old behavior is FAIL and new behavior is '
84 'PASS; If not specified, default = old behavior is PASS and new '
85 'behavior is FAIL')
86 group.add_argument(
87 '--metric',
Kuang-che Wud1b74152020-05-20 08:46:46 +080088 help='Metric name of performance test; example: '
89 '"cheets_SystemRawImageSize"')
Kuang-che Wu32f27242019-05-16 17:34:50 +080090
91 return parser
92
93
94def prepare_to_run_test(opts):
95 if opts.reboot_before_test:
Kuang-che Wu2ac9a922020-09-03 16:50:12 +080096 cros_util.reboot(
97 opts.dut, force_reboot_callback=cros_lab_util.reboot_via_servo)
Kuang-che Wu32f27242019-05-16 17:34:50 +080098
99
Kuang-che Wu5963ebf2020-10-21 09:01:04 +0800100def tast_cmd(opts, cmd, *args):
101 flags = []
102 if opts.prebuilt:
103 tast_dir = os.path.join(cros_util.chromeos_root_inside_chroot,
104 cros_util.prebuilt_tast_dir)
105 tast_bin = os.path.join(tast_dir, 'tast')
106 flags += ['-remotebundledir', os.path.join(tast_dir, 'bundles', 'remote')]
107 flags += ['-remotedatadir', os.path.join(tast_dir, 'data')]
108 flags += ['-remoterunner', os.path.join(tast_dir, 'remote_test_runner')]
109 if cmd == 'run':
110 flags += ['-defaultvarsdir', os.path.join(tast_dir, 'vars', 'private')]
111 else:
112 tast_bin = 'tast'
113
Zheng-Jie Chang181be6f2020-03-17 16:16:08 +0800114 # TODO(zjchang): ensure the tast version for buildbucket builds is correct
Kuang-che Wu11713052019-05-30 16:21:54 +0800115 if not opts.tast_build:
Kuang-che Wu4fc0f1c2019-08-02 21:56:49 +0800116 flags.append('-build=false')
117 if opts.with_private_bundles:
118 flags.append('-downloadprivatebundles=true')
Kuang-che Wu5963ebf2020-10-21 09:01:04 +0800119
120 return [tast_bin, '-verbose', cmd] + flags + list(args)
121
122
123def get_tast_bundle_info(opts, pattern=None):
124 bundles = ['cros']
125 if opts.with_private_bundles:
126 bundles.append('crosint')
Kuang-che Wu4fc0f1c2019-08-02 21:56:49 +0800127
128 args = [opts.dut]
Kuang-che Wu11713052019-05-30 16:21:54 +0800129 if pattern:
Kuang-che Wu4fc0f1c2019-08-02 21:56:49 +0800130 args.append(pattern)
131
132 result = {}
133 for bundle in bundles:
Kuang-che Wu5963ebf2020-10-21 09:01:04 +0800134 cmd = tast_cmd(opts, 'list', '-json', '-buildbundle=' + bundle, *args)
Kuang-che Wubcafc552019-08-15 15:27:02 +0800135 json_text = cros_util.cros_sdk(opts.chromeos_root, *cmd, log_stdout=False)
Kuang-che Wu4fc0f1c2019-08-02 21:56:49 +0800136 for entry in json.loads(json_text):
137 result[entry['name']] = bundle
138
139 return result
Kuang-che Wu11713052019-05-30 16:21:54 +0800140
141
Kuang-che Wu4fc0f1c2019-08-02 21:56:49 +0800142def run_test(opts, bundle):
Kuang-che Wu32f27242019-05-16 17:34:50 +0800143 """Runs an autotest test.
144
145 Args:
146 opts: An argparse.Namespace to hold command line arguments.
Kuang-che Wu4fc0f1c2019-08-02 21:56:49 +0800147 bundle: tast's test bundle
Kuang-che Wu32f27242019-05-16 17:34:50 +0800148
149 Returns:
150 path of test result (outside chroot)
151 """
152 # Set results dir inside source tree, so it's easier to access them outside
153 # chroot.
154 results_dir = os.path.join(cros_util.chromeos_root_inside_chroot,
155 'tmp/tast_results_tmp')
Kuang-che Wu6c51d302019-06-19 09:07:20 +0800156 results_dir_output_chroot = results_dir.replace(
157 cros_util.chromeos_root_inside_chroot, opts.chromeos_root)
158 # Don't reuse existing results dir, otherwise tast may rename output files.
159 if os.path.exists(results_dir_output_chroot):
160 shutil.rmtree(results_dir_output_chroot)
161
Kuang-che Wu32f27242019-05-16 17:34:50 +0800162 # TODO(kcwu): add -timeout
Kuang-che Wu5963ebf2020-10-21 09:01:04 +0800163 flags = ['-resultsdir', results_dir]
164 if opts.tast_build:
165 flags.append('-buildbundle=' + bundle)
166 cmd = tast_cmd(opts, 'run', *flags, opts.dut, opts.test_name)
Kuang-che Wu34a67542019-09-09 18:01:05 +0800167 cros_util.cros_sdk(opts.chromeos_root, *cmd)
Kuang-che Wu32f27242019-05-16 17:34:50 +0800168
Kuang-che Wu6c51d302019-06-19 09:07:20 +0800169 return results_dir_output_chroot
Kuang-che Wu32f27242019-05-16 17:34:50 +0800170
171
172def gather_test_result(opts, result_dir):
173 error_path = os.path.join(result_dir, 'run_error.txt')
174 if os.path.exists(error_path):
Kuang-che Wua5723492019-11-25 20:59:34 +0800175 with open(error_path) as f:
176 message = f.read()
Kuang-che Wu32f27242019-05-16 17:34:50 +0800177 raise Exception('tast global error: %s' % message)
178
179 results_path = os.path.join(result_dir, 'results.json')
180 passed = None
Kuang-che Wu74bcb642020-02-20 18:45:53 +0800181 with open(results_path) as f:
182 for result in json.load(f):
183 if result['name'] != opts.test_name:
184 logger.warning('unexpected test ran: %s', result['name'])
185 continue
186 passed = result['errors'] is None
Kuang-che Wu32f27242019-05-16 17:34:50 +0800187 if passed is None:
188 raise Exception('no test result for "%s"?' % opts.test_name)
189
190 values = []
191 if opts.metric:
192 chart_path = os.path.join(result_dir, 'tests', opts.test_name,
193 'results-chart.json')
194 values = catapult_util.get_benchmark_values(chart_path, opts.metric)
195
196 return passed, values
197
198
Kuang-che Wufe1e88a2019-09-10 21:52:25 +0800199@cli.fatal_error_handler
Kuang-che Wu32f27242019-05-16 17:34:50 +0800200def main(args=None):
201 common.init()
202 parser = create_argument_parser()
203 opts = parser.parse_args(args)
204 common.config_logging(opts)
205
206 if not cros_util.is_dut(opts.dut):
207 logger.error('%r is not a valid DUT address', opts.dut)
208 return FATAL
209
Kuang-che Wuc092bd52021-01-05 14:25:56 +0800210 # Private bundles are not included in the OS image, make sure they are
211 # available.
212 if opts.with_private_bundles and not opts.tast_build:
213 if not cros_util.query_dut_is_by_official_builder(opts.dut):
214 # Official builders uploaded private tast bundles and tast can download
215 # them with -downloadprivatebundles=true, so --tast_build is not
216 # required. Otherwise, we must build the bundles by ourselves.
Kuang-che Wu4fc0f1c2019-08-02 21:56:49 +0800217 logger.error(
218 'for non-official chromeos image, --tast_build must be specified')
219 return FATAL
220
Kuang-che Wu32f27242019-05-16 17:34:50 +0800221 # Verify command line options.
222 if opts.metric:
223 if opts.fail_to_pass:
224 logger.error('--fail_to_pass is not for benchmark test (--metric)')
225 return FATAL
226 # Remove the "tast." prefix prepended by autotest.
227 opts.test_name = re.sub(r'^tast\.', '', opts.test_name)
228
Zheng-Jie Chang6dc5fe02019-11-19 15:58:27 +0800229 try:
230 tast_bundle_info = get_tast_bundle_info(opts, opts.test_name)
231 if not tast_bundle_info:
232 tast_bundle_info = get_tast_bundle_info(opts)
233 util.show_similar_candidates('test name', opts.test_name,
234 list(tast_bundle_info))
235 return FATAL
236 except subprocess.CalledProcessError:
237 logger.exception(
238 'failed to get tast bundle info, assume it is temporary; SKIP')
239 return SKIP
Kuang-che Wu4fc0f1c2019-08-02 21:56:49 +0800240
241 if len(tast_bundle_info) != 1 or opts.test_name not in tast_bundle_info:
242 # For example, tast in chroot after 12205.0.0 is IPC incompatible with
243 # tast on DUT earlier than 12028.0.0 (crbug/932307)
244 logger.fatal('"tast list" returns unexpected tests; '
245 'incompatible tast on DUT and in chroot?')
Kuang-che Wu11713052019-05-30 16:21:54 +0800246 return FATAL
247
Kuang-che Wu32f27242019-05-16 17:34:50 +0800248 try:
249 prepare_to_run_test(opts)
250 except Exception:
251 logger.exception('failed when prepare, assume it is temporary; SKIP')
252 return SKIP
253
Kuang-che Wu4fc0f1c2019-08-02 21:56:49 +0800254 bundle = tast_bundle_info[opts.test_name]
Kuang-che Wu34a67542019-09-09 18:01:05 +0800255 try:
256 result_dir = run_test(opts, bundle)
257 except subprocess.CalledProcessError:
258 logger.error('failed to run tast; maybe build, ssh, or setup failures')
259 return SKIP
Kuang-che Wu32f27242019-05-16 17:34:50 +0800260
261 try:
262 passed, values = gather_test_result(opts, result_dir)
263 except Exception:
264 logger.exception('failed to parse test result')
265 return FATAL
266
267 if opts.metric:
268 if not values:
269 logger.warning('no values found; SKIP')
270 return SKIP
271
Kuang-che Wuc89f2a22019-11-26 15:30:50 +0800272 print('BISECT_RESULT_VALUES=', ' '.join(str(v) for v in values))
Kuang-che Wu32f27242019-05-16 17:34:50 +0800273 logger.info('values=%s', values)
274 # The exit code doesn't matter.
275 return OLD
Kuang-che Wu084eef22020-03-11 18:29:48 +0800276
277 if opts.fail_to_pass:
278 if passed:
279 logger.info('passed')
Kuang-che Wu32f27242019-05-16 17:34:50 +0800280 return NEW
Kuang-che Wu084eef22020-03-11 18:29:48 +0800281 logger.info('failed')
282 return OLD
283 if passed:
284 logger.info('passed')
285 return OLD
286 logger.info('failed')
287 return NEW
Kuang-che Wu32f27242019-05-16 17:34:50 +0800288
289
290if __name__ == '__main__':
291 sys.exit(EXIT_CODE_MAP[main()])