blob: 6c3c1cedcda49c5fef7e01b0fdf7a2588e2c4d8b [file] [log] [blame]
Kuang-che Wu875c89a2020-01-08 14:30:55 +08001#!/usr/bin/env python3
Kuang-che Wu1fcc0222018-07-07 16:43:22 +08002# -*- 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 logging
17import os
18
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080019from bisect_kit import cros_lab_util
20from bisect_kit import cros_util
21from bisect_kit import diagnoser_cros
Kuang-che Wue121fae2018-11-09 16:18:39 +080022from bisect_kit import errors
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080023from bisect_kit import util
Kuang-che Wud8fc9572018-10-03 21:00:41 +080024import setup_cros_bisect
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080025
26logger = logging.getLogger(__name__)
27
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080028
Kuang-che Wu22aa9d42019-01-25 10:35:33 +080029def get_test_dependency_labels(config):
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080030 # Assume "DEPENDENCIES" is identical between the period of
31 # `old` and `new` version.
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +080032 autotest_dir = os.path.join(config['chromeos_root'],
33 cros_util.prebuilt_autotest_dir)
Kuang-che Wua41525a2018-10-17 23:52:24 +080034 info = cros_util.get_autotest_test_info(autotest_dir, config['test_name'])
Kuang-che Wu0f1c3b02019-01-10 01:21:01 +080035 assert info, 'incorrect test name? %s' % config['test_name']
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080036
Kuang-che Wu22aa9d42019-01-25 10:35:33 +080037 extra_labels = []
38 dependencies = info.variables.get('DEPENDENCIES', '')
39 for label in dependencies.split(','):
40 label = label.strip()
41 # Skip non-machine labels
42 if not label or label in ['cleanup-reboot']:
43 continue
44 extra_labels.append(label)
45
46 return extra_labels
47
48
Kuang-che Wu32f27242019-05-16 17:34:50 +080049class DiagnoseAutotestCommandLine(diagnoser_cros.DiagnoseCommandLineBase):
Kuang-che Wua41525a2018-10-17 23:52:24 +080050 """Diagnose command line interface."""
Kuang-che Wud8fc9572018-10-03 21:00:41 +080051
Kuang-che Wua41525a2018-10-17 23:52:24 +080052 def check_options(self, opts, path_factory):
Kuang-che Wu6d91b8c2020-11-24 20:14:35 +080053 super().check_options(opts, path_factory)
Kuang-che Wua41525a2018-10-17 23:52:24 +080054
Kuang-che Wu85c613c2019-01-09 15:46:11 +080055 is_cts = (
56 opts.cts_revision or opts.cts_abi or opts.cts_prefix or
57 opts.cts_module or opts.cts_test or opts.cts_timeout)
58 if is_cts:
59 if opts.test_name or opts.metric or opts.args:
60 self.argument_parser.error(
61 'do not specify --test_name, --metric, --args for CTS/GTS tests')
Kuang-che Wub91b1512019-05-29 15:16:29 +080062 if not opts.cts_prefix:
63 self.argument_parser.error(
64 '--cts_prefix should be specified for CTS/GTS tests')
65 if not opts.cts_module:
66 self.argument_parser.error(
67 '--cts_module should be specified for CTS/GTS tests')
Kuang-che Wu85c613c2019-01-09 15:46:11 +080068 opts.test_name = '%s.tradefed-run-test' % opts.cts_prefix
Kuang-che Wu32f27242019-05-16 17:34:50 +080069 elif not opts.test_name:
70 self.argument_parser.error(
71 '--test_name should be specified if not CTS/GTS tests')
Kuang-che Wu85c613c2019-01-09 15:46:11 +080072
Kuang-che Wu32f27242019-05-16 17:34:50 +080073 def init_hook(self, opts):
74 self.states.config.update(
Kuang-che Wu85c613c2019-01-09 15:46:11 +080075 cts_revision=opts.cts_revision,
76 cts_abi=opts.cts_abi,
77 cts_prefix=opts.cts_prefix,
78 cts_module=opts.cts_module,
79 cts_test=opts.cts_test,
80 cts_timeout=opts.cts_timeout,
Kuang-che Wua41525a2018-10-17 23:52:24 +080081 test_that_args=opts.args,
Kuang-che Wua41525a2018-10-17 23:52:24 +080082 )
Kuang-che Wu1fcc0222018-07-07 16:43:22 +080083
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +080084 # Unpack old autotest prebuilt, assume following information don't change
85 # between versions:
86 # - what chrome binaries to run
87 # - dependency labels for DUT allocation
88 common_switch_cmd, _common_eval_cmd = self._build_cmds()
Kuang-che Wud1b74152020-05-20 08:46:46 +080089 util.check_call(*(common_switch_cmd +
90 [self.config['old'], '--dut', opts.dut]))
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +080091
Kuang-che Wua41525a2018-10-17 23:52:24 +080092 def _build_cmds(self):
93 # prebuilt version will be specified later.
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +080094 common_switch_cmd = [
Kuang-che Wua41525a2018-10-17 23:52:24 +080095 './switch_autotest_prebuilt.py',
Kuang-che Wu99e808f2019-06-26 12:17:32 +080096 '--chromeos_root',
97 self.config['chromeos_root'],
98 '--board',
99 self.config['board'],
100 ]
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800101 if self.config['test_name'] and not self.config['cts_test']:
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800102 common_switch_cmd += ['--test_name', self.config['test_name']]
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800103
Kuang-che Wua41525a2018-10-17 23:52:24 +0800104 common_eval_cmd = [
105 './eval_cros_autotest.py',
106 '--chromeos_root', self.config['chromeos_root'],
Kuang-che Wua41525a2018-10-17 23:52:24 +0800107 ] # yapf: disable
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800108 if self.config['test_name'] and not self.config['cts_test']:
109 common_eval_cmd += ['--test_name', self.config['test_name']]
Kuang-che Wua41525a2018-10-17 23:52:24 +0800110 if self.config['metric']:
111 common_eval_cmd += [
112 '--metric', self.config['metric'],
Kuang-che Wua41525a2018-10-17 23:52:24 +0800113 ] # yapf: disable
Kuang-che Wu32f27242019-05-16 17:34:50 +0800114 if self.config['fail_to_pass']:
Kuang-che Wu0a4304a2019-01-19 01:32:11 +0800115 common_eval_cmd.append('--fail_to_pass')
Kuang-che Wuda3abfe2019-03-21 14:48:12 +0800116 if self.config['reboot_before_test']:
117 common_eval_cmd.append('--reboot_before_test')
Kuang-che Wua41525a2018-10-17 23:52:24 +0800118 if self.config['test_that_args']:
119 common_eval_cmd += ['--args', self.config['test_that_args']]
Kuang-che Wud4603d72018-11-29 17:51:21 +0800120 if self.config['test_name'].startswith('telemetry_'):
121 common_eval_cmd += ['--chrome_root', self.config['chrome_root']]
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800122
123 for arg_name in [
124 'cts_revision', 'cts_abi', 'cts_prefix', 'cts_module', 'cts_test',
125 'cts_timeout'
126 ]:
127 if self.config.get(arg_name) is not None:
128 common_eval_cmd += ['--%s' % arg_name, str(self.config[arg_name])]
129
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800130 return common_switch_cmd, common_eval_cmd
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800131
Kuang-che Wua41525a2018-10-17 23:52:24 +0800132 def cmd_run(self, opts):
133 del opts # unused
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800134
Kuang-che Wua41525a2018-10-17 23:52:24 +0800135 self.states.load()
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800136
Kuang-che Wu8b654092018-11-09 17:56:25 +0800137 try:
138 path_factory = setup_cros_bisect.DefaultProjectPathFactory(
139 self.config['mirror_base'], self.config['work_base'],
140 self.config['session'])
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800141 common_switch_cmd, common_eval_cmd = self._build_cmds()
Kuang-che Wu8b654092018-11-09 17:56:25 +0800142
Kuang-che Wu30039b12021-01-18 10:10:33 +0800143 lease_reason = cros_lab_util.make_lease_reason(self.config['session'])
Kuang-che Wu0c9b7942019-10-30 16:55:39 +0800144 with cros_lab_util.dut_manager(
Kuang-che Wu30039b12021-01-18 10:10:33 +0800145 self.config['dut'],
146 lease_reason, lambda: diagnoser_cros.grab_dut(self.config)) as dut:
Kuang-che Wu8b654092018-11-09 17:56:25 +0800147 if not dut:
148 raise errors.NoDutAvailable('unable to allocate DUT')
Kuang-che Wu0c9b7942019-10-30 16:55:39 +0800149
Kuang-che Wu7cb08df2019-06-04 19:12:29 +0800150 if not cros_util.is_good_dut(dut):
151 if not cros_lab_util.repair(dut):
152 raise errors.ExternalError('Not a good DUT and unable to repair')
Kuang-che Wu8b654092018-11-09 17:56:25 +0800153 if self.config['dut'] == cros_lab_util.LAB_DUT:
154 self.config['allocated_dut'] = dut
155 self.states.save()
156 common_eval_cmd.append(dut)
157
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800158 util.check_call('./switch_cros_localbuild.py', '--nobuild',
159 '--chromeos_root', self.config['chromeos_root'],
160 '--chromeos_mirror', self.config['chromeos_mirror'],
Kuang-che Wu0ebbf7c2019-08-28 18:19:19 +0800161 '--board', self.config['board'], self.config['new'])
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800162
Kuang-che Wu186071a2019-03-28 17:11:41 +0800163 diagnoser = diagnoser_cros.CrosDiagnoser(self.states, path_factory,
164 self.config, dut)
Kuang-che Wu8b654092018-11-09 17:56:25 +0800165
Kuang-che Wufd2ea5e2019-06-10 20:40:17 +0800166 eval_cmd = common_eval_cmd + ['--prebuilt']
Kuang-che Wu8b654092018-11-09 17:56:25 +0800167 # Do not specify version for autotest prebuilt switching here. The trick
168 # is that version number is obtained via bisector's environment variable
169 # CROS_VERSION.
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800170 extra_switch_cmd = common_switch_cmd
Kuang-che Wu8b654092018-11-09 17:56:25 +0800171 diagnoser.narrow_down_chromeos_prebuilt(
172 self.config['old'],
173 self.config['new'],
174 eval_cmd,
Kuang-che Wu7f82c6f2019-08-12 14:29:28 +0800175 extra_switch_cmd=extra_switch_cmd)
Kuang-che Wu8b654092018-11-09 17:56:25 +0800176
177 diagnoser.switch_chromeos_to_old(force=self.config['always_reflash'])
Kuang-che Wud1b74152020-05-20 08:46:46 +0800178 util.check_call(*(common_switch_cmd +
179 [diagnoser.cros_old, '--dut', dut]))
Kuang-che Wuc8c495d2019-08-19 17:48:58 +0800180 dut_os_version = cros_util.query_dut_short_version(dut)
Kuang-che Wu8b654092018-11-09 17:56:25 +0800181
182 try:
183 if diagnoser.narrow_down_android(eval_cmd):
184 return
Kuang-che Wuec566f32019-03-07 16:53:32 +0800185 except errors.DiagnoseContradiction:
Kuang-che Wu8b654092018-11-09 17:56:25 +0800186 raise
187 except Exception:
Kuang-che Wuc93d0f92020-05-20 20:55:23 +0800188 diagnoser.make_decision(
Kuang-che Wue4435b42020-05-21 20:06:24 +0800189 'Exception in Android bisector before verification; '
190 'assume the culprit is not inside Android and continue')
Kuang-che Wu8b654092018-11-09 17:56:25 +0800191 # Assume it's ok to leave random version of android prebuilt on DUT.
192
Kuang-che Wuc8c495d2019-08-19 17:48:58 +0800193 # Sanity check. The OS version should not change after android bisect.
Kuang-che Wu523bdf22019-08-20 12:11:09 +0800194 assert dut_os_version == cros_util.query_dut_short_version(dut), \
Kuang-che Wu59400312020-11-10 14:53:54 +0800195 'Someone else reflashed the DUT. DUT locking is not respected?'
Kuang-che Wuc8c495d2019-08-19 17:48:58 +0800196
Kuang-che Wu8b654092018-11-09 17:56:25 +0800197 eval_cmd = common_eval_cmd + ['--prebuilt']
Kuang-che Wufd2ea5e2019-06-10 20:40:17 +0800198
Kuang-che Wuda9cb702019-11-11 14:40:56 +0800199 chrome_with_tests = bool(self.config['test_name']) # not CTS/GTS
200 if chrome_with_tests:
Kuang-che Wufd2ea5e2019-06-10 20:40:17 +0800201 # Now, the version of autotest on the DUT is unknown and may be even
202 # not installed. Invoke the test once here, so
203 # - make sure autotest-deps is installed, with expected version
Kuang-che Wuda9cb702019-11-11 14:40:56 +0800204 # - autotest-deps is installed first, so our chrome test binaries
Kuang-che Wufd2ea5e2019-06-10 20:40:17 +0800205 # won't be reset to default version during bisection.
206 # It's acceptable to spend extra time to run test once because
207 # - only few tests do so
208 # - tests are migrating away from autotest
209 util.call(*eval_cmd)
210
Kuang-che Wu8b654092018-11-09 17:56:25 +0800211 try:
Zheng-Jie Chang7a076a52020-05-31 13:03:37 +0800212 buildbucket_build = (
213 cros_util.is_buildbucket_buildable(self.config['old']) and
214 self.config['enable_buildbucket_chrome'])
Kuang-che Wu8b654092018-11-09 17:56:25 +0800215 if diagnoser.narrow_down_chrome(
Zheng-Jie Chang7a076a52020-05-31 13:03:37 +0800216 eval_cmd,
217 buildbucket_build=buildbucket_build,
218 with_tests=chrome_with_tests):
Kuang-che Wu8b654092018-11-09 17:56:25 +0800219 return
Kuang-che Wuec566f32019-03-07 16:53:32 +0800220 except errors.DiagnoseContradiction:
Kuang-che Wu8b654092018-11-09 17:56:25 +0800221 raise
222 except Exception:
Kuang-che Wuc93d0f92020-05-20 20:55:23 +0800223 diagnoser.make_decision(
Kuang-che Wue4435b42020-05-21 20:06:24 +0800224 'Exception in Chrome bisector before verification; '
225 'assume the culprit is not inside Chrome and continue')
Kuang-che Wu8b654092018-11-09 17:56:25 +0800226
Zheng-Jie Chang7a076a52020-05-31 13:03:37 +0800227 if not self.config['chrome_deploy_image'] and not buildbucket_build:
Kuang-che Wude5bfc32019-09-12 21:56:48 +0800228 # Sanity check. The OS version should not change after chrome bisect.
229 assert dut_os_version == cros_util.query_dut_short_version(dut), \
Kuang-che Wu59400312020-11-10 14:53:54 +0800230 'Someone else reflashed the DUT. DUT locking is not respected?'
Kuang-che Wuc8c495d2019-08-19 17:48:58 +0800231
Zheng-Jie Chang61a645a2020-04-29 10:12:13 +0800232 buildbucket_build = (
233 cros_util.is_buildbucket_buildable(self.config['old']) and
234 not self.config['disable_buildbucket_chromeos'])
235
236 if not buildbucket_build:
Zheng-Jie Chang181be6f2020-03-17 16:16:08 +0800237 eval_cmd = common_eval_cmd
238 extra_switch_cmd = None
Zheng-Jie Changd3fd8f12020-04-14 11:41:06 +0800239 else:
240 eval_cmd = common_eval_cmd + ['--prebuilt']
241 extra_switch_cmd = common_switch_cmd
Zheng-Jie Chang181be6f2020-03-17 16:16:08 +0800242 diagnoser.narrow_down_chromeos_localbuild(
Zheng-Jie Chang61a645a2020-04-29 10:12:13 +0800243 eval_cmd, buildbucket_build, extra_switch_cmd=extra_switch_cmd)
Kuang-che Wu8b654092018-11-09 17:56:25 +0800244 logger.info('%s done', __file__)
245 except Exception as e:
246 logger.exception('got exception; stop')
247 exception_name = e.__class__.__name__
248 self.states.add_history(
Kuang-che Wu0c2cb922020-06-16 23:34:53 +0800249 'failed',
250 text='%s: %s' % (exception_name, e),
251 exception=exception_name)
Kuang-che Wu8b654092018-11-09 17:56:25 +0800252
Kuang-che Wu32f27242019-05-16 17:34:50 +0800253 def create_argument_parser_hook(self, parser_init):
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800254 group = parser_init.add_argument_group(title='Options for CTS/GTS tests')
255 group.add_argument('--cts_revision', help='CTS revision, like "9.0_r3"')
Kuang-che Wu63f836a2019-02-21 16:33:32 +0000256 group.add_argument('--cts_abi', choices=['arm', 'x86'])
Kuang-che Wu85c613c2019-01-09 15:46:11 +0800257 group.add_argument(
258 '--cts_prefix',
259 help='Prefix of autotest test name, '
260 'like cheets_CTS_N, cheets_CTS_P, cheets_GTS')
261 group.add_argument(
262 '--cts_module', help='CTS/GTS module name, like "CtsCameraTestCases"')
263 group.add_argument(
264 '--cts_test',
265 help='CTS/GTS test name, like '
266 '"android.hardware.cts.CameraTest#testDisplayOrientation"')
267 group.add_argument('--cts_timeout', type=float, help='timeout, in seconds')
268
Kuang-che Wua41525a2018-10-17 23:52:24 +0800269 group = parser_init.add_argument_group(title='Options passed to test_that')
270 group.add_argument(
271 '--args',
272 help='Extra args passed to "test_that --args"; Overrides the default')
273
Kuang-che Wu1fcc0222018-07-07 16:43:22 +0800274
275if __name__ == '__main__':
Kuang-che Wu32f27242019-05-16 17:34:50 +0800276 DiagnoseAutotestCommandLine().main()