blob: 21b020a6c6024e0cda3fcb17d6946583340715e4 [file] [log] [blame]
# -*- coding: utf-8 -*-
# Copyright 2018 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 regressions on top of subcomponent bisectors."""
from __future__ import print_function
import argparse
import datetime
import json
import logging
import os
import random
import sys
import time
from bisect_kit import cli
from bisect_kit import common
from bisect_kit import configure
from bisect_kit import core
from bisect_kit import cros_lab_util
from bisect_kit import cros_util
from bisect_kit import errors
from bisect_kit import util
from bisect_kit import wrapper
import cros_helper
import setup_cros_bisect
logger = logging.getLogger(__name__)
def grab_dut(config):
reason = cros_lab_util.make_lease_reason(config['session'])
if config.get('allocated_dut'):
host_name = cros_lab_util.dut_host_name(config['allocated_dut'])
logger.info('try to lease the same host (%s) as last run', host_name)
status = cros_lab_util.skylab_lease_dut(host_name, reason)
if not status:
return None
return host_name
# TODO(kcwu): support test-specific dependency labels
dimensions = ['dut_state:ready']
if config['model']:
dimensions.append('label-model:' + config['model'])
if config['sku']:
dimensions.append('label-hwid_sku:' +
cros_lab_util.normalize_sku_name(config['sku']))
if config['pool']:
dimensions.append('label-pool:' + config['pool'])
bots = cros_lab_util.swarming_bots_list(dimensions, is_busy=False)
if not bots:
bots = cros_lab_util.swarming_bots_list(dimensions)
if not bots:
raise errors.NoDutAvailable(
'no bots satisfy constraints; incorrect model/sku? %s' % dimensions)
bot = random.choice(bots)
host = bot['dimensions']['dut_name'][0]
logger.info('trying to lease %s', host)
if not cros_lab_util.skylab_lease_dut(host, reason):
logger.error('all DUTs are busy')
return None
logger.info('leased %s (bot_id=%s)', host, bot['bot_id'])
return host
class DiagnoseStates(core.States):
"""Diagnose states."""
def init(self, config):
self.set_data(dict(config=config, history=[]))
@property
def config(self):
return self.data['config']
def add_history(self, event, **kwargs):
entry = dict(timestamp=time.time(), event=event, **kwargs)
self.data['history'].append(entry)
self.save()
class CrosDiagnoser:
"""Integrated bisectors API for ChromeOS regression analysis."""
def __init__(self, states, path_factory, config, dut):
self.states = states
self.path_factory = path_factory
self.config = config
# self.noisy will be updated after we gather more information from each
# bisect session.
self.noisy = config['noisy']
self.dut = dut
self.cros_old = None
self.cros_new = None
self.old_info = None
self.new_info = None
def _verified_chromeos_prebuilt(self):
if self.config['bypass_chromeos_prebuilt']:
return 'assume'
return 'verified'
def make_decision(self, text):
if sys.exc_info() != (None, None, None):
logger.exception('decision: %s', text)
else:
logger.info('decision: %s', text)
self.states.add_history('decision', text=text)
def narrow_down_chromeos_prebuilt(self,
old,
new,
eval_cmd,
extra_switch_cmd=None):
"""Bisect with ChromeOS prebuilt.
Args:
old: old ChromeOS version
new: new ChromeOS version
eval_cmd: command to reproduce the regression
extra_switch_cmd: additional command to run when switch chromeos version
"""
if self.config['bypass_chromeos_prebuilt']:
self.make_decision(
'The bisection with Chrome OS prebuilt image is bypassed; '
'assume that old Chrome OS has old behavior and '
'new Chrome OS has new behavior')
self.cros_old = old
self.cros_new = new
else:
self.states.add_history(
'bisect',
text='bisect chromeos prebuilt',
bisector='bisect_cros_version',
session=self.config['session'])
bisector = wrapper.BisectorWrapper('bisect_cros_version',
self.config['session'])
switch_cmd = [
'./switch_cros_prebuilt.py', '--session', self.config['session']
]
if not self.config['disable_rootfs_verification']:
switch_cmd.append('--no_disable_rootfs_verification')
init_args = ['--dut', self.dut, '--board', self.config['board']]
if self.config['disable_snapshot']:
init_args.append('--disable_snapshot')
bisector.init_if_necessary(
old,
new,
init_args=init_args,
switch_cmd=switch_cmd,
eval_cmd=eval_cmd,
old_value=self.config['old_value'],
new_value=self.config['new_value'],
term_old=self.config['term_old'],
term_new=self.config['term_new'],
recompute_init_values=self.config['recompute_init_values'],
noisy=self.noisy)
if extra_switch_cmd:
bisector.append_switch_command(extra_switch_cmd)
self.cros_old, self.cros_new, self.noisy = bisector.narrow_down()
prebuilt_states = core.BisectStates.from_bisector_class(
'ChromeOSVersionDomain', self.config['session'])
if prebuilt_states.load():
cros_util.SnapshotStore.init_with_state(prebuilt_states)
self.old_info = cros_util.version_info(self.config['board'], self.cros_old)
self.new_info = cros_util.version_info(self.config['board'], self.cros_new)
logger.info('old: cros %s, chrome %s, android %s',
self.old_info.get('cros_full_version'),
self.old_info.get('cr_version'),
self.old_info.get('android_build_id'))
logger.info('new: cros %s, chrome %s, android %s',
self.new_info.get('cros_full_version'),
self.new_info.get('cr_version'),
self.new_info.get('android_build_id'))
def switch_chromeos_to_old(self, force=False):
"""Switch ChromeOS version to old prebuilt.
Args:
force: always do 'cros flash' no matter the current version number of DUT
"""
util.check_call('./switch_cros_localbuild.py', '--chromeos_root',
self.config['chromeos_root'], '--chromeos_mirror',
self.config['chromeos_mirror'], '--board',
self.config['board'], '--nobuild', self.cros_old)
if not force:
if cros_util.is_cros_full_version(self.cros_old):
version = cros_util.version_to_short(self.cros_old)
else:
version = self.cros_old
if cros_util.query_dut_prebuilt_version(self.dut) == version:
return
cmd = [
'./switch_cros_prebuilt.py', '--session', self.config['session'],
'--board', self.config['board'], '--dut', self.dut, self.cros_old
]
if not self.config['disable_rootfs_verification']:
cmd.append('--no_disable_rootfs_verification')
util.check_call(*cmd)
def _need_android_bisect(self, old_info, new_info):
"""Determine whether to do android bisect or not for given two versions.
Args:
old_info: old version info from cros_util.version_info()
new_info: new version info from cros_util.version_info()
Returns:
True if android bisect is necessary (ex. version not equal) and possible
(ex. same branch).
"""
if (old_info.get('android_build_id') is None or
new_info.get('android_build_id') is None):
self.make_decision(
'At least one version has no Android. skip Android bisect')
return False
if old_info['android_build_id'] == new_info['android_build_id']:
self.make_decision(
'The Android version is identical, no need to bisect Android')
return False
if old_info.get('android_branch') != new_info.get('android_branch'):
self.make_decision(
'Android branch mismatch: %s vs %s; unable to bisect Android' %
(old_info.get('android_branch'), new_info.get('android_branch')))
return False
return True
def narrow_down_android(self, eval_cmd, extra_switch_cmd=None):
"""Bisect with Android prebuilt and localbuild.
It's known that the version of ChromeOS has old behavior.
Args:
eval_cmd: command to reproduce the regression
extra_switch_cmd: additional command to run when switch android version
Returns:
True: culprit is inside Android
False: culprit is not inside Android or unable to bisect
"""
verified = False
if not self._need_android_bisect(self.old_info, self.new_info):
return verified
if (self.config['bypass_android_prebuilt'] and
self.config['bypass_android_build']):
self.make_decision('The bisection with Android is bypassed')
return verified
if self.config['bypass_android_prebuilt']:
self.make_decision('The bisection with Android prebuilt is bypassed')
android_old = self.old_info['android_build_id']
android_new = self.new_info['android_build_id']
else:
logger.info('bisect android prebuilt')
self.states.add_history(
'bisect',
text='bisect android prebuilt',
bisector='bisect_android_build_id',
session=self.config['session'])
bisector = wrapper.BisectorWrapper('bisect_android_build_id',
self.config['session'])
try:
bisector.init_if_necessary(
self.old_info['android_build_id'],
self.new_info['android_build_id'],
init_args=[
'--dut',
self.dut,
'--branch',
self.old_info['android_branch'],
],
switch_cmd=['./switch_arc_prebuilt.py'],
eval_cmd=eval_cmd,
old_value=self.config['old_value'],
new_value=self.config['new_value'],
term_old=self.config['term_old'],
term_new=self.config['term_new'],
recompute_init_values=self.config['recompute_init_values'],
noisy=self.noisy)
if extra_switch_cmd:
bisector.append_switch_command(extra_switch_cmd)
android_old, android_new, self.noisy = bisector.narrow_down()
verified = True
except errors.VerifyOldBehaviorFailed as e:
self.make_decision(
'Expect old Android has old behavior (%s) but failed' %
self.config['term_old'])
raise errors.DiagnoseContradiction(
'%s that old chromeos has old behavior (%s); '
'but it became new behavior (%s) '
'after deployed old android prebuilt' %
(self._verified_chromeos_prebuilt(), self.config['term_old'],
self.config['term_new'])) from e
except errors.VerifyNewBehaviorFailed:
self.make_decision('Unable to reproduce, the culprit is not in Android')
return verified
if self.config['bypass_android_build']:
self.make_decision('The bisection with Android local build is bypassed')
return verified
# TODO(kcwu): what if the branch name does not have 'git_' prefix
assert self.old_info['android_branch'].startswith('git_')
git_branch = self.old_info['android_branch'][4:]
android_mirror = self.config['android_mirror']
if not android_mirror:
android_mirror = self.path_factory.get_android_mirror(git_branch)
logger.info('android_mirror = %s', android_mirror)
android_root = self.config['android_root']
if not android_root:
android_root = self.path_factory.get_android_tree(git_branch)
logger.info('android_root = %s', android_root)
if not os.path.exists(android_mirror):
logger.info(
'android_mirror does not exist; skip android local build bisect')
return verified
if not os.path.exists(android_root):
logger.info(
'android_root does not exist; skip android local build bisect')
return verified
logger.info('bisect android local build')
self.states.add_history(
'bisect',
text='bisect android local build',
bisector='bisect_android_repo',
session=self.config['session'])
bisector = wrapper.BisectorWrapper('bisect_android_repo',
self.config['session'])
try:
bisector.init_if_necessary(
android_old,
android_new,
init_args=[
'--dut', self.dut, '--android_root', android_root,
'--android_mirror', android_mirror
],
switch_cmd=['./switch_arc_localbuild.py'],
eval_cmd=eval_cmd,
old_value=self.config['old_value'],
new_value=self.config['new_value'],
term_old=self.config['term_old'],
term_new=self.config['term_new'],
recompute_init_values=self.config['recompute_init_values'],
noisy=self.noisy)
if extra_switch_cmd:
bisector.append_switch_command(extra_switch_cmd)
android_old, android_new, self.noisy = bisector.narrow_down()
verified = True
except errors.VerificationFailed as e:
if verified:
self.make_decision(
'Verified with Android prebuilt but failed with local build')
raise errors.DiagnoseContradiction(
'verified that the issue could be reproduced with '
'android prebuilt; but unable to reproduce with local build')
if isinstance(e, errors.VerifyOldBehaviorFailed):
raise errors.DiagnoseContradiction(
'%s that old chromeos has old behavior (%s); '
'but it became new behavior (%s) '
'after deployed old android prebuilt' %
(self._verified_chromeos_prebuilt(), self.config['term_old'],
self.config['term_new'])) from e
self.make_decision('Unable to reproduce, the culprit is not in Android')
return verified
return verified
def narrow_down_chrome(self,
eval_cmd,
buildbucket_build=False,
extra_switch_cmd=None,
with_tests=True):
"""Bisect with Chrome localbuild.
It's known that the version of ChromeOS has old behavior.
Args:
eval_cmd: command to reproduce the regression
buildbucket_build: to build ChromeOS image on buildbucket
extra_switch_cmd: additional command to run when switch chrome version
with_tests: build test binaries as well
Returns:
True: culprit is inside Chrome
False: culprit is not inside Chrome or unable to bisect
"""
if self.old_info['cr_version'] == self.new_info['cr_version']:
self.make_decision(
'The Chrome version is identical, no need to bisect Chrome')
return False
if self.config['bypass_chrome_build']:
self.make_decision('The bisection with Chrome local build is bypassed')
return False
logger.info('bisect chrome local build')
self.states.add_history(
'bisect',
text='bisect chrome local build',
bisector='bisect_cr_localbuild_internal',
session=self.config['session'])
if buildbucket_build:
switch_cmd = [
'./switch_cros_localbuild_buildbucket.py',
'bisect_chrome',
'--dut',
self.dut,
'--chromeos_root',
self.config['chromeos_root'],
'--chromeos_mirror',
self.config['chromeos_mirror'],
'--chromeos_rev',
self.cros_old,
]
if not self.config['disable_rootfs_verification']:
switch_cmd.append('--no_disable_rootfs_verification')
else:
switch_cmd = [
'./switch_cros_cr_localbuild_internal.py', '--dut', self.dut
]
if not with_tests:
switch_cmd.append('--without_tests')
if self.config['chrome_deploy_image']:
switch_cmd += [
'--deploy_method=image',
'--chromeos_root',
self.config['chromeos_root'],
]
if not self.config['disable_rootfs_verification']:
switch_cmd.append('--no_disable_rootfs_verification')
bisector = wrapper.BisectorWrapper('bisect_cr_localbuild_internal',
self.config['session'])
try:
bisector.init_if_necessary(
self.old_info['cr_version'],
self.new_info['cr_version'],
init_args=[
'--chrome_root', self.config['chrome_root'], '--chrome_mirror',
self.config['chrome_mirror']
],
switch_cmd=switch_cmd,
eval_cmd=eval_cmd,
old_value=self.config['old_value'],
new_value=self.config['new_value'],
term_old=self.config['term_old'],
term_new=self.config['term_new'],
recompute_init_values=self.config['recompute_init_values'],
noisy=self.noisy)
if extra_switch_cmd:
bisector.append_switch_command(extra_switch_cmd)
bisector.narrow_down()
except errors.VerifyOldBehaviorFailed as e:
self.make_decision('Expect old Chrome has old behavior (%s) but failed' %
self.config['term_old'])
raise errors.DiagnoseContradiction(
'%s that old chrome os has old behavior (%s); '
'but it became new behavior (%s) after deployed chrome' %
(self._verified_chromeos_prebuilt(), self.config['term_old'],
self.config['term_new'])) from e
except errors.VerifyNewBehaviorFailed:
self.make_decision('Unable to reproduce, the culprit is not in Chrome')
return False
return True
def narrow_down_chromeos_localbuild(self,
eval_cmd,
buildbucket_build=False,
extra_switch_cmd=None):
"""Bisect with ChromeOS localbuild.
Args:
eval_cmd: command to reproduce the regression
buildbucket_build: to build ChromeOS image on buildbucket
extra_switch_cmd: additional command to run when switch ChromeOS version
"""
if self.config['bypass_chromeos_build']:
self.make_decision('The bisection with Chrome OS local build is bypassed')
return
self.states.add_history(
'bisect',
text='bisect chromeos local build',
bisector='bisect_cros_repo',
session=self.config['session'])
bisector = wrapper.BisectorWrapper('bisect_cros_repo',
self.config['session'])
if not buildbucket_build:
switch_cmd = ['./switch_cros_localbuild.py']
else:
switch_cmd = [
'./switch_cros_localbuild_buildbucket.py', 'bisect_chromeos'
]
if not self.config['disable_rootfs_verification']:
switch_cmd.append('--no_disable_rootfs_verification')
bisector.init_if_necessary(
self.cros_old,
self.cros_new,
init_args=[
'--dut', self.dut, '--board', self.config['board'],
'--chromeos_root', self.config['chromeos_root'],
'--chromeos_mirror', self.config['chromeos_mirror']
],
switch_cmd=switch_cmd,
eval_cmd=eval_cmd,
old_value=self.config['old_value'],
new_value=self.config['new_value'],
term_old=self.config['term_old'],
term_new=self.config['term_new'],
recompute_init_values=self.config['recompute_init_values'],
noisy=self.noisy)
if extra_switch_cmd:
bisector.append_switch_command(extra_switch_cmd)
try:
bisector.narrow_down()
except errors.VerifyNewBehaviorFailed as e:
raise errors.DiagnoseContradiction(
'%s that the issue could be reproduced with chromeos prebuilt; '
'but unable to reproduce with local build' %
self._verified_chromeos_prebuilt()) from e
except errors.VerifyOldBehaviorFailed as e:
raise errors.DiagnoseContradiction(
'%s that the issue could only be reproduced with new chromeos '
'prebuilt; but reproduced with both old and new local build' %
self._verified_chromeos_prebuilt()) from e
def cmd_log(self, json_output=False):
result = []
history = self.states.data['history']
for i, entry in enumerate(history):
if json_output:
entry = entry.copy()
result.append(entry)
else:
entry_time = datetime.datetime.fromtimestamp(int(entry['timestamp']))
print(entry_time, entry['text'])
# Special case, combine bisector log nestedly.
if entry.get('event') == 'bisect':
bisector = wrapper.BisectorWrapper(entry['bisector'], entry['session'])
try:
cmd = ['log', '--after', str(entry['timestamp'])]
if len(history) > i + 1:
cmd += ['--before', str(history[i + 1]['timestamp'])]
if json_output:
cmd.append('--json')
output = bisector.call(*cmd, capture_output=True)
entry['log'] = json.loads(output)
else:
bisector.call(*cmd)
except errors.Uninitialized:
# if 'init' failed, skip
continue
if json_output:
print(json.dumps(result, indent=2))
def cmd_view(self, json_output=False, verbose=False):
result = []
# Analyze log to determine what to show
bisects = []
history = self.states.data['history']
seen = set()
for entry in history:
if entry.get('event') != 'bisect':
continue
bisector = entry['bisector']
if bisector in seen:
continue
seen.add(bisector)
bisects.append(entry)
for entry in bisects:
bisector = wrapper.BisectorWrapper(entry['bisector'], entry['session'])
try:
cmd = ['view']
if verbose:
cmd.append('--verbose')
if json_output:
cmd.append('--json')
output = bisector.call(*cmd, capture_output=True)
result.append({
'bisector': entry['bisector'],
'view': json.loads(output),
})
else:
print('bisector: %s' % entry['bisector'])
bisector.call(*cmd)
print('=' * 60)
except errors.Uninitialized:
# if 'init' failed, skip
continue
if json_output:
print(json.dumps(result, indent=2))
class DiagnoseCommandLineBase:
"""Diagnose command line interface."""
def __init__(self):
common.init()
self.argument_parser = self.create_argument_parser()
self.states = None
@property
def config(self):
return self.states.config
def check_options(self, opts, path_factory):
if not opts.chromeos_mirror:
opts.chromeos_mirror = path_factory.get_chromeos_mirror()
logger.info('chromeos_mirror = %s', opts.chromeos_mirror)
if not opts.chromeos_root:
opts.chromeos_root = path_factory.get_chromeos_tree()
logger.info('chromeos_root = %s', opts.chromeos_root)
if not opts.chrome_mirror:
opts.chrome_mirror = path_factory.get_chrome_cache()
logger.info('chrome_mirror = %s', opts.chrome_mirror)
if not opts.chrome_root:
opts.chrome_root = path_factory.get_chrome_tree()
logger.info('chrome_root = %s', opts.chrome_root)
if not cros_util.is_ancestor_version(opts.old, opts.new):
self.argument_parser.error('%s is not ancestor of %s' %
(opts.old, opts.new))
if opts.dut == cros_lab_util.LAB_DUT:
if not opts.model and not opts.sku:
self.argument_parser.error(
'either --model or --sku need to be specified if DUT is "%s"' %
cros_lab_util.LAB_DUT)
# Board name cannot be deduced from auto allocated devices because they
# may be provisioned with image of unexpected board.
if not opts.board:
self.argument_parser.error(
'--board need to be specified if DUT is "%s"' %
cros_lab_util.LAB_DUT)
else:
if not opts.board:
opts.board = cros_util.query_dut_board(opts.dut)
names = cros_util.list_board_names(opts.chromeos_root)
if opts.board not in names:
util.show_similar_candidates('board name', opts.board, names)
self.argument_parser.error('incorrect board name: %r' % opts.board)
if cros_util.is_cros_short_version(opts.old):
opts.old = cros_util.version_to_full(opts.board, opts.old)
if cros_util.is_cros_short_version(opts.new):
opts.new = cros_util.version_to_full(opts.board, opts.new)
if not cros_util.has_test_image(opts.board, opts.old):
self.argument_parser.error('--old: %s has no image for %s' %
(opts.board, opts.old))
if not cros_util.has_test_image(opts.board, opts.new):
self.argument_parser.error('--new: %s has no image for %s' %
(opts.board, opts.new))
if opts.metric:
if opts.old_value is None:
self.argument_parser.error('--old_value is not provided')
if opts.new_value is None:
self.argument_parser.error('--new_value is not provided')
if opts.fail_to_pass:
self.argument_parser.error(
'--fail_to_pass is not for benchmark test (--metric)')
else:
if opts.old_value is not None:
self.argument_parser.error(
'--old_value is provided but --metric is not')
if opts.new_value is not None:
self.argument_parser.error(
'--new_value is provided but --metric is not')
if opts.recompute_init_values:
self.argument_parser.error(
'--recompute_init_values is provided but --metric is not')
if (opts.term_old is None and opts.term_new is not None or
opts.term_old is not None and opts.term_new is None):
self.argument_parser.error(
'--term_old and --term_new must be both specified or both blank')
if not opts.term_old:
if opts.metric:
if opts.old_value < opts.new_value:
opts.term_old = 'LOW'
opts.term_new = 'HIGH'
else:
opts.term_old = 'HIGH'
opts.term_new = 'LOW'
else:
if opts.fail_to_pass:
opts.term_old = 'FAIL'
opts.term_new = 'PASS'
else:
opts.term_old = 'PASS'
opts.term_new = 'FAIL'
def init_hook(self, opts):
pass # implemented by subclass if necessary
def cmd_init(self, opts):
path_factory = setup_cros_bisect.DefaultProjectPathFactory(
opts.mirror_base, opts.work_base, opts.session)
self.check_options(opts, path_factory)
if (opts.dut != cros_lab_util.LAB_DUT and
not cros_util.is_good_dut(opts.dut)):
if not cros_lab_util.repair(opts.dut):
raise ValueError('DUT is in bad state')
config = dict(
session=opts.session,
mirror_base=opts.mirror_base,
work_base=opts.work_base,
chromeos_root=opts.chromeos_root,
chromeos_mirror=opts.chromeos_mirror,
chrome_root=opts.chrome_root,
chrome_mirror=opts.chrome_mirror,
android_root=opts.android_root,
android_mirror=opts.android_mirror,
dut=opts.dut,
pool=opts.pool,
model=opts.model,
sku=opts.sku,
board=opts.board,
old=opts.old,
new=opts.new,
term_old=opts.term_old,
term_new=opts.term_new,
test_name=opts.test_name,
fail_to_pass=opts.fail_to_pass,
metric=opts.metric,
old_value=opts.old_value,
new_value=opts.new_value,
recompute_init_values=opts.recompute_init_values,
noisy=opts.noisy,
always_reflash=opts.always_reflash,
reboot_before_test=opts.reboot_before_test,
bypass_chromeos_prebuilt=opts.bypass_chromeos_prebuilt,
bypass_chromeos_build=opts.bypass_chromeos_build,
bypass_chrome_build=opts.bypass_chrome_build,
bypass_android_prebuilt=opts.bypass_android_prebuilt,
bypass_android_build=opts.bypass_android_build,
disable_rootfs_verification=opts.disable_rootfs_verification,
chrome_deploy_image=opts.chrome_deploy_image,
disable_snapshot=opts.disable_snapshot,
disable_buildbucket_chromeos=opts.disable_buildbucket_chromeos,
enable_buildbucket_chrome=opts.enable_buildbucket_chrome,
extra_test_variables=opts.extra_test_variables,
)
self.states.init(config)
self.init_hook(opts)
self.states.save()
def cmd_run(self, opts):
raise NotImplementedError # implemented by subclass
def cmd_log(self, opts):
self.states.load()
path_factory = setup_cros_bisect.DefaultProjectPathFactory(
self.config['mirror_base'], self.config['work_base'],
self.config['session'])
diagnoser = CrosDiagnoser(self.states, path_factory, self.config, None)
diagnoser.cmd_log(opts.json)
def cmd_view(self, opts):
self.states.load()
path_factory = setup_cros_bisect.DefaultProjectPathFactory(
self.config['mirror_base'], self.config['work_base'],
self.config['session'])
diagnoser = CrosDiagnoser(self.states, path_factory, self.config, None)
diagnoser.cmd_view(opts.json, opts.verbose)
def create_argument_parser_hook(self, parser_init):
pass # implemented by subclass if necessary
def create_argument_parser(self):
parents = [common.common_argument_parser, common.session_optional_parser]
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(
dest='command', title='commands', metavar='<command>', required=True)
parser_init = subparsers.add_parser(
'init', help='Initialize', parents=parents)
group = parser_init.add_argument_group(
title='Source tree path options',
description="""
Specify the paths of chromeos/chrome/android mirror and checkout. They
have the same default values as setup_cros_bisect.py, so usually you can
omit them and it just works.
""")
group.add_argument(
'--mirror_base',
metavar='MIRROR_BASE',
default=configure.get('MIRROR_BASE',
setup_cros_bisect.DEFAULT_MIRROR_BASE),
help='Directory for mirrors (default: %(default)s)')
group.add_argument(
'--work_base',
metavar='WORK_BASE',
default=configure.get('WORK_BASE', setup_cros_bisect.DEFAULT_WORK_BASE),
help='Directory for bisection working directories '
'(default: %(default)s)')
group.add_argument(
'--chromeos_root',
metavar='CHROMEOS_ROOT',
type=cli.argtype_dir_path,
default=configure.get('CHROMEOS_ROOT'),
help='ChromeOS tree root')
group.add_argument(
'--chromeos_mirror',
type=cli.argtype_dir_path,
default=configure.get('CHROMEOS_MIRROR'),
help='ChromeOS repo mirror path')
group.add_argument(
'--android_root',
metavar='ANDROID_ROOT',
type=cli.argtype_dir_path,
default=configure.get('ANDROID_ROOT'),
help='Android tree root')
group.add_argument(
'--android_mirror',
type=cli.argtype_dir_path,
default=configure.get('ANDROID_MIRROR'),
help='Android repo mirror path')
group.add_argument(
'--chrome_root',
metavar='CHROME_ROOT',
type=cli.argtype_dir_path,
default=configure.get('CHROME_ROOT'),
help='Chrome tree root')
group.add_argument(
'--chrome_mirror',
metavar='CHROME_MIRROR',
type=cli.argtype_dir_path,
default=configure.get('CHROME_MIRROR'),
help="chrome's gclient cache dir")
group = parser_init.add_argument_group(title='DUT allocation options')
group.add_argument(
'--dut',
metavar='DUT',
required=True,
help='Address of DUT (Device Under Test). If "%s", DUT will be '
'automatically allocated from the lab' % cros_lab_util.LAB_DUT)
group.add_argument(
'--pool',
type=cli.argtype_notempty,
default=cros_helper.DEFAULT_DUT_POOL,
help='Desired pools (default: %(default)s)')
group.add_argument(
'--model',
metavar='MODEL',
help='"model" criteria if DUT is auto allocated from the lab')
group.add_argument(
'--sku',
metavar='SKU',
help='"sku" criteria if DUT is auto allocated from the lab')
group = parser_init.add_argument_group(title='Essential options')
group.add_argument(
'--board',
metavar='BOARD',
default=configure.get('BOARD'),
help='ChromeOS board name; auto detected if DUT is not auto allocated')
group.add_argument(
'--old',
type=cros_util.argtype_cros_version,
required=True,
help='ChromeOS version with old behavior')
group.add_argument(
'--new',
type=cros_util.argtype_cros_version,
required=True,
help='ChromeOS version with new behavior')
group.add_argument('--term_old', help='Alternative term for "old" state')
group.add_argument('--term_new', help='Alternative term for "new" state')
group = parser_init.add_argument_group(
title='Options for normal autotest tests')
group.add_argument('--test_name', help='Test name')
group.add_argument(
'--fail_to_pass',
action='store_true',
help='For functional tests: bisect the CL fixed the regression (when '
'test became PASS). If not specified, the default is to bisect the CL '
'which broke the test (when test became FAIL)')
group.add_argument('--metric', help='Metric name of benchmark test')
group.add_argument(
'--old_value',
type=float,
help='For benchmark test, old value of metric')
group.add_argument(
'--new_value',
type=float,
help='For benchmark test, new value of metric')
group.add_argument(
'--recompute_init_values',
action='store_true',
help='For performance test, recompute initial values')
self.create_argument_parser_hook(parser_init)
group = parser_init.add_argument_group(title='Bisect behavior options')
group.add_argument(
'--noisy',
help='Enable noisy binary search. Example value: "old=1/10,new=2/3"')
group.add_argument(
'--always_reflash',
action='store_true',
help='Do not trust ChromeOS version number of DUT and always reflash. '
'This is usually only needed when resume because previous bisect was '
'interrupted and the DUT may be in an unexpected state')
group.add_argument(
'--reboot_before_test',
action='store_true',
help='Reboot before test run')
group.add_argument(
'--bypass_chromeos_prebuilt',
action='store_true',
help='Bypass chromeos prebuilt image bisect')
group.add_argument(
'--bypass_chromeos_build',
action='store_true',
help='Bypass chromeos local build bisect')
group.add_argument(
'--bypass_chrome_build',
action='store_true',
help='Bypass chrome local build bisect')
group.add_argument(
'--bypass_android_prebuilt',
action='store_true',
help='Bypass android prebuilt image bisect')
group.add_argument(
'--bypass_android_build',
action='store_true',
help='Bypass chromeos local build bisect')
group.add_argument(
'--no-disable-rootfs-verification',
'--no_disable_rootfs_verification',
dest='disable_rootfs_verification',
action='store_false',
help="Don't disable rootfs verification after update is completed")
group.add_argument(
'--chrome_deploy_image',
action='store_true',
help='Build and deploy ChromeOS image when bisect Chrome')
group.add_argument(
'--disable_snapshot',
action='store_true',
help='Disable snapshot for bisect chromeos prebuilt')
group.add_argument(
'--disable_buildbucket_chromeos',
action='store_true',
help='Build ChromeOS on local instead of buildbucket')
group.add_argument(
'--enable_buildbucket_chrome',
action='store_true',
help='Build Chrome on buildbucket')
group.add_argument(
'--extra_test_variables',
help='Extra variables passed to `test_that --args` or `tast -var`',
action='append',
default=[])
parser_init.set_defaults(func=self.cmd_init)
parser_run = subparsers.add_parser(
'run', help='Start auto bisection', parents=parents)
parser_run.set_defaults(func=self.cmd_run)
parser_log = subparsers.add_parser(
'log', help='Prints what has been done so far', parents=parents)
parser_log.add_argument(
'--json', action='store_true', help='Machine readable output')
parser_log.set_defaults(func=self.cmd_log)
parser_view = subparsers.add_parser(
'view', help='Prints summary of current status', parents=parents)
parser_view.add_argument('--verbose', '-v', action='store_true')
parser_view.add_argument(
'--json', action='store_true', help='Machine readable output')
parser_view.set_defaults(func=self.cmd_view)
return parser
def main(self, args=None):
opts = self.argument_parser.parse_args(args)
common.config_logging(opts)
session_dir = common.determine_session_dir(opts.session)
session_file = os.path.join(session_dir, self.__class__.__name__)
self.states = DiagnoseStates(session_file)
opts.func(opts)