blob: b4f38d49764f3f4b2f57b1a39b5142a5c14a5369 [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
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080074def may_depend_on_extra_chrome_binaries(autotest_dir, test_name):
75 info = cros_util.get_autotest_test_info(autotest_dir, test_name)
Kuang-che Wu0f1c3b02019-01-10 01:21:01 +080076 assert info, 'incorrect test name? %s' % test_name
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080077 dirpath = os.path.dirname(info.path)
78 for pypath in glob.glob(os.path.join(dirpath, '*.py')):
Kuang-che Wu85ed87d2019-09-17 20:47:28 +080079 content = open(pypath).read()
80 # Checking these two functions is more accurate than 'ChromeBinaryTest'
81 # (b/141164895).
82 if ('run_chrome_test_binary' in content or
83 'get_chrome_binary_path' in content):
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080084 return True
85 return False
86
87
Kuang-che Wua41525a2018-10-17 23:52:24 +080088def determine_chrome_binaries(chromeos_root, test_name):
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080089 chrome_binaries = None
90 for name_pattern, binaries in CHROME_BINARIES_OF_TEST.items():
Kuang-che Wua41525a2018-10-17 23:52:24 +080091 if fnmatch.fnmatch(test_name, name_pattern):
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080092 chrome_binaries = binaries
93 break
94
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +080095 autotest_dir = os.path.join(chromeos_root, cros_util.prebuilt_autotest_dir)
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080096 if chrome_binaries:
97 logger.info('This test depends on chrome binary: %s', chrome_binaries)
Kuang-che Wua41525a2018-10-17 23:52:24 +080098 elif may_depend_on_extra_chrome_binaries(autotest_dir, test_name):
Kuang-che Wuaf7b06b2019-01-30 18:50:05 +080099 raise errors.InternalError(
Kuang-che Wu74768d32018-09-07 12:03:24 +0800100 '%s code used ChromeBinaryTest but the binary is unknown; '
Kuang-che Wuaf7b06b2019-01-30 18:50:05 +0800101 'please update CHROME_BINARIES_OF_TEST table' % test_name)
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800102 return chrome_binaries
103
104
Kuang-che Wu32f27242019-05-16 17:34:50 +0800105class DiagnoseAutotestCommandLine(diagnoser_cros.DiagnoseCommandLineBase):
Kuang-che Wua41525a2018-10-17 23:52:24 +0800106 """Diagnose command line interface."""
Kuang-che Wud8fc9572018-10-03 21:00:41 +0800107
Kuang-che Wua41525a2018-10-17 23:52:24 +0800108 def check_options(self, opts, path_factory):
Kuang-che Wu32f27242019-05-16 17:34:50 +0800109 super(DiagnoseAutotestCommandLine, self).check_options(opts, path_factory)
Kuang-che Wua41525a2018-10-17 23:52:24 +0800110
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800111 is_cts = (
112 opts.cts_revision or opts.cts_abi or opts.cts_prefix or
113 opts.cts_module or opts.cts_test or opts.cts_timeout)
114 if is_cts:
115 if opts.test_name or opts.metric or opts.args:
116 self.argument_parser.error(
117 'do not specify --test_name, --metric, --args for CTS/GTS tests')
Kuang-che Wub91b1512019-05-29 15:16:29 +0800118 if not opts.cts_prefix:
119 self.argument_parser.error(
120 '--cts_prefix should be specified for CTS/GTS tests')
121 if not opts.cts_module:
122 self.argument_parser.error(
123 '--cts_module should be specified for CTS/GTS tests')
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800124 opts.test_name = '%s.tradefed-run-test' % opts.cts_prefix
Kuang-che Wu32f27242019-05-16 17:34:50 +0800125 elif not opts.test_name:
126 self.argument_parser.error(
127 '--test_name should be specified if not CTS/GTS tests')
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800128
Kuang-che Wu32f27242019-05-16 17:34:50 +0800129 def init_hook(self, opts):
130 self.states.config.update(
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800131 cts_revision=opts.cts_revision,
132 cts_abi=opts.cts_abi,
133 cts_prefix=opts.cts_prefix,
134 cts_module=opts.cts_module,
135 cts_test=opts.cts_test,
136 cts_timeout=opts.cts_timeout,
Kuang-che Wua41525a2018-10-17 23:52:24 +0800137 test_that_args=opts.args,
Kuang-che Wua41525a2018-10-17 23:52:24 +0800138 )
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800139
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800140 # Unpack old autotest prebuilt, assume following information don't change
141 # between versions:
142 # - what chrome binaries to run
143 # - dependency labels for DUT allocation
144 common_switch_cmd, _common_eval_cmd = self._build_cmds()
145 util.check_call(*(common_switch_cmd + [self.config['old']]))
146
Kuang-che Wua41525a2018-10-17 23:52:24 +0800147 def _build_cmds(self):
148 # prebuilt version will be specified later.
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800149 common_switch_cmd = [
Kuang-che Wua41525a2018-10-17 23:52:24 +0800150 './switch_autotest_prebuilt.py',
Kuang-che Wu99e808f2019-06-26 12:17:32 +0800151 '--chromeos_root',
152 self.config['chromeos_root'],
153 '--board',
154 self.config['board'],
155 ]
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800156 if self.config['test_name'] and not self.config['cts_test']:
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800157 common_switch_cmd += ['--test_name', self.config['test_name']]
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800158
Kuang-che Wua41525a2018-10-17 23:52:24 +0800159 common_eval_cmd = [
160 './eval_cros_autotest.py',
161 '--chromeos_root', self.config['chromeos_root'],
Kuang-che Wua41525a2018-10-17 23:52:24 +0800162 ] # yapf: disable
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800163 if self.config['test_name'] and not self.config['cts_test']:
164 common_eval_cmd += ['--test_name', self.config['test_name']]
Kuang-che Wua41525a2018-10-17 23:52:24 +0800165 if self.config['metric']:
166 common_eval_cmd += [
167 '--metric', self.config['metric'],
Kuang-che Wua41525a2018-10-17 23:52:24 +0800168 ] # yapf: disable
Kuang-che Wu32f27242019-05-16 17:34:50 +0800169 if self.config['fail_to_pass']:
Kuang-che Wu0a4304a2019-01-19 01:32:11 +0800170 common_eval_cmd.append('--fail_to_pass')
Kuang-che Wuda3abfe2019-03-21 14:48:12 +0800171 if self.config['reboot_before_test']:
172 common_eval_cmd.append('--reboot_before_test')
Kuang-che Wua41525a2018-10-17 23:52:24 +0800173 if self.config['test_that_args']:
174 common_eval_cmd += ['--args', self.config['test_that_args']]
Kuang-che Wud4603d72018-11-29 17:51:21 +0800175 if self.config['test_name'].startswith('telemetry_'):
176 common_eval_cmd += ['--chrome_root', self.config['chrome_root']]
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800177
178 for arg_name in [
179 'cts_revision', 'cts_abi', 'cts_prefix', 'cts_module', 'cts_test',
180 'cts_timeout'
181 ]:
182 if self.config.get(arg_name) is not None:
183 common_eval_cmd += ['--%s' % arg_name, str(self.config[arg_name])]
184
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800185 return common_switch_cmd, common_eval_cmd
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800186
Kuang-che Wua41525a2018-10-17 23:52:24 +0800187 def cmd_run(self, opts):
188 del opts # unused
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800189
Kuang-che Wua41525a2018-10-17 23:52:24 +0800190 self.states.load()
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800191
Kuang-che Wu8b654092018-11-09 17:56:25 +0800192 try:
193 path_factory = setup_cros_bisect.DefaultProjectPathFactory(
194 self.config['mirror_base'], self.config['work_base'],
195 self.config['session'])
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800196 common_switch_cmd, common_eval_cmd = self._build_cmds()
Kuang-che Wu8b654092018-11-09 17:56:25 +0800197
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800198 if self.config['test_name']:
199 chrome_binaries = determine_chrome_binaries(
200 self.config['chromeos_root'], self.config['test_name'])
201 else:
202 chrome_binaries = None
Kuang-che Wu8b654092018-11-09 17:56:25 +0800203
Kuang-che Wu0c9b7942019-10-30 16:55:39 +0800204 with cros_lab_util.dut_manager(
205 self.config['dut'],
206 lambda: diagnoser_cros.grab_dut(self.config)) as dut:
Kuang-che Wu8b654092018-11-09 17:56:25 +0800207 if not dut:
208 raise errors.NoDutAvailable('unable to allocate DUT')
Kuang-che Wu0c9b7942019-10-30 16:55:39 +0800209
Kuang-che Wu7cb08df2019-06-04 19:12:29 +0800210 if not cros_util.is_good_dut(dut):
211 if not cros_lab_util.repair(dut):
212 raise errors.ExternalError('Not a good DUT and unable to repair')
Kuang-che Wu8b654092018-11-09 17:56:25 +0800213 if self.config['dut'] == cros_lab_util.LAB_DUT:
214 self.config['allocated_dut'] = dut
215 self.states.save()
216 common_eval_cmd.append(dut)
217
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800218 util.check_call('./switch_cros_localbuild.py', '--nobuild',
219 '--chromeos_root', self.config['chromeos_root'],
220 '--chromeos_mirror', self.config['chromeos_mirror'],
Kuang-che Wu0ebbf7c2019-08-28 18:19:19 +0800221 '--board', self.config['board'], self.config['new'])
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800222
Kuang-che Wu186071a2019-03-28 17:11:41 +0800223 diagnoser = diagnoser_cros.CrosDiagnoser(self.states, path_factory,
224 self.config, dut)
Kuang-che Wu8b654092018-11-09 17:56:25 +0800225
Kuang-che Wufd2ea5e2019-06-10 20:40:17 +0800226 eval_cmd = common_eval_cmd + ['--prebuilt']
Kuang-che Wu8b654092018-11-09 17:56:25 +0800227 # Do not specify version for autotest prebuilt switching here. The trick
228 # is that version number is obtained via bisector's environment variable
229 # CROS_VERSION.
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800230 extra_switch_cmd = common_switch_cmd
Kuang-che Wu8b654092018-11-09 17:56:25 +0800231 diagnoser.narrow_down_chromeos_prebuilt(
232 self.config['old'],
233 self.config['new'],
234 eval_cmd,
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800235 extra_switch_cmd=extra_switch_cmd)
Kuang-che Wu8b654092018-11-09 17:56:25 +0800236
237 diagnoser.switch_chromeos_to_old(force=self.config['always_reflash'])
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800238 util.check_call(*(common_switch_cmd + [diagnoser.cros_old]))
Kuang-che Wuc8c495d2019-08-19 17:48:58 +0800239 dut_os_version = cros_util.query_dut_short_version(dut)
Kuang-che Wu8b654092018-11-09 17:56:25 +0800240
241 try:
242 if diagnoser.narrow_down_android(eval_cmd):
243 return
Kuang-che Wuec566f32019-03-07 16:53:32 +0800244 except errors.DiagnoseContradiction:
Kuang-che Wu8b654092018-11-09 17:56:25 +0800245 raise
246 except Exception:
247 logger.exception('exception in android bisector before verification; '
248 'assume culprit is not inside android and continue')
249 # Assume it's ok to leave random version of android prebuilt on DUT.
250
Kuang-che Wuc8c495d2019-08-19 17:48:58 +0800251 # Sanity check. The OS version should not change after android bisect.
Kuang-che Wu523bdf22019-08-20 12:11:09 +0800252 assert dut_os_version == cros_util.query_dut_short_version(dut), \
253 'Someone else reflashed the DUT. ' \
254 'DUT locking is not respected? b/126141102'
Kuang-che Wuc8c495d2019-08-19 17:48:58 +0800255
Kuang-che Wu8b654092018-11-09 17:56:25 +0800256 eval_cmd = common_eval_cmd + ['--prebuilt']
Kuang-che Wufd2ea5e2019-06-10 20:40:17 +0800257
258 if chrome_binaries:
259 # Now, the version of autotest on the DUT is unknown and may be even
260 # not installed. Invoke the test once here, so
261 # - make sure autotest-deps is installed, with expected version
262 # - autotest-deps is installed first, so our chrome_binaries
263 # won't be reset to default version during bisection.
264 # It's acceptable to spend extra time to run test once because
265 # - only few tests do so
266 # - tests are migrating away from autotest
267 util.call(*eval_cmd)
268
Kuang-che Wu8b654092018-11-09 17:56:25 +0800269 try:
270 if diagnoser.narrow_down_chrome(
Kuang-che Wu50d8ff42018-11-26 12:48:30 +0800271 eval_cmd, chrome_binaries=chrome_binaries):
Kuang-che Wu8b654092018-11-09 17:56:25 +0800272 return
Kuang-che Wuec566f32019-03-07 16:53:32 +0800273 except errors.DiagnoseContradiction:
Kuang-che Wu8b654092018-11-09 17:56:25 +0800274 raise
275 except Exception:
276 logger.exception('exception in chrome bisector before verification; '
277 'assume culprit is not inside chrome and continue')
278
Kuang-che Wude5bfc32019-09-12 21:56:48 +0800279 if not self.config['chrome_deploy_image']:
280 # Sanity check. The OS version should not change after chrome bisect.
281 assert dut_os_version == cros_util.query_dut_short_version(dut), \
282 'Someone else reflashed the DUT. ' \
283 'DUT locking is not respected? b/126141102'
Kuang-che Wuc8c495d2019-08-19 17:48:58 +0800284
Kuang-che Wufd2ea5e2019-06-10 20:40:17 +0800285 eval_cmd = common_eval_cmd
Kuang-che Wu8b654092018-11-09 17:56:25 +0800286 diagnoser.narrow_down_chromeos_localbuild(eval_cmd)
287 logger.info('%s done', __file__)
288 except Exception as e:
289 logger.exception('got exception; stop')
290 exception_name = e.__class__.__name__
291 self.states.add_history(
292 'failed', '%s: %s' % (exception_name, e), exception=exception_name)
293
Kuang-che Wu32f27242019-05-16 17:34:50 +0800294 def create_argument_parser_hook(self, parser_init):
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800295 group = parser_init.add_argument_group(title='Options for CTS/GTS tests')
296 group.add_argument('--cts_revision', help='CTS revision, like "9.0_r3"')
Kuang-che Wu63f836a2019-02-21 16:33:32 +0000297 group.add_argument('--cts_abi', choices=['arm', 'x86'])
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800298 group.add_argument(
299 '--cts_prefix',
300 help='Prefix of autotest test name, '
301 'like cheets_CTS_N, cheets_CTS_P, cheets_GTS')
302 group.add_argument(
303 '--cts_module', help='CTS/GTS module name, like "CtsCameraTestCases"')
304 group.add_argument(
305 '--cts_test',
306 help='CTS/GTS test name, like '
307 '"android.hardware.cts.CameraTest#testDisplayOrientation"')
308 group.add_argument('--cts_timeout', type=float, help='timeout, in seconds')
309
Kuang-che Wua41525a2018-10-17 23:52:24 +0800310 group = parser_init.add_argument_group(title='Options passed to test_that')
311 group.add_argument(
312 '--args',
313 help='Extra args passed to "test_that --args"; Overrides the default')
314
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800315
316if __name__ == '__main__':
Kuang-che Wu32f27242019-05-16 17:34:50 +0800317 DiagnoseAutotestCommandLine().main()