blob: 008dfeaaf8319511eb4f953fe68c0b1d2f067b72 [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 Wu7f82c6f2019-08-12 14:29:28 +080057 autotest_dir = os.path.join(config['chromeos_root'],
58 cros_util.prebuilt_autotest_dir)
Kuang-che Wua41525a2018-10-17 23:52:24 +080059 info = cros_util.get_autotest_test_info(autotest_dir, config['test_name'])
Kuang-che Wu0f1c3b02019-01-10 01:21:01 +080060 assert info, 'incorrect test name? %s' % config['test_name']
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080061
Kuang-che Wu22aa9d42019-01-25 10:35:33 +080062 extra_labels = []
63 dependencies = info.variables.get('DEPENDENCIES', '')
64 for label in dependencies.split(','):
65 label = label.strip()
66 # Skip non-machine labels
67 if not label or label in ['cleanup-reboot']:
68 continue
69 extra_labels.append(label)
70
71 return extra_labels
72
73
74def grab_dut(config):
75 reason = cros_lab_util.make_lock_reason(config['session'])
Kuang-che Wuf65c61d2018-10-19 17:48:30 +080076 if config.get('allocated_dut'):
77 host_name = cros_lab_util.dut_host_name(config['allocated_dut'])
Kuang-che Wu22aa9d42019-01-25 10:35:33 +080078 logger.info('try to lock the same host (%s) as last run', host_name)
79 candidates = cros_lab_util.list_host(host=host_name).values()
Kuang-che Wuf65c61d2018-10-19 17:48:30 +080080 else:
Kuang-che Wu22aa9d42019-01-25 10:35:33 +080081 extra_labels = get_test_dependency_labels(config)
82 candidates = cros_lab_util.seek_host(
83 pools=config.get('pools', '').split(','),
Kuang-che Wuf65c61d2018-10-19 17:48:30 +080084 model=config['model'],
85 sku=config['sku'],
86 extra_labels=extra_labels)
87
Kuang-che Wu22aa9d42019-01-25 10:35:33 +080088 if (not candidates or candidates[0]['Status'] != 'Ready' or
89 candidates[0]['Locked']):
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080090 logger.error('unable to allocate dut')
91 return None
92
Kuang-che Wu22aa9d42019-01-25 10:35:33 +080093 host_name = candidates[0]['Host']
94 info = cros_lab_util.lock_host(host_name, reason)
95 if info['Status'] != 'Ready':
96 cros_lab_util.unlock_host(host_name)
97 raise Exception(
98 'unexpected host status=%s, a race condition?' % info['Status'])
99
100 logger.info('allocated host %s', host_name)
101 return host_name
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800102
103
104def may_depend_on_extra_chrome_binaries(autotest_dir, test_name):
105 info = cros_util.get_autotest_test_info(autotest_dir, test_name)
Kuang-che Wu0f1c3b02019-01-10 01:21:01 +0800106 assert info, 'incorrect test name? %s' % test_name
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800107 dirpath = os.path.dirname(info.path)
108 for pypath in glob.glob(os.path.join(dirpath, '*.py')):
Kuang-che Wu85ed87d2019-09-17 20:47:28 +0800109 content = open(pypath).read()
110 # Checking these two functions is more accurate than 'ChromeBinaryTest'
111 # (b/141164895).
112 if ('run_chrome_test_binary' in content or
113 'get_chrome_binary_path' in content):
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800114 return True
115 return False
116
117
Kuang-che Wua41525a2018-10-17 23:52:24 +0800118def determine_chrome_binaries(chromeos_root, test_name):
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800119 chrome_binaries = None
120 for name_pattern, binaries in CHROME_BINARIES_OF_TEST.items():
Kuang-che Wua41525a2018-10-17 23:52:24 +0800121 if fnmatch.fnmatch(test_name, name_pattern):
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800122 chrome_binaries = binaries
123 break
124
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800125 autotest_dir = os.path.join(chromeos_root, cros_util.prebuilt_autotest_dir)
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800126 if chrome_binaries:
127 logger.info('This test depends on chrome binary: %s', chrome_binaries)
Kuang-che Wua41525a2018-10-17 23:52:24 +0800128 elif may_depend_on_extra_chrome_binaries(autotest_dir, test_name):
Kuang-che Wuaf7b06b2019-01-30 18:50:05 +0800129 raise errors.InternalError(
Kuang-che Wu74768d32018-09-07 12:03:24 +0800130 '%s code used ChromeBinaryTest but the binary is unknown; '
Kuang-che Wuaf7b06b2019-01-30 18:50:05 +0800131 'please update CHROME_BINARIES_OF_TEST table' % test_name)
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800132 return chrome_binaries
133
134
Kuang-che Wu32f27242019-05-16 17:34:50 +0800135class DiagnoseAutotestCommandLine(diagnoser_cros.DiagnoseCommandLineBase):
Kuang-che Wua41525a2018-10-17 23:52:24 +0800136 """Diagnose command line interface."""
Kuang-che Wud8fc9572018-10-03 21:00:41 +0800137
Kuang-che Wua41525a2018-10-17 23:52:24 +0800138 def check_options(self, opts, path_factory):
Kuang-che Wu32f27242019-05-16 17:34:50 +0800139 super(DiagnoseAutotestCommandLine, self).check_options(opts, path_factory)
Kuang-che Wua41525a2018-10-17 23:52:24 +0800140
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800141 is_cts = (
142 opts.cts_revision or opts.cts_abi or opts.cts_prefix or
143 opts.cts_module or opts.cts_test or opts.cts_timeout)
144 if is_cts:
145 if opts.test_name or opts.metric or opts.args:
146 self.argument_parser.error(
147 'do not specify --test_name, --metric, --args for CTS/GTS tests')
Kuang-che Wub91b1512019-05-29 15:16:29 +0800148 if not opts.cts_prefix:
149 self.argument_parser.error(
150 '--cts_prefix should be specified for CTS/GTS tests')
151 if not opts.cts_module:
152 self.argument_parser.error(
153 '--cts_module should be specified for CTS/GTS tests')
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800154 opts.test_name = '%s.tradefed-run-test' % opts.cts_prefix
Kuang-che Wu32f27242019-05-16 17:34:50 +0800155 elif not opts.test_name:
156 self.argument_parser.error(
157 '--test_name should be specified if not CTS/GTS tests')
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800158
Kuang-che Wu32f27242019-05-16 17:34:50 +0800159 def init_hook(self, opts):
160 self.states.config.update(
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800161 cts_revision=opts.cts_revision,
162 cts_abi=opts.cts_abi,
163 cts_prefix=opts.cts_prefix,
164 cts_module=opts.cts_module,
165 cts_test=opts.cts_test,
166 cts_timeout=opts.cts_timeout,
Kuang-che Wua41525a2018-10-17 23:52:24 +0800167 test_that_args=opts.args,
Kuang-che Wua41525a2018-10-17 23:52:24 +0800168 )
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800169
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800170 # Unpack old autotest prebuilt, assume following information don't change
171 # between versions:
172 # - what chrome binaries to run
173 # - dependency labels for DUT allocation
174 common_switch_cmd, _common_eval_cmd = self._build_cmds()
175 util.check_call(*(common_switch_cmd + [self.config['old']]))
176
Kuang-che Wua41525a2018-10-17 23:52:24 +0800177 def _build_cmds(self):
178 # prebuilt version will be specified later.
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800179 common_switch_cmd = [
Kuang-che Wua41525a2018-10-17 23:52:24 +0800180 './switch_autotest_prebuilt.py',
Kuang-che Wu99e808f2019-06-26 12:17:32 +0800181 '--chromeos_root',
182 self.config['chromeos_root'],
183 '--board',
184 self.config['board'],
185 ]
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800186 if self.config['test_name'] and not self.config['cts_test']:
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800187 common_switch_cmd += ['--test_name', self.config['test_name']]
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800188
Kuang-che Wua41525a2018-10-17 23:52:24 +0800189 common_eval_cmd = [
190 './eval_cros_autotest.py',
191 '--chromeos_root', self.config['chromeos_root'],
Kuang-che Wua41525a2018-10-17 23:52:24 +0800192 ] # yapf: disable
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800193 if self.config['test_name'] and not self.config['cts_test']:
194 common_eval_cmd += ['--test_name', self.config['test_name']]
Kuang-che Wua41525a2018-10-17 23:52:24 +0800195 if self.config['metric']:
196 common_eval_cmd += [
197 '--metric', self.config['metric'],
Kuang-che Wua41525a2018-10-17 23:52:24 +0800198 ] # yapf: disable
Kuang-che Wu32f27242019-05-16 17:34:50 +0800199 if self.config['fail_to_pass']:
Kuang-che Wu0a4304a2019-01-19 01:32:11 +0800200 common_eval_cmd.append('--fail_to_pass')
Kuang-che Wuda3abfe2019-03-21 14:48:12 +0800201 if self.config['reboot_before_test']:
202 common_eval_cmd.append('--reboot_before_test')
Kuang-che Wua41525a2018-10-17 23:52:24 +0800203 if self.config['test_that_args']:
204 common_eval_cmd += ['--args', self.config['test_that_args']]
Kuang-che Wud4603d72018-11-29 17:51:21 +0800205 if self.config['test_name'].startswith('telemetry_'):
206 common_eval_cmd += ['--chrome_root', self.config['chrome_root']]
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800207
208 for arg_name in [
209 'cts_revision', 'cts_abi', 'cts_prefix', 'cts_module', 'cts_test',
210 'cts_timeout'
211 ]:
212 if self.config.get(arg_name) is not None:
213 common_eval_cmd += ['--%s' % arg_name, str(self.config[arg_name])]
214
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800215 return common_switch_cmd, common_eval_cmd
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800216
Kuang-che Wua41525a2018-10-17 23:52:24 +0800217 def cmd_run(self, opts):
218 del opts # unused
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800219
Kuang-che Wua41525a2018-10-17 23:52:24 +0800220 self.states.load()
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800221
Kuang-che Wu8b654092018-11-09 17:56:25 +0800222 try:
223 path_factory = setup_cros_bisect.DefaultProjectPathFactory(
224 self.config['mirror_base'], self.config['work_base'],
225 self.config['session'])
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800226 common_switch_cmd, common_eval_cmd = self._build_cmds()
Kuang-che Wu8b654092018-11-09 17:56:25 +0800227
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800228 if self.config['test_name']:
229 chrome_binaries = determine_chrome_binaries(
230 self.config['chromeos_root'], self.config['test_name'])
231 else:
232 chrome_binaries = None
Kuang-che Wu8b654092018-11-09 17:56:25 +0800233
234 with cros_lab_util.dut_manager(self.config['dut'],
235 lambda: grab_dut(self.config)) as dut:
236 if not dut:
237 raise errors.NoDutAvailable('unable to allocate DUT')
Kuang-che Wu7cb08df2019-06-04 19:12:29 +0800238 if not cros_util.is_good_dut(dut):
239 if not cros_lab_util.repair(dut):
240 raise errors.ExternalError('Not a good DUT and unable to repair')
Kuang-che Wu8b654092018-11-09 17:56:25 +0800241 if self.config['dut'] == cros_lab_util.LAB_DUT:
242 self.config['allocated_dut'] = dut
243 self.states.save()
244 common_eval_cmd.append(dut)
245
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800246 util.check_call('./switch_cros_localbuild.py', '--nobuild',
247 '--chromeos_root', self.config['chromeos_root'],
248 '--chromeos_mirror', self.config['chromeos_mirror'],
Kuang-che Wu0ebbf7c2019-08-28 18:19:19 +0800249 '--board', self.config['board'], self.config['new'])
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800250
Kuang-che Wu186071a2019-03-28 17:11:41 +0800251 diagnoser = diagnoser_cros.CrosDiagnoser(self.states, path_factory,
252 self.config, dut)
Kuang-che Wu8b654092018-11-09 17:56:25 +0800253
Kuang-che Wufd2ea5e2019-06-10 20:40:17 +0800254 eval_cmd = common_eval_cmd + ['--prebuilt']
Kuang-che Wu8b654092018-11-09 17:56:25 +0800255 # Do not specify version for autotest prebuilt switching here. The trick
256 # is that version number is obtained via bisector's environment variable
257 # CROS_VERSION.
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800258 extra_switch_cmd = common_switch_cmd
Kuang-che Wu8b654092018-11-09 17:56:25 +0800259 diagnoser.narrow_down_chromeos_prebuilt(
260 self.config['old'],
261 self.config['new'],
262 eval_cmd,
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800263 extra_switch_cmd=extra_switch_cmd)
Kuang-che Wu8b654092018-11-09 17:56:25 +0800264
265 diagnoser.switch_chromeos_to_old(force=self.config['always_reflash'])
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800266 util.check_call(*(common_switch_cmd + [diagnoser.cros_old]))
Kuang-che Wuc8c495d2019-08-19 17:48:58 +0800267 dut_os_version = cros_util.query_dut_short_version(dut)
Kuang-che Wu8b654092018-11-09 17:56:25 +0800268
269 try:
270 if diagnoser.narrow_down_android(eval_cmd):
271 return
Kuang-che Wuec566f32019-03-07 16:53:32 +0800272 except errors.DiagnoseContradiction:
Kuang-che Wu8b654092018-11-09 17:56:25 +0800273 raise
274 except Exception:
275 logger.exception('exception in android bisector before verification; '
276 'assume culprit is not inside android and continue')
277 # Assume it's ok to leave random version of android prebuilt on DUT.
278
Kuang-che Wuc8c495d2019-08-19 17:48:58 +0800279 # Sanity check. The OS version should not change after android bisect.
Kuang-che Wu523bdf22019-08-20 12:11:09 +0800280 assert dut_os_version == cros_util.query_dut_short_version(dut), \
281 'Someone else reflashed the DUT. ' \
282 'DUT locking is not respected? b/126141102'
Kuang-che Wuc8c495d2019-08-19 17:48:58 +0800283
Kuang-che Wu8b654092018-11-09 17:56:25 +0800284 eval_cmd = common_eval_cmd + ['--prebuilt']
Kuang-che Wufd2ea5e2019-06-10 20:40:17 +0800285
286 if chrome_binaries:
287 # Now, the version of autotest on the DUT is unknown and may be even
288 # not installed. Invoke the test once here, so
289 # - make sure autotest-deps is installed, with expected version
290 # - autotest-deps is installed first, so our chrome_binaries
291 # won't be reset to default version during bisection.
292 # It's acceptable to spend extra time to run test once because
293 # - only few tests do so
294 # - tests are migrating away from autotest
295 util.call(*eval_cmd)
296
Kuang-che Wu8b654092018-11-09 17:56:25 +0800297 try:
298 if diagnoser.narrow_down_chrome(
Kuang-che Wu50d8ff42018-11-26 12:48:30 +0800299 eval_cmd, chrome_binaries=chrome_binaries):
Kuang-che Wu8b654092018-11-09 17:56:25 +0800300 return
Kuang-che Wuec566f32019-03-07 16:53:32 +0800301 except errors.DiagnoseContradiction:
Kuang-che Wu8b654092018-11-09 17:56:25 +0800302 raise
303 except Exception:
304 logger.exception('exception in chrome bisector before verification; '
305 'assume culprit is not inside chrome and continue')
306
Kuang-che Wude5bfc32019-09-12 21:56:48 +0800307 if not self.config['chrome_deploy_image']:
308 # Sanity check. The OS version should not change after chrome bisect.
309 assert dut_os_version == cros_util.query_dut_short_version(dut), \
310 'Someone else reflashed the DUT. ' \
311 'DUT locking is not respected? b/126141102'
Kuang-che Wuc8c495d2019-08-19 17:48:58 +0800312
Kuang-che Wufd2ea5e2019-06-10 20:40:17 +0800313 eval_cmd = common_eval_cmd
Kuang-che Wu8b654092018-11-09 17:56:25 +0800314 diagnoser.narrow_down_chromeos_localbuild(eval_cmd)
315 logger.info('%s done', __file__)
316 except Exception as e:
317 logger.exception('got exception; stop')
318 exception_name = e.__class__.__name__
319 self.states.add_history(
320 'failed', '%s: %s' % (exception_name, e), exception=exception_name)
321
Kuang-che Wu32f27242019-05-16 17:34:50 +0800322 def create_argument_parser_hook(self, parser_init):
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800323 group = parser_init.add_argument_group(title='Options for CTS/GTS tests')
324 group.add_argument('--cts_revision', help='CTS revision, like "9.0_r3"')
Kuang-che Wu63f836a2019-02-21 16:33:32 +0000325 group.add_argument('--cts_abi', choices=['arm', 'x86'])
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800326 group.add_argument(
327 '--cts_prefix',
328 help='Prefix of autotest test name, '
329 'like cheets_CTS_N, cheets_CTS_P, cheets_GTS')
330 group.add_argument(
331 '--cts_module', help='CTS/GTS module name, like "CtsCameraTestCases"')
332 group.add_argument(
333 '--cts_test',
334 help='CTS/GTS test name, like '
335 '"android.hardware.cts.CameraTest#testDisplayOrientation"')
336 group.add_argument('--cts_timeout', type=float, help='timeout, in seconds')
337
Kuang-che Wua41525a2018-10-17 23:52:24 +0800338 group = parser_init.add_argument_group(title='Options passed to test_that')
339 group.add_argument(
340 '--args',
341 help='Extra args passed to "test_that --args"; Overrides the default')
342
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800343
344if __name__ == '__main__':
Kuang-che Wu32f27242019-05-16 17:34:50 +0800345 DiagnoseAutotestCommandLine().main()