blob: 04515b45aebf3c333c87dadd4c6fa6ace6f3c316 [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2018 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Diagnose ChromeOS autotest 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.
"""
import logging
import os
from bisect_kit import cros_lab_util
from bisect_kit import cros_util
from bisect_kit import diagnoser_cros
from bisect_kit import util
import cros_helper
import setup_cros_bisect
logger = logging.getLogger(__name__)
def get_test_dependency_labels(config):
# Assume "DEPENDENCIES" is identical between the period of
# `old` and `new` version.
autotest_dir = os.path.join(
config['chromeos_root'], cros_util.prebuilt_autotest_dir
)
info = cros_util.get_autotest_test_info(autotest_dir, config['test_name'])
assert info, 'incorrect test name? %s' % config['test_name']
extra_labels = []
dependencies = info.variables.get('DEPENDENCIES', '')
for label in dependencies.split(','):
label = label.strip()
# Skip non-machine labels
if not label or label in ['cleanup-reboot']:
continue
extra_labels.append(label)
return extra_labels
class DiagnoseAutotestCommandLine(diagnoser_cros.DiagnoseCommandLineBase):
"""Diagnose command line interface."""
def check_options(self, opts, path_factory):
super().check_options(opts, path_factory)
is_cts = (
opts.cts_revision
or opts.cts_abi
or opts.cts_prefix
or opts.cts_module
or opts.cts_test
or opts.cts_timeout
)
if is_cts:
if opts.test_name or opts.metric or opts.extra_test_variables:
self.argument_parser.error(
'do not specify --test_name, --metric, '
+ '--extra_test_variables for CTS/GTS tests'
)
if not opts.cts_prefix:
self.argument_parser.error(
'--cts_prefix should be specified for CTS/GTS tests'
)
if not opts.cts_module:
self.argument_parser.error(
'--cts_module should be specified for CTS/GTS tests'
)
opts.test_name = '%s.tradefed-run-test' % opts.cts_prefix
elif not opts.test_name:
self.argument_parser.error(
'--test_name should be specified if not CTS/GTS tests'
)
def init_hook(self, opts):
self.states.config.update(
cts_revision=opts.cts_revision,
cts_abi=opts.cts_abi,
cts_prefix=opts.cts_prefix,
cts_module=opts.cts_module,
cts_test=opts.cts_test,
cts_timeout=opts.cts_timeout,
)
if opts.dut == cros_lab_util.LAB_DUT:
raise NotImplementedError(
f'dut "{cros_lab_util.LAB_DUT}" is not supported yet'
)
# Unpack old autotest prebuilt, assume following information don't change
# between versions:
# - what chrome binaries to run
# - dependency labels for DUT allocation
common_switch_cmd, _common_eval_cmd = self._build_cmds()
util.check_call(
*(
common_switch_cmd
+ [self.config['cros_prebuilt_old'], '--dut', opts.dut]
)
)
def _build_cmds(self):
# prebuilt version will be specified later.
common_switch_cmd = [
'./switch_autotest_prebuilt.py',
'--rich_result',
'--chromeos_root',
self.config['chromeos_root'],
'--board',
self.config['board'],
]
if self.config['test_name'] and not self.config['cts_test']:
common_switch_cmd += ['--test_name', self.config['test_name']]
common_eval_cmd = [
'./eval_cros_autotest.py',
'--rich_result',
'--chromeos_root',
self.config['chromeos_root'],
] # yapf: disable
if self.config['test_name'] and not self.config['cts_test']:
common_eval_cmd += ['--test_name', self.config['test_name']]
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)
if self.config['test_name'].startswith('telemetry_'):
common_eval_cmd += ['--chrome_root', self.config['chrome_root']]
for arg_name in [
'cts_revision',
'cts_abi',
'cts_prefix',
'cts_module',
'cts_test',
'cts_timeout',
]:
if self.config.get(arg_name) is not None:
common_eval_cmd += [
'--%s' % arg_name,
str(self.config[arg_name]),
]
if self.config['consider_test_crash_as_failure']:
common_eval_cmd.append('--consider_test_crash_as_failure')
return common_switch_cmd, common_eval_cmd
def do_run(self, dut):
if (
dut != cros_lab_util.LAB_DUT
and self.config['dut'] == cros_lab_util.LAB_DUT
):
self.update_dut_alloc_spec(dut)
util.check_call(
'./switch_cros_localbuild.py',
'--nobuild',
'--chromeos_root',
self.config['chromeos_root'],
'--chromeos_mirror',
self.config['chromeos_mirror'],
'--board',
self.config['board'],
self.config['cros_prebuilt_new'],
)
path_factory = setup_cros_bisect.DefaultProjectPathFactory(
self.config['mirror_base'],
self.config['work_base'],
self.config['session'],
)
diagnoser = diagnoser_cros.CrosDiagnoser(
self.states, path_factory, self.config, dut
)
common_switch_cmd, common_eval_cmd = self._build_cmds()
common_eval_cmd.append(dut)
# Do not specify version for autotest prebuilt switching here. The trick
# is that version number is obtained via bisector's environment variable
# CROS_VERSION.
cros_prebuilt_eval_cmd = common_eval_cmd + ['--prebuilt']
cros_prebuilt_extra_switch_cmd = common_switch_cmd
android_prebuilt_eval_cmd = common_eval_cmd + ['--prebuilt']
lacros_prebuilt_eval_cmd = common_eval_cmd + ['--prebuilt']
chrome_localbuild_eval_cmd = common_eval_cmd + ['--prebuilt']
chrome_with_tests = bool(self.config['test_name']) # not CTS/GTS
cros_buildbucket_build = (
cros_util.is_buildbucket_buildable(self.config['cros_prebuilt_old'])
and not self.config['disable_buildbucket_chromeos']
)
if not cros_buildbucket_build:
cros_localbuild_eval_cmd = common_eval_cmd
cros_localbuild_extra_switch_cmd = None
else:
cros_localbuild_eval_cmd = common_eval_cmd + ['--prebuilt']
cros_localbuild_extra_switch_cmd = common_switch_cmd
diagnoser.diagnose(
autotest=True,
common_switch_cmd=common_switch_cmd,
cros_prebuilt_extra_switch_cmd=cros_prebuilt_extra_switch_cmd,
cros_prebuilt_eval_cmd=cros_prebuilt_eval_cmd,
android_prebuilt_eval_cmd=android_prebuilt_eval_cmd,
lacros_prebuilt_eval_cmd=lacros_prebuilt_eval_cmd,
chrome_localbuild_eval_cmd=chrome_localbuild_eval_cmd,
chrome_localbuild_with_tests=chrome_with_tests,
cros_localbuild_eval_cmd=cros_localbuild_eval_cmd,
cros_localbuild_extra_switch_cmd=cros_localbuild_extra_switch_cmd,
)
def cmd_run(self, opts):
del opts # unused
self.states.load_states()
try:
reason = cros_lab_util.make_lease_reason(self.config['session'])
def allocator():
return cros_helper.allocate_dut(self.dut_allocate_spec)
with cros_lab_util.dut_manager(
self.config['dut'], reason, allocator
) as dut:
self.do_run(dut)
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,
)
def create_argument_parser_hook(self, parser_init):
group = parser_init.add_argument_group(
title='Options for CTS/GTS tests'
)
group.add_argument('--cts_revision', help='CTS revision, like "9.0_r3"')
group.add_argument('--cts_abi', choices=['arm', 'x86'])
group.add_argument(
'--cts_prefix',
help='Prefix of autotest test name, '
'like cheets_CTS_N, cheets_CTS_P, cheets_GTS',
)
group.add_argument(
'--cts_module',
help='CTS/GTS module name, like "CtsCameraTestCases"',
)
group.add_argument(
'--cts_test',
help='CTS/GTS test name, like '
'"android.hardware.cts.CameraTest#testDisplayOrientation"',
)
group.add_argument(
'--cts_timeout', type=float, help='timeout, in seconds'
)
if __name__ == '__main__':
DiagnoseAutotestCommandLine().main()