blob: d970a42be3ad81c39db69ae96f49da1d700bbd78 [file] [log] [blame]
Kuang-che Wu1fcc0222018-07-07 16:43:22 +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"""Diagnose ChromeOS autotest regressions.
7
8This is integrated bisection utility. Given ChromeOS, Chrome, Android source
Kuang-che Wu927231f2018-07-24 14:21:56 +08009tree, and necessary parameters, this script can determine which components to
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080010bisect, and hopefully output the culprit CL of regression.
11
12Sometimes the script failed to figure out the final CL for various reasons, it
13will cut down the search range as narrow as it can.
14"""
15from __future__ import print_function
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080016import fnmatch
17import glob
18import logging
19import os
20
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080021from bisect_kit import cros_lab_util
22from bisect_kit import cros_util
23from bisect_kit import diagnoser_cros
Kuang-che Wue121fae2018-11-09 16:18:39 +080024from bisect_kit import errors
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080025from bisect_kit import util
Kuang-che Wud8fc9572018-10-03 21:00:41 +080026import setup_cros_bisect
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080027
28logger = logging.getLogger(__name__)
29
30# What chrome binaries to build for given autotest.
31# This dict is created manually by inspecting output of
32# 'grep -r ChromeBinaryTest autotest/files/client/site_tests'
33# If you change this dict, build_and_deploy_chrome_helper.sh may need update
34# as well.
35CHROME_BINARIES_OF_TEST = {
36 'graphics_Chrome.ozone_gl_unittests': ['ozone_gl_unittests'],
37 'security_SandboxLinuxUnittests': ['sandbox_linux_unittests'],
38 'video_HangoutHardwarePerf*': [
39 'video_decode_accelerator_unittest',
40 'video_encode_accelerator_unittest',
41 ],
42 'video_JDAPerf*': ['jpeg_decode_accelerator_unittest'],
43 'video_JEAPerf': ['jpeg_encode_accelerator_unittest'],
44 'video_JpegDecodeAccelerator': ['jpeg_decode_accelerator_unittest'],
45 'video_JpegEncodeAccelerator': ['jpeg_encode_accelerator_unittest'],
Kuang-che Wu17cdeb52019-01-30 17:58:46 +080046 'video_VDAPerf*': ['video_decode_accelerator_unittest'],
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080047 'video_VDASanity': ['video_decode_accelerator_unittest'],
Kuang-che Wu17cdeb52019-01-30 17:58:46 +080048 'video_VEAPerf*': ['video_encode_accelerator_unittest'],
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080049 'video_VideoDecodeAccelerator*': ['video_decode_accelerator_unittest'],
50 'video_VideoEncodeAccelerator*': ['video_encode_accelerator_unittest'],
51}
52
53
Kuang-che Wu22aa9d42019-01-25 10:35:33 +080054def get_test_dependency_labels(config):
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080055 # Assume "DEPENDENCIES" is identical between the period of
56 # `old` and `new` version.
Kuang-che Wu99e808f2019-06-26 12:17:32 +080057 autotest_dir = os.path.join(config['chromeos_root'], cros_util.autotest_path)
Kuang-che Wua41525a2018-10-17 23:52:24 +080058 info = cros_util.get_autotest_test_info(autotest_dir, config['test_name'])
Kuang-che Wu0f1c3b02019-01-10 01:21:01 +080059 assert info, 'incorrect test name? %s' % config['test_name']
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080060
Kuang-che Wu22aa9d42019-01-25 10:35:33 +080061 extra_labels = []
62 dependencies = info.variables.get('DEPENDENCIES', '')
63 for label in dependencies.split(','):
64 label = label.strip()
65 # Skip non-machine labels
66 if not label or label in ['cleanup-reboot']:
67 continue
68 extra_labels.append(label)
69
70 return extra_labels
71
72
73def grab_dut(config):
74 reason = cros_lab_util.make_lock_reason(config['session'])
Kuang-che Wuf65c61d2018-10-19 17:48:30 +080075 if config.get('allocated_dut'):
76 host_name = cros_lab_util.dut_host_name(config['allocated_dut'])
Kuang-che Wu22aa9d42019-01-25 10:35:33 +080077 logger.info('try to lock the same host (%s) as last run', host_name)
78 candidates = cros_lab_util.list_host(host=host_name).values()
Kuang-che Wuf65c61d2018-10-19 17:48:30 +080079 else:
Kuang-che Wu22aa9d42019-01-25 10:35:33 +080080 extra_labels = get_test_dependency_labels(config)
81 candidates = cros_lab_util.seek_host(
82 pools=config.get('pools', '').split(','),
Kuang-che Wuf65c61d2018-10-19 17:48:30 +080083 model=config['model'],
84 sku=config['sku'],
85 extra_labels=extra_labels)
86
Kuang-che Wu22aa9d42019-01-25 10:35:33 +080087 if (not candidates or candidates[0]['Status'] != 'Ready' or
88 candidates[0]['Locked']):
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080089 logger.error('unable to allocate dut')
90 return None
91
Kuang-che Wu22aa9d42019-01-25 10:35:33 +080092 host_name = candidates[0]['Host']
93 info = cros_lab_util.lock_host(host_name, reason)
94 if info['Status'] != 'Ready':
95 cros_lab_util.unlock_host(host_name)
96 raise Exception(
97 'unexpected host status=%s, a race condition?' % info['Status'])
98
99 logger.info('allocated host %s', host_name)
100 return host_name
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800101
102
103def may_depend_on_extra_chrome_binaries(autotest_dir, test_name):
104 info = cros_util.get_autotest_test_info(autotest_dir, test_name)
Kuang-che Wu0f1c3b02019-01-10 01:21:01 +0800105 assert info, 'incorrect test name? %s' % test_name
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800106 dirpath = os.path.dirname(info.path)
107 for pypath in glob.glob(os.path.join(dirpath, '*.py')):
108 if 'ChromeBinaryTest' in open(pypath).read():
109 return True
110 return False
111
112
Kuang-che Wua41525a2018-10-17 23:52:24 +0800113def determine_chrome_binaries(chromeos_root, test_name):
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800114 chrome_binaries = None
115 for name_pattern, binaries in CHROME_BINARIES_OF_TEST.items():
Kuang-che Wua41525a2018-10-17 23:52:24 +0800116 if fnmatch.fnmatch(test_name, name_pattern):
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800117 chrome_binaries = binaries
118 break
119
Kuang-che Wu99e808f2019-06-26 12:17:32 +0800120 autotest_dir = os.path.join(chromeos_root, cros_util.autotest_path)
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800121 if chrome_binaries:
122 logger.info('This test depends on chrome binary: %s', chrome_binaries)
Kuang-che Wua41525a2018-10-17 23:52:24 +0800123 elif may_depend_on_extra_chrome_binaries(autotest_dir, test_name):
Kuang-che Wuaf7b06b2019-01-30 18:50:05 +0800124 raise errors.InternalError(
Kuang-che Wu74768d32018-09-07 12:03:24 +0800125 '%s code used ChromeBinaryTest but the binary is unknown; '
Kuang-che Wuaf7b06b2019-01-30 18:50:05 +0800126 'please update CHROME_BINARIES_OF_TEST table' % test_name)
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800127 return chrome_binaries
128
129
Kuang-che Wu32f27242019-05-16 17:34:50 +0800130class DiagnoseAutotestCommandLine(diagnoser_cros.DiagnoseCommandLineBase):
Kuang-che Wua41525a2018-10-17 23:52:24 +0800131 """Diagnose command line interface."""
Kuang-che Wud8fc9572018-10-03 21:00:41 +0800132
Kuang-che Wua41525a2018-10-17 23:52:24 +0800133 def check_options(self, opts, path_factory):
Kuang-che Wu32f27242019-05-16 17:34:50 +0800134 super(DiagnoseAutotestCommandLine, self).check_options(opts, path_factory)
Kuang-che Wua41525a2018-10-17 23:52:24 +0800135
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800136 is_cts = (
137 opts.cts_revision or opts.cts_abi or opts.cts_prefix or
138 opts.cts_module or opts.cts_test or opts.cts_timeout)
139 if is_cts:
140 if opts.test_name or opts.metric or opts.args:
141 self.argument_parser.error(
142 'do not specify --test_name, --metric, --args for CTS/GTS tests')
Kuang-che Wub91b1512019-05-29 15:16:29 +0800143 if not opts.cts_prefix:
144 self.argument_parser.error(
145 '--cts_prefix should be specified for CTS/GTS tests')
146 if not opts.cts_module:
147 self.argument_parser.error(
148 '--cts_module should be specified for CTS/GTS tests')
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800149 opts.test_name = '%s.tradefed-run-test' % opts.cts_prefix
Kuang-che Wu32f27242019-05-16 17:34:50 +0800150 elif not opts.test_name:
151 self.argument_parser.error(
152 '--test_name should be specified if not CTS/GTS tests')
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800153
Kuang-che Wu32f27242019-05-16 17:34:50 +0800154 def init_hook(self, opts):
155 self.states.config.update(
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800156 cts_revision=opts.cts_revision,
157 cts_abi=opts.cts_abi,
158 cts_prefix=opts.cts_prefix,
159 cts_module=opts.cts_module,
160 cts_test=opts.cts_test,
161 cts_timeout=opts.cts_timeout,
Kuang-che Wua41525a2018-10-17 23:52:24 +0800162 test_that_args=opts.args,
Kuang-che Wua41525a2018-10-17 23:52:24 +0800163 )
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800164
Kuang-che Wua41525a2018-10-17 23:52:24 +0800165 def _build_cmds(self):
166 # prebuilt version will be specified later.
Kuang-che Wu99e808f2019-06-26 12:17:32 +0800167 cros_switch_cmd = [
168 './switch_cros_localbuild.py',
169 '--chromeos_root',
170 self.config['chromeos_root'],
171 '--chromeos_mirror',
172 self.config['chromeos_mirror'],
173 '--board',
174 self.config['board'],
175 '--nobuild',
176 self.config['dut'],
177 ]
178 autotest_switch_cmd = [
Kuang-che Wua41525a2018-10-17 23:52:24 +0800179 './switch_autotest_prebuilt.py',
Kuang-che Wu99e808f2019-06-26 12:17:32 +0800180 '--chromeos_root',
181 self.config['chromeos_root'],
182 '--board',
183 self.config['board'],
184 ]
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800185 if self.config['test_name'] and not self.config['cts_test']:
Kuang-che Wu99e808f2019-06-26 12:17:32 +0800186 autotest_switch_cmd += ['--test_name', self.config['test_name']]
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800187
Kuang-che Wua41525a2018-10-17 23:52:24 +0800188 common_eval_cmd = [
189 './eval_cros_autotest.py',
190 '--chromeos_root', self.config['chromeos_root'],
Kuang-che Wua41525a2018-10-17 23:52:24 +0800191 ] # yapf: disable
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800192 if self.config['test_name'] and not self.config['cts_test']:
193 common_eval_cmd += ['--test_name', self.config['test_name']]
Kuang-che Wua41525a2018-10-17 23:52:24 +0800194 if self.config['metric']:
195 common_eval_cmd += [
196 '--metric', self.config['metric'],
Kuang-che Wua41525a2018-10-17 23:52:24 +0800197 ] # yapf: disable
Kuang-che Wu32f27242019-05-16 17:34:50 +0800198 if self.config['fail_to_pass']:
Kuang-che Wu0a4304a2019-01-19 01:32:11 +0800199 common_eval_cmd.append('--fail_to_pass')
Kuang-che Wuda3abfe2019-03-21 14:48:12 +0800200 if self.config['reboot_before_test']:
201 common_eval_cmd.append('--reboot_before_test')
Kuang-che Wua41525a2018-10-17 23:52:24 +0800202 if self.config['test_that_args']:
203 common_eval_cmd += ['--args', self.config['test_that_args']]
Kuang-che Wud4603d72018-11-29 17:51:21 +0800204 if self.config['test_name'].startswith('telemetry_'):
205 common_eval_cmd += ['--chrome_root', self.config['chrome_root']]
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800206
207 for arg_name in [
208 'cts_revision', 'cts_abi', 'cts_prefix', 'cts_module', 'cts_test',
209 'cts_timeout'
210 ]:
211 if self.config.get(arg_name) is not None:
212 common_eval_cmd += ['--%s' % arg_name, str(self.config[arg_name])]
Kuang-che Wu958d7d02019-05-30 17:58:21 +0800213 if self.config.get('cts_runonce'):
214 common_eval_cmd.append('--cts_runonce')
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800215
Kuang-che Wu99e808f2019-06-26 12:17:32 +0800216 common_switch_cmds = [
217 cros_switch_cmd,
218 autotest_switch_cmd,
219 ]
220
221 return common_switch_cmds, common_eval_cmd
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800222
Kuang-che Wua41525a2018-10-17 23:52:24 +0800223 def cmd_run(self, opts):
224 del opts # unused
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800225
Kuang-che Wua41525a2018-10-17 23:52:24 +0800226 self.states.load()
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800227
Kuang-che Wu8b654092018-11-09 17:56:25 +0800228 try:
229 path_factory = setup_cros_bisect.DefaultProjectPathFactory(
230 self.config['mirror_base'], self.config['work_base'],
231 self.config['session'])
Kuang-che Wu99e808f2019-06-26 12:17:32 +0800232 common_switch_cmds, common_eval_cmd = self._build_cmds()
Kuang-che Wu8b654092018-11-09 17:56:25 +0800233
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800234 if self.config['test_name']:
235 chrome_binaries = determine_chrome_binaries(
236 self.config['chromeos_root'], self.config['test_name'])
237 else:
238 chrome_binaries = None
Kuang-che Wu8b654092018-11-09 17:56:25 +0800239
240 with cros_lab_util.dut_manager(self.config['dut'],
241 lambda: grab_dut(self.config)) as dut:
242 if not dut:
243 raise errors.NoDutAvailable('unable to allocate DUT')
Kuang-che Wu7cb08df2019-06-04 19:12:29 +0800244 if not cros_util.is_good_dut(dut):
245 if not cros_lab_util.repair(dut):
246 raise errors.ExternalError('Not a good DUT and unable to repair')
Kuang-che Wu8b654092018-11-09 17:56:25 +0800247 if self.config['dut'] == cros_lab_util.LAB_DUT:
248 self.config['allocated_dut'] = dut
249 self.states.save()
250 common_eval_cmd.append(dut)
251
Kuang-che Wu186071a2019-03-28 17:11:41 +0800252 diagnoser = diagnoser_cros.CrosDiagnoser(self.states, path_factory,
253 self.config, dut)
Kuang-che Wu8b654092018-11-09 17:56:25 +0800254
Kuang-che Wufd2ea5e2019-06-10 20:40:17 +0800255 eval_cmd = common_eval_cmd + ['--prebuilt']
Kuang-che Wu8b654092018-11-09 17:56:25 +0800256 # Do not specify version for autotest prebuilt switching here. The trick
257 # is that version number is obtained via bisector's environment variable
258 # CROS_VERSION.
Kuang-che Wu99e808f2019-06-26 12:17:32 +0800259 extra_switch_cmds = common_switch_cmds
Kuang-che Wu8b654092018-11-09 17:56:25 +0800260 diagnoser.narrow_down_chromeos_prebuilt(
261 self.config['old'],
262 self.config['new'],
263 eval_cmd,
Kuang-che Wu99e808f2019-06-26 12:17:32 +0800264 extra_switch_cmds=extra_switch_cmds)
Kuang-che Wu8b654092018-11-09 17:56:25 +0800265
266 diagnoser.switch_chromeos_to_old(force=self.config['always_reflash'])
Kuang-che Wu99e808f2019-06-26 12:17:32 +0800267 env = os.environ.copy()
268 env['CROS_VERSION'] = diagnoser.cros_old
269 for cmd in common_switch_cmds:
270 util.check_call(*cmd, env=env)
Kuang-che Wu8b654092018-11-09 17:56:25 +0800271
272 try:
273 if diagnoser.narrow_down_android(eval_cmd):
274 return
Kuang-che Wuec566f32019-03-07 16:53:32 +0800275 except errors.DiagnoseContradiction:
Kuang-che Wu8b654092018-11-09 17:56:25 +0800276 raise
277 except Exception:
278 logger.exception('exception in android bisector before verification; '
279 'assume culprit is not inside android and continue')
280 # Assume it's ok to leave random version of android prebuilt on DUT.
281
Kuang-che Wu8b654092018-11-09 17:56:25 +0800282 eval_cmd = common_eval_cmd + ['--prebuilt']
Kuang-che Wufd2ea5e2019-06-10 20:40:17 +0800283
284 if chrome_binaries:
285 # Now, the version of autotest on the DUT is unknown and may be even
286 # not installed. Invoke the test once here, so
287 # - make sure autotest-deps is installed, with expected version
288 # - autotest-deps is installed first, so our chrome_binaries
289 # won't be reset to default version during bisection.
290 # It's acceptable to spend extra time to run test once because
291 # - only few tests do so
292 # - tests are migrating away from autotest
293 util.call(*eval_cmd)
294
Kuang-che Wu8b654092018-11-09 17:56:25 +0800295 try:
296 if diagnoser.narrow_down_chrome(
Kuang-che Wu50d8ff42018-11-26 12:48:30 +0800297 eval_cmd, chrome_binaries=chrome_binaries):
Kuang-che Wu8b654092018-11-09 17:56:25 +0800298 return
Kuang-che Wuec566f32019-03-07 16:53:32 +0800299 except errors.DiagnoseContradiction:
Kuang-che Wu8b654092018-11-09 17:56:25 +0800300 raise
301 except Exception:
302 logger.exception('exception in chrome bisector before verification; '
303 'assume culprit is not inside chrome and continue')
304
Kuang-che Wufd2ea5e2019-06-10 20:40:17 +0800305 eval_cmd = common_eval_cmd
Kuang-che Wu8b654092018-11-09 17:56:25 +0800306 diagnoser.narrow_down_chromeos_localbuild(eval_cmd)
307 logger.info('%s done', __file__)
308 except Exception as e:
309 logger.exception('got exception; stop')
310 exception_name = e.__class__.__name__
311 self.states.add_history(
312 'failed', '%s: %s' % (exception_name, e), exception=exception_name)
313
Kuang-che Wu32f27242019-05-16 17:34:50 +0800314 def create_argument_parser_hook(self, parser_init):
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800315 group = parser_init.add_argument_group(title='Options for CTS/GTS tests')
316 group.add_argument('--cts_revision', help='CTS revision, like "9.0_r3"')
Kuang-che Wu63f836a2019-02-21 16:33:32 +0000317 group.add_argument('--cts_abi', choices=['arm', 'x86'])
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800318 group.add_argument(
319 '--cts_prefix',
320 help='Prefix of autotest test name, '
321 'like cheets_CTS_N, cheets_CTS_P, cheets_GTS')
322 group.add_argument(
323 '--cts_module', help='CTS/GTS module name, like "CtsCameraTestCases"')
324 group.add_argument(
325 '--cts_test',
326 help='CTS/GTS test name, like '
327 '"android.hardware.cts.CameraTest#testDisplayOrientation"')
328 group.add_argument('--cts_timeout', type=float, help='timeout, in seconds')
Kuang-che Wu958d7d02019-05-30 17:58:21 +0800329 group.add_argument(
330 '--cts_runonce', action='store_true', help='run test only once')
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800331
Kuang-che Wua41525a2018-10-17 23:52:24 +0800332 group = parser_init.add_argument_group(title='Options passed to test_that')
333 group.add_argument(
334 '--args',
335 help='Extra args passed to "test_that --args"; Overrides the default')
336
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800337
338if __name__ == '__main__':
Kuang-che Wu32f27242019-05-16 17:34:50 +0800339 DiagnoseAutotestCommandLine().main()