blob: fdb4857e19b84c6ce1c21514a1198970475bb877 [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')):
109 if 'ChromeBinaryTest' in open(pypath).read():
110 return True
111 return False
112
113
Kuang-che Wua41525a2018-10-17 23:52:24 +0800114def determine_chrome_binaries(chromeos_root, test_name):
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800115 chrome_binaries = None
116 for name_pattern, binaries in CHROME_BINARIES_OF_TEST.items():
Kuang-che Wua41525a2018-10-17 23:52:24 +0800117 if fnmatch.fnmatch(test_name, name_pattern):
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800118 chrome_binaries = binaries
119 break
120
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800121 autotest_dir = os.path.join(chromeos_root, cros_util.prebuilt_autotest_dir)
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800122 if chrome_binaries:
123 logger.info('This test depends on chrome binary: %s', chrome_binaries)
Kuang-che Wua41525a2018-10-17 23:52:24 +0800124 elif may_depend_on_extra_chrome_binaries(autotest_dir, test_name):
Kuang-che Wuaf7b06b2019-01-30 18:50:05 +0800125 raise errors.InternalError(
Kuang-che Wu74768d32018-09-07 12:03:24 +0800126 '%s code used ChromeBinaryTest but the binary is unknown; '
Kuang-che Wuaf7b06b2019-01-30 18:50:05 +0800127 'please update CHROME_BINARIES_OF_TEST table' % test_name)
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800128 return chrome_binaries
129
130
Kuang-che Wu32f27242019-05-16 17:34:50 +0800131class DiagnoseAutotestCommandLine(diagnoser_cros.DiagnoseCommandLineBase):
Kuang-che Wua41525a2018-10-17 23:52:24 +0800132 """Diagnose command line interface."""
Kuang-che Wud8fc9572018-10-03 21:00:41 +0800133
Kuang-che Wua41525a2018-10-17 23:52:24 +0800134 def check_options(self, opts, path_factory):
Kuang-che Wu32f27242019-05-16 17:34:50 +0800135 super(DiagnoseAutotestCommandLine, self).check_options(opts, path_factory)
Kuang-che Wua41525a2018-10-17 23:52:24 +0800136
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800137 is_cts = (
138 opts.cts_revision or opts.cts_abi or opts.cts_prefix or
139 opts.cts_module or opts.cts_test or opts.cts_timeout)
140 if is_cts:
141 if opts.test_name or opts.metric or opts.args:
142 self.argument_parser.error(
143 'do not specify --test_name, --metric, --args for CTS/GTS tests')
Kuang-che Wub91b1512019-05-29 15:16:29 +0800144 if not opts.cts_prefix:
145 self.argument_parser.error(
146 '--cts_prefix should be specified for CTS/GTS tests')
147 if not opts.cts_module:
148 self.argument_parser.error(
149 '--cts_module should be specified for CTS/GTS tests')
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800150 opts.test_name = '%s.tradefed-run-test' % opts.cts_prefix
Kuang-che Wu32f27242019-05-16 17:34:50 +0800151 elif not opts.test_name:
152 self.argument_parser.error(
153 '--test_name should be specified if not CTS/GTS tests')
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800154
Kuang-che Wu32f27242019-05-16 17:34:50 +0800155 def init_hook(self, opts):
156 self.states.config.update(
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800157 cts_revision=opts.cts_revision,
158 cts_abi=opts.cts_abi,
159 cts_prefix=opts.cts_prefix,
160 cts_module=opts.cts_module,
161 cts_test=opts.cts_test,
162 cts_timeout=opts.cts_timeout,
Kuang-che Wua41525a2018-10-17 23:52:24 +0800163 test_that_args=opts.args,
Kuang-che Wua41525a2018-10-17 23:52:24 +0800164 )
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800165
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800166 # Unpack old autotest prebuilt, assume following information don't change
167 # between versions:
168 # - what chrome binaries to run
169 # - dependency labels for DUT allocation
170 common_switch_cmd, _common_eval_cmd = self._build_cmds()
171 util.check_call(*(common_switch_cmd + [self.config['old']]))
172
Kuang-che Wua41525a2018-10-17 23:52:24 +0800173 def _build_cmds(self):
174 # prebuilt version will be specified later.
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800175 common_switch_cmd = [
Kuang-che Wua41525a2018-10-17 23:52:24 +0800176 './switch_autotest_prebuilt.py',
Kuang-che Wu99e808f2019-06-26 12:17:32 +0800177 '--chromeos_root',
178 self.config['chromeos_root'],
179 '--board',
180 self.config['board'],
181 ]
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800182 if self.config['test_name'] and not self.config['cts_test']:
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800183 common_switch_cmd += ['--test_name', self.config['test_name']]
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800184
Kuang-che Wua41525a2018-10-17 23:52:24 +0800185 common_eval_cmd = [
186 './eval_cros_autotest.py',
187 '--chromeos_root', self.config['chromeos_root'],
Kuang-che Wua41525a2018-10-17 23:52:24 +0800188 ] # yapf: disable
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800189 if self.config['test_name'] and not self.config['cts_test']:
190 common_eval_cmd += ['--test_name', self.config['test_name']]
Kuang-che Wua41525a2018-10-17 23:52:24 +0800191 if self.config['metric']:
192 common_eval_cmd += [
193 '--metric', self.config['metric'],
Kuang-che Wua41525a2018-10-17 23:52:24 +0800194 ] # yapf: disable
Kuang-che Wu32f27242019-05-16 17:34:50 +0800195 if self.config['fail_to_pass']:
Kuang-che Wu0a4304a2019-01-19 01:32:11 +0800196 common_eval_cmd.append('--fail_to_pass')
Kuang-che Wuda3abfe2019-03-21 14:48:12 +0800197 if self.config['reboot_before_test']:
198 common_eval_cmd.append('--reboot_before_test')
Kuang-che Wua41525a2018-10-17 23:52:24 +0800199 if self.config['test_that_args']:
200 common_eval_cmd += ['--args', self.config['test_that_args']]
Kuang-che Wud4603d72018-11-29 17:51:21 +0800201 if self.config['test_name'].startswith('telemetry_'):
202 common_eval_cmd += ['--chrome_root', self.config['chrome_root']]
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800203
204 for arg_name in [
205 'cts_revision', 'cts_abi', 'cts_prefix', 'cts_module', 'cts_test',
206 'cts_timeout'
207 ]:
208 if self.config.get(arg_name) is not None:
209 common_eval_cmd += ['--%s' % arg_name, str(self.config[arg_name])]
Kuang-che Wu958d7d02019-05-30 17:58:21 +0800210 if self.config.get('cts_runonce'):
211 common_eval_cmd.append('--cts_runonce')
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800212
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800213 return common_switch_cmd, common_eval_cmd
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800214
Kuang-che Wua41525a2018-10-17 23:52:24 +0800215 def cmd_run(self, opts):
216 del opts # unused
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800217
Kuang-che Wua41525a2018-10-17 23:52:24 +0800218 self.states.load()
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800219
Kuang-che Wu8b654092018-11-09 17:56:25 +0800220 try:
221 path_factory = setup_cros_bisect.DefaultProjectPathFactory(
222 self.config['mirror_base'], self.config['work_base'],
223 self.config['session'])
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800224 common_switch_cmd, common_eval_cmd = self._build_cmds()
Kuang-che Wu8b654092018-11-09 17:56:25 +0800225
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800226 if self.config['test_name']:
227 chrome_binaries = determine_chrome_binaries(
228 self.config['chromeos_root'], self.config['test_name'])
229 else:
230 chrome_binaries = None
Kuang-che Wu8b654092018-11-09 17:56:25 +0800231
232 with cros_lab_util.dut_manager(self.config['dut'],
233 lambda: grab_dut(self.config)) as dut:
234 if not dut:
235 raise errors.NoDutAvailable('unable to allocate DUT')
Kuang-che Wu7cb08df2019-06-04 19:12:29 +0800236 if not cros_util.is_good_dut(dut):
237 if not cros_lab_util.repair(dut):
238 raise errors.ExternalError('Not a good DUT and unable to repair')
Kuang-che Wu8b654092018-11-09 17:56:25 +0800239 if self.config['dut'] == cros_lab_util.LAB_DUT:
240 self.config['allocated_dut'] = dut
241 self.states.save()
242 common_eval_cmd.append(dut)
243
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800244 util.check_call('./switch_cros_localbuild.py', '--nobuild',
245 '--chromeos_root', self.config['chromeos_root'],
246 '--chromeos_mirror', self.config['chromeos_mirror'],
Kuang-che Wu0ebbf7c2019-08-28 18:19:19 +0800247 '--board', self.config['board'], self.config['new'])
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800248
Kuang-che Wu186071a2019-03-28 17:11:41 +0800249 diagnoser = diagnoser_cros.CrosDiagnoser(self.states, path_factory,
250 self.config, dut)
Kuang-che Wu8b654092018-11-09 17:56:25 +0800251
Kuang-che Wufd2ea5e2019-06-10 20:40:17 +0800252 eval_cmd = common_eval_cmd + ['--prebuilt']
Kuang-che Wu8b654092018-11-09 17:56:25 +0800253 # Do not specify version for autotest prebuilt switching here. The trick
254 # is that version number is obtained via bisector's environment variable
255 # CROS_VERSION.
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800256 extra_switch_cmd = common_switch_cmd
Kuang-che Wu8b654092018-11-09 17:56:25 +0800257 diagnoser.narrow_down_chromeos_prebuilt(
258 self.config['old'],
259 self.config['new'],
260 eval_cmd,
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800261 extra_switch_cmd=extra_switch_cmd)
Kuang-che Wu8b654092018-11-09 17:56:25 +0800262
263 diagnoser.switch_chromeos_to_old(force=self.config['always_reflash'])
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800264 util.check_call(*(common_switch_cmd + [diagnoser.cros_old]))
Kuang-che Wuc8c495d2019-08-19 17:48:58 +0800265 dut_os_version = cros_util.query_dut_short_version(dut)
Kuang-che Wu8b654092018-11-09 17:56:25 +0800266
267 try:
268 if diagnoser.narrow_down_android(eval_cmd):
269 return
Kuang-che Wuec566f32019-03-07 16:53:32 +0800270 except errors.DiagnoseContradiction:
Kuang-che Wu8b654092018-11-09 17:56:25 +0800271 raise
272 except Exception:
273 logger.exception('exception in android bisector before verification; '
274 'assume culprit is not inside android and continue')
275 # Assume it's ok to leave random version of android prebuilt on DUT.
276
Kuang-che Wuc8c495d2019-08-19 17:48:58 +0800277 # Sanity check. The OS version should not change after android bisect.
Kuang-che Wu523bdf22019-08-20 12:11:09 +0800278 assert dut_os_version == cros_util.query_dut_short_version(dut), \
279 'Someone else reflashed the DUT. ' \
280 'DUT locking is not respected? b/126141102'
Kuang-che Wuc8c495d2019-08-19 17:48:58 +0800281
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 Wuc8c495d2019-08-19 17:48:58 +0800305 # Sanity check. The OS version should not change after chrome bisect.
Kuang-che Wu523bdf22019-08-20 12:11:09 +0800306 assert dut_os_version == cros_util.query_dut_short_version(dut), \
307 'Someone else reflashed the DUT. ' \
308 'DUT locking is not respected? b/126141102'
Kuang-che Wuc8c495d2019-08-19 17:48:58 +0800309
Kuang-che Wufd2ea5e2019-06-10 20:40:17 +0800310 eval_cmd = common_eval_cmd
Kuang-che Wu8b654092018-11-09 17:56:25 +0800311 diagnoser.narrow_down_chromeos_localbuild(eval_cmd)
312 logger.info('%s done', __file__)
313 except Exception as e:
314 logger.exception('got exception; stop')
315 exception_name = e.__class__.__name__
316 self.states.add_history(
317 'failed', '%s: %s' % (exception_name, e), exception=exception_name)
318
Kuang-che Wu32f27242019-05-16 17:34:50 +0800319 def create_argument_parser_hook(self, parser_init):
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800320 group = parser_init.add_argument_group(title='Options for CTS/GTS tests')
321 group.add_argument('--cts_revision', help='CTS revision, like "9.0_r3"')
Kuang-che Wu63f836a2019-02-21 16:33:32 +0000322 group.add_argument('--cts_abi', choices=['arm', 'x86'])
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800323 group.add_argument(
324 '--cts_prefix',
325 help='Prefix of autotest test name, '
326 'like cheets_CTS_N, cheets_CTS_P, cheets_GTS')
327 group.add_argument(
328 '--cts_module', help='CTS/GTS module name, like "CtsCameraTestCases"')
329 group.add_argument(
330 '--cts_test',
331 help='CTS/GTS test name, like '
332 '"android.hardware.cts.CameraTest#testDisplayOrientation"')
333 group.add_argument('--cts_timeout', type=float, help='timeout, in seconds')
Kuang-che Wu958d7d02019-05-30 17:58:21 +0800334 group.add_argument(
335 '--cts_runonce', action='store_true', help='run test only once')
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800336
Kuang-che Wua41525a2018-10-17 23:52:24 +0800337 group = parser_init.add_argument_group(title='Options passed to test_that')
338 group.add_argument(
339 '--args',
340 help='Extra args passed to "test_that --args"; Overrides the default')
341
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800342
343if __name__ == '__main__':
Kuang-che Wu32f27242019-05-16 17:34:50 +0800344 DiagnoseAutotestCommandLine().main()