blob: 77b7d93cd58ea506846271626229fb4a1b0999d7 [file] [log] [blame]
#!/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()