| #!/usr/bin/env python3 |
| # -*- coding: utf-8 -*- |
| # Copyright 2019 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """Diagnose ChromeOS tast regressions. |
| |
| This is integrated bisection utility. Given ChromeOS, Chrome, Android source |
| tree, and necessary parameters, this script can determine which components to |
| bisect, and hopefully output the culprit CL of regression. |
| |
| Sometimes the script failed to figure out the final CL for various reasons, it |
| will cut down the search range as narrow as it can. |
| """ |
| from __future__ import print_function |
| import logging |
| |
| from bisect_kit import cros_lab_util |
| from bisect_kit import cros_util |
| from bisect_kit import diagnoser_cros |
| from bisect_kit import errors |
| from bisect_kit import util |
| import setup_cros_bisect |
| |
| logger = logging.getLogger(__name__) |
| |
| |
| class DiagnoseTastCommandLine(diagnoser_cros.DiagnoseCommandLineBase): |
| """Diagnose command line interface.""" |
| |
| def check_options(self, opts, path_factory): |
| super().check_options(opts, path_factory) |
| if not opts.test_name: |
| self.argument_parser.error('argument --test_name is required') |
| |
| def init_hook(self, opts): |
| pass |
| |
| def _build_cmds(self): |
| # prebuilt version will be specified later. |
| common_switch_cmd = [ |
| './switch_tast_prebuilt.py', |
| '--chromeos_root', |
| self.config['chromeos_root'], |
| '--board', |
| self.config['board'], |
| ] |
| common_eval_cmd = [ |
| './eval_cros_tast.py', |
| '--with_private_bundles', |
| '--chromeos_root', self.config['chromeos_root'], |
| '--test_name', self.config['test_name'], |
| ] # yapf: disable |
| if self.config['metric']: |
| common_eval_cmd += [ |
| '--metric', self.config['metric'], |
| ] # yapf: disable |
| if self.config['fail_to_pass']: |
| common_eval_cmd.append('--fail_to_pass') |
| if self.config['reboot_before_test']: |
| common_eval_cmd.append('--reboot_before_test') |
| for var in self.config['extra_test_variables']: |
| common_eval_cmd.append('--args') |
| common_eval_cmd.append(var) |
| |
| return common_switch_cmd, common_eval_cmd |
| |
| def cmd_run(self, opts): |
| del opts # unused |
| |
| self.states.load() |
| |
| try: |
| path_factory = setup_cros_bisect.DefaultProjectPathFactory( |
| self.config['mirror_base'], self.config['work_base'], |
| self.config['session']) |
| common_switch_cmd, common_eval_cmd = self._build_cmds() |
| |
| lease_reason = cros_lab_util.make_lease_reason(self.config['session']) |
| with cros_lab_util.dut_manager( |
| self.config['dut'], |
| lease_reason, lambda: diagnoser_cros.grab_dut(self.config)) as dut: |
| if not dut: |
| raise errors.NoDutAvailable('unable to allocate DUT') |
| if not cros_util.is_good_dut(dut): |
| if not cros_lab_util.repair(dut): |
| raise errors.ExternalError('Not a good DUT and unable to repair') |
| assert cros_util.is_dut(dut) |
| if self.config['dut'] == cros_lab_util.LAB_DUT: |
| self.config['allocated_dut'] = dut |
| self.states.save() |
| common_eval_cmd.append(dut) |
| |
| diagnoser = diagnoser_cros.CrosDiagnoser(self.states, path_factory, |
| self.config, dut) |
| |
| eval_cmd = common_eval_cmd + ['--prebuilt'] |
| extra_switch_cmd = common_switch_cmd |
| diagnoser.narrow_down_chromeos_prebuilt( |
| self.config['old'], |
| self.config['new'], |
| eval_cmd, |
| extra_switch_cmd=extra_switch_cmd) |
| |
| diagnoser.switch_chromeos_to_old(force=self.config['always_reflash']) |
| util.check_call(*(common_switch_cmd + |
| [diagnoser.cros_old, '--dut', dut])) |
| dut_os_version = cros_util.query_dut_short_version(dut) |
| |
| try: |
| if diagnoser.narrow_down_android(common_eval_cmd): |
| return |
| except errors.DiagnoseContradiction: |
| raise |
| except Exception: |
| diagnoser.make_decision( |
| 'Exception in Android bisector before verification; ' |
| 'assume the culprit is not inside Android and continue') |
| # Assume it's ok to leave random version of android prebuilt on DUT. |
| |
| # Sanity check. The OS version should not change after android bisect. |
| assert dut_os_version == cros_util.query_dut_short_version(dut), ( |
| 'Someone else reflashed the DUT. DUT locking is not respected?') |
| |
| try: |
| buildbucket_build = ( |
| cros_util.is_buildbucket_buildable(self.config['old']) and |
| self.config['enable_buildbucket_chrome']) |
| if self.config['chrome_deploy_image']: |
| eval_cmd = common_eval_cmd + ['--tast_build'] |
| else: |
| eval_cmd = common_eval_cmd + ['--prebuilt'] |
| if diagnoser.narrow_down_chrome( |
| eval_cmd, buildbucket_build=buildbucket_build): |
| return |
| except errors.DiagnoseContradiction: |
| raise |
| except Exception: |
| diagnoser.make_decision( |
| 'Exception in Chrome bisector before verification; ' |
| 'assume the culprit is not inside Chrome and continue') |
| |
| if not self.config['chrome_deploy_image'] and not buildbucket_build: |
| # Sanity check. The OS version should not change after chrome bisect. |
| assert dut_os_version == cros_util.query_dut_short_version(dut), ( |
| 'Someone else reflashed the DUT. DUT locking is not respected?') |
| |
| buildbucket_build = ( |
| cros_util.is_buildbucket_buildable(self.config['old']) and |
| not self.config['disable_buildbucket_chromeos']) |
| |
| if not buildbucket_build: |
| eval_cmd = common_eval_cmd + ['--tast_build'] |
| extra_switch_cmd = None |
| else: |
| eval_cmd = common_eval_cmd + ['--prebuilt'] |
| extra_switch_cmd = common_switch_cmd |
| diagnoser.narrow_down_chromeos_localbuild( |
| eval_cmd, buildbucket_build, extra_switch_cmd=extra_switch_cmd) |
| logger.info('%s done', __file__) |
| except Exception as e: |
| logger.exception('got exception; stop') |
| exception_name = e.__class__.__name__ |
| self.states.add_history( |
| 'failed', |
| text='%s: %s' % (exception_name, e), |
| exception=exception_name) |
| |
| |
| if __name__ == '__main__': |
| DiagnoseTastCommandLine().main() |