blob: 2edb2613ef19784cae5e7cab5c4aba617233d3f5 [file] [log] [blame]
Kuang-che Wu32f27242019-05-16 17:34:50 +08001#!/usr/bin/env python2
2# -*- 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
21from bisect_kit import cros_util
Kuang-che Wu11713052019-05-30 16:21:54 +080022from bisect_kit import util
Kuang-che Wu32f27242019-05-16 17:34:50 +080023
24logger = logging.getLogger(__name__)
25
26OLD = 'old'
27NEW = 'new'
28SKIP = 'skip'
29FATAL = 'fatal'
30
31EXIT_CODE_MAP = {
32 OLD: cli.EXIT_CODE_OLD,
33 NEW: cli.EXIT_CODE_NEW,
34 SKIP: cli.EXIT_CODE_SKIP,
35 FATAL: cli.EXIT_CODE_FATAL,
36}
37
38
39def create_argument_parser():
40 parser = argparse.ArgumentParser(description=__doc__)
41 common.add_common_arguments(parser)
42 parser.add_argument(
43 'dut',
44 nargs='?',
45 type=cli.argtype_notempty,
46 metavar='DUT',
47 default=configure.get('DUT', ''))
48 parser.add_argument(
49 '--chromeos_root',
50 type=cli.argtype_dir_path,
51 metavar='CHROMEOS_ROOT',
52 default=configure.get('CHROMEOS_ROOT', ''),
53 help='ChromeOS tree root')
54 parser.add_argument(
55 '--tast_build',
56 action='store_true',
57 help='Build tast test bundle (-build=true) if specified; '
58 'default is using prebuilt bundle on the DUT')
59 parser.add_argument(
Kuang-che Wu4fc0f1c2019-08-02 21:56:49 +080060 '--with_private_bundles',
61 action='store_true',
62 help='Whether search tests in private bundles or not')
63 parser.add_argument(
Kuang-che Wu32f27242019-05-16 17:34:50 +080064 '--reboot_before_test',
65 action='store_true',
66 help='Reboot before test run')
67
68 group = parser.add_argument_group(title='Options for normal autotest tests')
69 group.add_argument(
70 '--test_name',
71 required=True,
72 help='Test name, like "video_VideoDecodeAccelerator.h264"')
73 group.add_argument(
74 '--fail_to_pass',
75 action='store_true',
76 help='For functional tests: old behavior is FAIL and new behavior is '
77 'PASS; If not specified, default = old behavior is PASS and new '
78 'behavior is FAIL')
79 group.add_argument(
80 '--metric',
81 help=
82 'Metric name of performance test; example: "cheets_SystemRawImageSize"')
83
84 return parser
85
86
87def prepare_to_run_test(opts):
88 if opts.reboot_before_test:
89 cros_util.reboot(opts.dut)
90
91
Kuang-che Wu4fc0f1c2019-08-02 21:56:49 +080092def get_tast_bundle_info(opts, pattern=None):
93 bundles = ['cros']
94 flags = ['-json']
Kuang-che Wu11713052019-05-30 16:21:54 +080095 if not opts.tast_build:
Kuang-che Wu4fc0f1c2019-08-02 21:56:49 +080096 flags.append('-build=false')
97 if opts.with_private_bundles:
98 flags.append('-downloadprivatebundles=true')
99 else:
100 if opts.with_private_bundles:
101 bundles.append('crosint')
102
103 args = [opts.dut]
Kuang-che Wu11713052019-05-30 16:21:54 +0800104 if pattern:
Kuang-che Wu4fc0f1c2019-08-02 21:56:49 +0800105 args.append(pattern)
106
107 result = {}
108 for bundle in bundles:
109 cmd = ['tast', 'list'] + flags + ['-buildbundle=' + bundle] + args
Kuang-che Wubcafc552019-08-15 15:27:02 +0800110 json_text = cros_util.cros_sdk(opts.chromeos_root, *cmd, log_stdout=False)
Kuang-che Wu4fc0f1c2019-08-02 21:56:49 +0800111 for entry in json.loads(json_text):
112 result[entry['name']] = bundle
113
114 return result
Kuang-che Wu11713052019-05-30 16:21:54 +0800115
116
Kuang-che Wu4fc0f1c2019-08-02 21:56:49 +0800117def run_test(opts, bundle):
Kuang-che Wu32f27242019-05-16 17:34:50 +0800118 """Runs an autotest test.
119
120 Args:
121 opts: An argparse.Namespace to hold command line arguments.
Kuang-che Wu4fc0f1c2019-08-02 21:56:49 +0800122 bundle: tast's test bundle
Kuang-che Wu32f27242019-05-16 17:34:50 +0800123
124 Returns:
125 path of test result (outside chroot)
126 """
127 # Set results dir inside source tree, so it's easier to access them outside
128 # chroot.
129 results_dir = os.path.join(cros_util.chromeos_root_inside_chroot,
130 'tmp/tast_results_tmp')
Kuang-che Wu6c51d302019-06-19 09:07:20 +0800131 results_dir_output_chroot = results_dir.replace(
132 cros_util.chromeos_root_inside_chroot, opts.chromeos_root)
133 # Don't reuse existing results dir, otherwise tast may rename output files.
134 if os.path.exists(results_dir_output_chroot):
135 shutil.rmtree(results_dir_output_chroot)
136
Kuang-che Wu32f27242019-05-16 17:34:50 +0800137 # TODO(kcwu): add -timeout
138 cmd = ['tast', '-verbose', 'run']
139 if not opts.tast_build:
140 cmd.append('-build=false')
Kuang-che Wu4fc0f1c2019-08-02 21:56:49 +0800141 if opts.with_private_bundles:
142 cmd.append('-downloadprivatebundles=true')
143 else:
144 cmd.append('-buildbundle=' + bundle)
Kuang-che Wu32f27242019-05-16 17:34:50 +0800145 cmd += ['-resultsdir', results_dir, opts.dut, opts.test_name]
146
Kuang-che Wu34a67542019-09-09 18:01:05 +0800147 cros_util.cros_sdk(opts.chromeos_root, *cmd)
Kuang-che Wu32f27242019-05-16 17:34:50 +0800148
Kuang-che Wu6c51d302019-06-19 09:07:20 +0800149 return results_dir_output_chroot
Kuang-che Wu32f27242019-05-16 17:34:50 +0800150
151
152def gather_test_result(opts, result_dir):
153 error_path = os.path.join(result_dir, 'run_error.txt')
154 if os.path.exists(error_path):
155 message = open(error_path).read()
156 raise Exception('tast global error: %s' % message)
157
158 results_path = os.path.join(result_dir, 'results.json')
159 passed = None
160 for result in json.load(open(results_path)):
161 if result['name'] != opts.test_name:
162 logger.warning('unexpected test ran: %s', result['name'])
163 continue
164 passed = result['errors'] is None
165 if passed is None:
166 raise Exception('no test result for "%s"?' % opts.test_name)
167
168 values = []
169 if opts.metric:
170 chart_path = os.path.join(result_dir, 'tests', opts.test_name,
171 'results-chart.json')
172 values = catapult_util.get_benchmark_values(chart_path, opts.metric)
173
174 return passed, values
175
176
177def main(args=None):
178 common.init()
179 parser = create_argument_parser()
180 opts = parser.parse_args(args)
181 common.config_logging(opts)
182
183 if not cros_util.is_dut(opts.dut):
184 logger.error('%r is not a valid DUT address', opts.dut)
185 return FATAL
186
Kuang-che Wu4fc0f1c2019-08-02 21:56:49 +0800187 if opts.with_private_bundles and not opts.tast_build:
188 version = cros_util.query_dut_short_version(opts.dut)
189 if cros_util.is_cros_localbuild_version(version):
190 logger.error(
191 'for non-official chromeos image, --tast_build must be specified')
192 return FATAL
193
Kuang-che Wu32f27242019-05-16 17:34:50 +0800194 # Verify command line options.
195 if opts.metric:
196 if opts.fail_to_pass:
197 logger.error('--fail_to_pass is not for benchmark test (--metric)')
198 return FATAL
199 # Remove the "tast." prefix prepended by autotest.
200 opts.test_name = re.sub(r'^tast\.', '', opts.test_name)
201
Kuang-che Wu4fc0f1c2019-08-02 21:56:49 +0800202 tast_bundle_info = get_tast_bundle_info(opts, opts.test_name)
203 if not tast_bundle_info:
204 tast_bundle_info = get_tast_bundle_info(opts)
205 util.show_similar_candidates('test name', opts.test_name,
206 list(tast_bundle_info))
207 return FATAL
208
209 if len(tast_bundle_info) != 1 or opts.test_name not in tast_bundle_info:
210 # For example, tast in chroot after 12205.0.0 is IPC incompatible with
211 # tast on DUT earlier than 12028.0.0 (crbug/932307)
212 logger.fatal('"tast list" returns unexpected tests; '
213 'incompatible tast on DUT and in chroot?')
Kuang-che Wu11713052019-05-30 16:21:54 +0800214 return FATAL
215
Kuang-che Wu32f27242019-05-16 17:34:50 +0800216 try:
217 prepare_to_run_test(opts)
218 except Exception:
219 logger.exception('failed when prepare, assume it is temporary; SKIP')
220 return SKIP
221
Kuang-che Wu4fc0f1c2019-08-02 21:56:49 +0800222 bundle = tast_bundle_info[opts.test_name]
Kuang-che Wu34a67542019-09-09 18:01:05 +0800223 try:
224 result_dir = run_test(opts, bundle)
225 except subprocess.CalledProcessError:
226 logger.error('failed to run tast; maybe build, ssh, or setup failures')
227 return SKIP
Kuang-che Wu32f27242019-05-16 17:34:50 +0800228
229 try:
230 passed, values = gather_test_result(opts, result_dir)
231 except Exception:
232 logger.exception('failed to parse test result')
233 return FATAL
234
235 if opts.metric:
236 if not values:
237 logger.warning('no values found; SKIP')
238 return SKIP
239
240 print('BISECT_RESULT_VALUES=', ' '.join(map(str, values)))
241 logger.info('values=%s', values)
242 # The exit code doesn't matter.
243 return OLD
244 else:
245 if opts.fail_to_pass:
246 if passed:
247 logger.info('passed')
248 return NEW
249 logger.info('failed')
250 return OLD
251 else:
252 if passed:
253 logger.info('passed')
254 return OLD
255 logger.info('failed')
256 return NEW
257
258
259if __name__ == '__main__':
260 sys.exit(EXIT_CODE_MAP[main()])