blob: a404503ae509b3bb9384a7c4424ab38fad9b3aa8 [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(
57 '--tast_build',
58 action='store_true',
59 help='Build tast test bundle (-build=true) if specified; '
60 'default is using prebuilt bundle on the DUT')
61 parser.add_argument(
Kuang-che Wu4fc0f1c2019-08-02 21:56:49 +080062 '--with_private_bundles',
63 action='store_true',
64 help='Whether search tests in private bundles or not')
65 parser.add_argument(
Kuang-che Wu32f27242019-05-16 17:34:50 +080066 '--reboot_before_test',
67 action='store_true',
68 help='Reboot before test run')
69
70 group = parser.add_argument_group(title='Options for normal autotest tests')
71 group.add_argument(
72 '--test_name',
73 required=True,
74 help='Test name, like "video_VideoDecodeAccelerator.h264"')
75 group.add_argument(
76 '--fail_to_pass',
77 action='store_true',
78 help='For functional tests: old behavior is FAIL and new behavior is '
79 'PASS; If not specified, default = old behavior is PASS and new '
80 'behavior is FAIL')
81 group.add_argument(
82 '--metric',
Kuang-che Wud1b74152020-05-20 08:46:46 +080083 help='Metric name of performance test; example: '
84 '"cheets_SystemRawImageSize"')
Kuang-che Wu32f27242019-05-16 17:34:50 +080085
86 return parser
87
88
89def prepare_to_run_test(opts):
90 if opts.reboot_before_test:
Kuang-che Wu2ac9a922020-09-03 16:50:12 +080091 cros_util.reboot(
92 opts.dut, force_reboot_callback=cros_lab_util.reboot_via_servo)
Kuang-che Wu32f27242019-05-16 17:34:50 +080093
94
Kuang-che Wu4fc0f1c2019-08-02 21:56:49 +080095def get_tast_bundle_info(opts, pattern=None):
96 bundles = ['cros']
97 flags = ['-json']
Zheng-Jie Chang181be6f2020-03-17 16:16:08 +080098 # TODO(zjchang): ensure the tast version for buildbucket builds is correct
Kuang-che Wu11713052019-05-30 16:21:54 +080099 if not opts.tast_build:
Kuang-che Wu4fc0f1c2019-08-02 21:56:49 +0800100 flags.append('-build=false')
101 if opts.with_private_bundles:
102 flags.append('-downloadprivatebundles=true')
103 else:
104 if opts.with_private_bundles:
105 bundles.append('crosint')
106
107 args = [opts.dut]
Kuang-che Wu11713052019-05-30 16:21:54 +0800108 if pattern:
Kuang-che Wu4fc0f1c2019-08-02 21:56:49 +0800109 args.append(pattern)
110
111 result = {}
112 for bundle in bundles:
113 cmd = ['tast', 'list'] + flags + ['-buildbundle=' + bundle] + args
Kuang-che Wubcafc552019-08-15 15:27:02 +0800114 json_text = cros_util.cros_sdk(opts.chromeos_root, *cmd, log_stdout=False)
Kuang-che Wu4fc0f1c2019-08-02 21:56:49 +0800115 for entry in json.loads(json_text):
116 result[entry['name']] = bundle
117
118 return result
Kuang-che Wu11713052019-05-30 16:21:54 +0800119
120
Kuang-che Wu4fc0f1c2019-08-02 21:56:49 +0800121def run_test(opts, bundle):
Kuang-che Wu32f27242019-05-16 17:34:50 +0800122 """Runs an autotest test.
123
124 Args:
125 opts: An argparse.Namespace to hold command line arguments.
Kuang-che Wu4fc0f1c2019-08-02 21:56:49 +0800126 bundle: tast's test bundle
Kuang-che Wu32f27242019-05-16 17:34:50 +0800127
128 Returns:
129 path of test result (outside chroot)
130 """
131 # Set results dir inside source tree, so it's easier to access them outside
132 # chroot.
133 results_dir = os.path.join(cros_util.chromeos_root_inside_chroot,
134 'tmp/tast_results_tmp')
Kuang-che Wu6c51d302019-06-19 09:07:20 +0800135 results_dir_output_chroot = results_dir.replace(
136 cros_util.chromeos_root_inside_chroot, opts.chromeos_root)
137 # Don't reuse existing results dir, otherwise tast may rename output files.
138 if os.path.exists(results_dir_output_chroot):
139 shutil.rmtree(results_dir_output_chroot)
140
Kuang-che Wu32f27242019-05-16 17:34:50 +0800141 # TODO(kcwu): add -timeout
142 cmd = ['tast', '-verbose', 'run']
143 if not opts.tast_build:
144 cmd.append('-build=false')
Kuang-che Wu4fc0f1c2019-08-02 21:56:49 +0800145 if opts.with_private_bundles:
146 cmd.append('-downloadprivatebundles=true')
147 else:
148 cmd.append('-buildbundle=' + bundle)
Kuang-che Wu32f27242019-05-16 17:34:50 +0800149 cmd += ['-resultsdir', results_dir, opts.dut, opts.test_name]
150
Kuang-che Wu34a67542019-09-09 18:01:05 +0800151 cros_util.cros_sdk(opts.chromeos_root, *cmd)
Kuang-che Wu32f27242019-05-16 17:34:50 +0800152
Kuang-che Wu6c51d302019-06-19 09:07:20 +0800153 return results_dir_output_chroot
Kuang-che Wu32f27242019-05-16 17:34:50 +0800154
155
156def gather_test_result(opts, result_dir):
157 error_path = os.path.join(result_dir, 'run_error.txt')
158 if os.path.exists(error_path):
Kuang-che Wua5723492019-11-25 20:59:34 +0800159 with open(error_path) as f:
160 message = f.read()
Kuang-che Wu32f27242019-05-16 17:34:50 +0800161 raise Exception('tast global error: %s' % message)
162
163 results_path = os.path.join(result_dir, 'results.json')
164 passed = None
Kuang-che Wu74bcb642020-02-20 18:45:53 +0800165 with open(results_path) as f:
166 for result in json.load(f):
167 if result['name'] != opts.test_name:
168 logger.warning('unexpected test ran: %s', result['name'])
169 continue
170 passed = result['errors'] is None
Kuang-che Wu32f27242019-05-16 17:34:50 +0800171 if passed is None:
172 raise Exception('no test result for "%s"?' % opts.test_name)
173
174 values = []
175 if opts.metric:
176 chart_path = os.path.join(result_dir, 'tests', opts.test_name,
177 'results-chart.json')
178 values = catapult_util.get_benchmark_values(chart_path, opts.metric)
179
180 return passed, values
181
182
Kuang-che Wufe1e88a2019-09-10 21:52:25 +0800183@cli.fatal_error_handler
Kuang-che Wu32f27242019-05-16 17:34:50 +0800184def main(args=None):
185 common.init()
186 parser = create_argument_parser()
187 opts = parser.parse_args(args)
188 common.config_logging(opts)
189
190 if not cros_util.is_dut(opts.dut):
191 logger.error('%r is not a valid DUT address', opts.dut)
192 return FATAL
193
Zheng-Jie Chang181be6f2020-03-17 16:16:08 +0800194 by_official_builder = cros_util.query_dut_is_by_official_builder(opts.dut)
195 if (opts.with_private_bundles and not opts.tast_build and
196 not by_official_builder):
Kuang-che Wu4fc0f1c2019-08-02 21:56:49 +0800197 version = cros_util.query_dut_short_version(opts.dut)
198 if cros_util.is_cros_localbuild_version(version):
199 logger.error(
200 'for non-official chromeos image, --tast_build must be specified')
201 return FATAL
202
Kuang-che Wu32f27242019-05-16 17:34:50 +0800203 # Verify command line options.
204 if opts.metric:
205 if opts.fail_to_pass:
206 logger.error('--fail_to_pass is not for benchmark test (--metric)')
207 return FATAL
208 # Remove the "tast." prefix prepended by autotest.
209 opts.test_name = re.sub(r'^tast\.', '', opts.test_name)
210
Zheng-Jie Chang6dc5fe02019-11-19 15:58:27 +0800211 try:
212 tast_bundle_info = get_tast_bundle_info(opts, opts.test_name)
213 if not tast_bundle_info:
214 tast_bundle_info = get_tast_bundle_info(opts)
215 util.show_similar_candidates('test name', opts.test_name,
216 list(tast_bundle_info))
217 return FATAL
218 except subprocess.CalledProcessError:
219 logger.exception(
220 'failed to get tast bundle info, assume it is temporary; SKIP')
221 return SKIP
Kuang-che Wu4fc0f1c2019-08-02 21:56:49 +0800222
223 if len(tast_bundle_info) != 1 or opts.test_name not in tast_bundle_info:
224 # For example, tast in chroot after 12205.0.0 is IPC incompatible with
225 # tast on DUT earlier than 12028.0.0 (crbug/932307)
226 logger.fatal('"tast list" returns unexpected tests; '
227 'incompatible tast on DUT and in chroot?')
Kuang-che Wu11713052019-05-30 16:21:54 +0800228 return FATAL
229
Kuang-che Wu32f27242019-05-16 17:34:50 +0800230 try:
231 prepare_to_run_test(opts)
232 except Exception:
233 logger.exception('failed when prepare, assume it is temporary; SKIP')
234 return SKIP
235
Kuang-che Wu4fc0f1c2019-08-02 21:56:49 +0800236 bundle = tast_bundle_info[opts.test_name]
Kuang-che Wu34a67542019-09-09 18:01:05 +0800237 try:
238 result_dir = run_test(opts, bundle)
239 except subprocess.CalledProcessError:
240 logger.error('failed to run tast; maybe build, ssh, or setup failures')
241 return SKIP
Kuang-che Wu32f27242019-05-16 17:34:50 +0800242
243 try:
244 passed, values = gather_test_result(opts, result_dir)
245 except Exception:
246 logger.exception('failed to parse test result')
247 return FATAL
248
249 if opts.metric:
250 if not values:
251 logger.warning('no values found; SKIP')
252 return SKIP
253
Kuang-che Wuc89f2a22019-11-26 15:30:50 +0800254 print('BISECT_RESULT_VALUES=', ' '.join(str(v) for v in values))
Kuang-che Wu32f27242019-05-16 17:34:50 +0800255 logger.info('values=%s', values)
256 # The exit code doesn't matter.
257 return OLD
Kuang-che Wu084eef22020-03-11 18:29:48 +0800258
259 if opts.fail_to_pass:
260 if passed:
261 logger.info('passed')
Kuang-che Wu32f27242019-05-16 17:34:50 +0800262 return NEW
Kuang-che Wu084eef22020-03-11 18:29:48 +0800263 logger.info('failed')
264 return OLD
265 if passed:
266 logger.info('passed')
267 return OLD
268 logger.info('failed')
269 return NEW
Kuang-che Wu32f27242019-05-16 17:34:50 +0800270
271
272if __name__ == '__main__':
273 sys.exit(EXIT_CODE_MAP[main()])