You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
398 lines
13 KiB
398 lines
13 KiB
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
# Copyright 2020 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.
|
|
|
|
"""The unified package/object bisecting tool."""
|
|
|
|
from __future__ import print_function
|
|
|
|
import abc
|
|
import argparse
|
|
from argparse import RawTextHelpFormatter
|
|
import os
|
|
import sys
|
|
|
|
from binary_search_tool import binary_search_state
|
|
from binary_search_tool import common
|
|
|
|
from cros_utils import command_executer
|
|
from cros_utils import logger
|
|
|
|
|
|
class Bisector(object, metaclass=abc.ABCMeta):
|
|
"""The abstract base class for Bisectors."""
|
|
|
|
def __init__(self, options, overrides=None):
|
|
"""Constructor for Bisector abstract base class
|
|
|
|
Args:
|
|
options: positional arguments for specific mode (board, remote, etc.)
|
|
overrides: optional dict of overrides for argument defaults
|
|
"""
|
|
self.options = options
|
|
self.overrides = overrides
|
|
if not overrides:
|
|
self.overrides = {}
|
|
self.logger = logger.GetLogger()
|
|
self.ce = command_executer.GetCommandExecuter()
|
|
|
|
def _PrettyPrintArgs(self, args, overrides):
|
|
"""Output arguments in a nice, human readable format
|
|
|
|
Will print and log all arguments for the bisecting tool and make note of
|
|
which arguments have been overridden.
|
|
|
|
Example output:
|
|
./run_bisect.py package daisy 172.17.211.184 -I "" -t cros_pkg/my_test.sh
|
|
Performing ChromeOS Package bisection
|
|
Method Config:
|
|
board : daisy
|
|
remote : 172.17.211.184
|
|
|
|
Bisection Config: (* = overridden)
|
|
get_initial_items : cros_pkg/get_initial_items.sh
|
|
switch_to_good : cros_pkg/switch_to_good.sh
|
|
switch_to_bad : cros_pkg/switch_to_bad.sh
|
|
* test_setup_script :
|
|
* test_script : cros_pkg/my_test.sh
|
|
prune : True
|
|
noincremental : False
|
|
file_args : True
|
|
|
|
Args:
|
|
args: The args to be given to binary_search_state.Run. This represents
|
|
how the bisection tool will run (with overridden arguments already
|
|
added in).
|
|
overrides: The dict of overriden arguments provided by the user. This is
|
|
provided so the user can be told which arguments were
|
|
overriden and with what value.
|
|
"""
|
|
# Output method config (board, remote, etc.)
|
|
options = vars(self.options)
|
|
out = '\nPerforming %s bisection\n' % self.method_name
|
|
out += 'Method Config:\n'
|
|
max_key_len = max([len(str(x)) for x in options.keys()])
|
|
for key in sorted(options):
|
|
val = options[key]
|
|
key_str = str(key).rjust(max_key_len)
|
|
val_str = str(val)
|
|
out += ' %s : %s\n' % (key_str, val_str)
|
|
|
|
# Output bisection config (scripts, prune, etc.)
|
|
out += '\nBisection Config: (* = overridden)\n'
|
|
max_key_len = max([len(str(x)) for x in args.keys()])
|
|
# Print args in common._ArgsDict order
|
|
args_order = [x['dest'] for x in common.GetArgsDict().values()]
|
|
for key in sorted(args, key=args_order.index):
|
|
val = args[key]
|
|
key_str = str(key).rjust(max_key_len)
|
|
val_str = str(val)
|
|
changed_str = '*' if key in overrides else ' '
|
|
|
|
out += ' %s %s : %s\n' % (changed_str, key_str, val_str)
|
|
|
|
out += '\n'
|
|
self.logger.LogOutput(out)
|
|
|
|
def ArgOverride(self, args, overrides, pretty_print=True):
|
|
"""Override arguments based on given overrides and provide nice output
|
|
|
|
Args:
|
|
args: dict of arguments to be passed to binary_search_state.Run (runs
|
|
dict.update, causing args to be mutated).
|
|
overrides: dict of arguments to update args with
|
|
pretty_print: if True print out args/overrides to user in pretty format
|
|
"""
|
|
args.update(overrides)
|
|
if pretty_print:
|
|
self._PrettyPrintArgs(args, overrides)
|
|
|
|
@abc.abstractmethod
|
|
def PreRun(self):
|
|
pass
|
|
|
|
@abc.abstractmethod
|
|
def Run(self):
|
|
pass
|
|
|
|
@abc.abstractmethod
|
|
def PostRun(self):
|
|
pass
|
|
|
|
|
|
class BisectPackage(Bisector):
|
|
"""The class for package bisection steps."""
|
|
|
|
cros_pkg_setup = 'cros_pkg/setup.sh'
|
|
cros_pkg_cleanup = 'cros_pkg/%s_cleanup.sh'
|
|
|
|
def __init__(self, options, overrides):
|
|
super(BisectPackage, self).__init__(options, overrides)
|
|
self.method_name = 'ChromeOS Package'
|
|
self.default_kwargs = {
|
|
'get_initial_items': 'cros_pkg/get_initial_items.sh',
|
|
'switch_to_good': 'cros_pkg/switch_to_good.sh',
|
|
'switch_to_bad': 'cros_pkg/switch_to_bad.sh',
|
|
'test_setup_script': 'cros_pkg/test_setup.sh',
|
|
'test_script': 'cros_pkg/interactive_test.sh',
|
|
'noincremental': False,
|
|
'prune': True,
|
|
'file_args': True
|
|
}
|
|
self.setup_cmd = ('%s %s %s' % (self.cros_pkg_setup, self.options.board,
|
|
self.options.remote))
|
|
self.ArgOverride(self.default_kwargs, self.overrides)
|
|
|
|
def PreRun(self):
|
|
ret, _, _ = self.ce.RunCommandWExceptionCleanup(
|
|
self.setup_cmd, print_to_console=True)
|
|
if ret:
|
|
self.logger.LogError('Package bisector setup failed w/ error %d' % ret)
|
|
return 1
|
|
return 0
|
|
|
|
def Run(self):
|
|
return binary_search_state.Run(**self.default_kwargs)
|
|
|
|
def PostRun(self):
|
|
cmd = self.cros_pkg_cleanup % self.options.board
|
|
ret, _, _ = self.ce.RunCommandWExceptionCleanup(cmd, print_to_console=True)
|
|
if ret:
|
|
self.logger.LogError('Package bisector cleanup failed w/ error %d' % ret)
|
|
return 1
|
|
|
|
self.logger.LogOutput(('Cleanup successful! To restore the bisection '
|
|
'environment run the following:\n'
|
|
' cd %s; %s') % (os.getcwd(), self.setup_cmd))
|
|
return 0
|
|
|
|
|
|
class BisectObject(Bisector):
|
|
"""The class for object bisection steps."""
|
|
|
|
sysroot_wrapper_setup = 'sysroot_wrapper/setup.sh'
|
|
sysroot_wrapper_cleanup = 'sysroot_wrapper/cleanup.sh'
|
|
|
|
def __init__(self, options, overrides):
|
|
super(BisectObject, self).__init__(options, overrides)
|
|
self.method_name = 'ChromeOS Object'
|
|
self.default_kwargs = {
|
|
'get_initial_items': 'sysroot_wrapper/get_initial_items.sh',
|
|
'switch_to_good': 'sysroot_wrapper/switch_to_good.sh',
|
|
'switch_to_bad': 'sysroot_wrapper/switch_to_bad.sh',
|
|
'test_setup_script': 'sysroot_wrapper/test_setup.sh',
|
|
'test_script': 'sysroot_wrapper/interactive_test.sh',
|
|
'noincremental': False,
|
|
'prune': True,
|
|
'file_args': True
|
|
}
|
|
self.options = options
|
|
if options.dir:
|
|
os.environ['BISECT_DIR'] = options.dir
|
|
self.options.dir = os.environ.get('BISECT_DIR', '/tmp/sysroot_bisect')
|
|
self.setup_cmd = (
|
|
'%s %s %s %s' % (self.sysroot_wrapper_setup, self.options.board,
|
|
self.options.remote, self.options.package))
|
|
|
|
self.ArgOverride(self.default_kwargs, overrides)
|
|
|
|
def PreRun(self):
|
|
ret, _, _ = self.ce.RunCommandWExceptionCleanup(
|
|
self.setup_cmd, print_to_console=True)
|
|
if ret:
|
|
self.logger.LogError('Object bisector setup failed w/ error %d' % ret)
|
|
return 1
|
|
|
|
os.environ['BISECT_STAGE'] = 'TRIAGE'
|
|
return 0
|
|
|
|
def Run(self):
|
|
return binary_search_state.Run(**self.default_kwargs)
|
|
|
|
def PostRun(self):
|
|
cmd = self.sysroot_wrapper_cleanup
|
|
ret, _, _ = self.ce.RunCommandWExceptionCleanup(cmd, print_to_console=True)
|
|
if ret:
|
|
self.logger.LogError('Object bisector cleanup failed w/ error %d' % ret)
|
|
return 1
|
|
self.logger.LogOutput(('Cleanup successful! To restore the bisection '
|
|
'environment run the following:\n'
|
|
' cd %s; %s') % (os.getcwd(), self.setup_cmd))
|
|
return 0
|
|
|
|
|
|
class BisectAndroid(Bisector):
|
|
"""The class for Android bisection steps."""
|
|
|
|
android_setup = 'android/setup.sh'
|
|
android_cleanup = 'android/cleanup.sh'
|
|
default_dir = os.path.expanduser('~/ANDROID_BISECT')
|
|
|
|
def __init__(self, options, overrides):
|
|
super(BisectAndroid, self).__init__(options, overrides)
|
|
self.method_name = 'Android'
|
|
self.default_kwargs = {
|
|
'get_initial_items': 'android/get_initial_items.sh',
|
|
'switch_to_good': 'android/switch_to_good.sh',
|
|
'switch_to_bad': 'android/switch_to_bad.sh',
|
|
'test_setup_script': 'android/test_setup.sh',
|
|
'test_script': 'android/interactive_test.sh',
|
|
'prune': True,
|
|
'file_args': True,
|
|
'noincremental': False,
|
|
}
|
|
self.options = options
|
|
if options.dir:
|
|
os.environ['BISECT_DIR'] = options.dir
|
|
self.options.dir = os.environ.get('BISECT_DIR', self.default_dir)
|
|
|
|
num_jobs = "NUM_JOBS='%s'" % self.options.num_jobs
|
|
device_id = ''
|
|
if self.options.device_id:
|
|
device_id = "ANDROID_SERIAL='%s'" % self.options.device_id
|
|
|
|
self.setup_cmd = ('%s %s %s %s' % (num_jobs, device_id, self.android_setup,
|
|
self.options.android_src))
|
|
|
|
self.ArgOverride(self.default_kwargs, overrides)
|
|
|
|
def PreRun(self):
|
|
ret, _, _ = self.ce.RunCommandWExceptionCleanup(
|
|
self.setup_cmd, print_to_console=True)
|
|
if ret:
|
|
self.logger.LogError('Android bisector setup failed w/ error %d' % ret)
|
|
return 1
|
|
|
|
os.environ['BISECT_STAGE'] = 'TRIAGE'
|
|
return 0
|
|
|
|
def Run(self):
|
|
return binary_search_state.Run(**self.default_kwargs)
|
|
|
|
def PostRun(self):
|
|
cmd = self.android_cleanup
|
|
ret, _, _ = self.ce.RunCommandWExceptionCleanup(cmd, print_to_console=True)
|
|
if ret:
|
|
self.logger.LogError('Android bisector cleanup failed w/ error %d' % ret)
|
|
return 1
|
|
self.logger.LogOutput(('Cleanup successful! To restore the bisection '
|
|
'environment run the following:\n'
|
|
' cd %s; %s') % (os.getcwd(), self.setup_cmd))
|
|
return 0
|
|
|
|
|
|
def Run(bisector):
|
|
log = logger.GetLogger()
|
|
|
|
log.LogOutput('Setting up Bisection tool')
|
|
ret = bisector.PreRun()
|
|
if ret:
|
|
return ret
|
|
|
|
log.LogOutput('Running Bisection tool')
|
|
ret = bisector.Run()
|
|
if ret:
|
|
return ret
|
|
|
|
log.LogOutput('Cleaning up Bisection tool')
|
|
ret = bisector.PostRun()
|
|
if ret:
|
|
return ret
|
|
|
|
return 0
|
|
|
|
|
|
_HELP_EPILOG = """
|
|
Run ./run_bisect.py {method} --help for individual method help/args
|
|
|
|
------------------
|
|
|
|
See README.bisect for examples on argument overriding
|
|
|
|
See below for full override argument reference:
|
|
"""
|
|
|
|
|
|
def Main(argv):
|
|
override_parser = argparse.ArgumentParser(
|
|
add_help=False,
|
|
argument_default=argparse.SUPPRESS,
|
|
usage='run_bisect.py {mode} [options]')
|
|
common.BuildArgParser(override_parser, override=True)
|
|
|
|
epilog = _HELP_EPILOG + override_parser.format_help()
|
|
parser = argparse.ArgumentParser(
|
|
epilog=epilog, formatter_class=RawTextHelpFormatter)
|
|
subparsers = parser.add_subparsers(
|
|
title='Bisect mode',
|
|
description=('Which bisection method to '
|
|
'use. Each method has '
|
|
'specific setup and '
|
|
'arguments. Please consult '
|
|
'the README for more '
|
|
'information.'))
|
|
|
|
parser_package = subparsers.add_parser('package')
|
|
parser_package.add_argument('board', help='Board to target')
|
|
parser_package.add_argument('remote', help='Remote machine to test on')
|
|
parser_package.set_defaults(handler=BisectPackage)
|
|
|
|
parser_object = subparsers.add_parser('object')
|
|
parser_object.add_argument('board', help='Board to target')
|
|
parser_object.add_argument('remote', help='Remote machine to test on')
|
|
parser_object.add_argument('package', help='Package to emerge and test')
|
|
parser_object.add_argument(
|
|
'--dir',
|
|
help=('Bisection directory to use, sets '
|
|
'$BISECT_DIR if provided. Defaults to '
|
|
'current value of $BISECT_DIR (or '
|
|
'/tmp/sysroot_bisect if $BISECT_DIR is '
|
|
'empty).'))
|
|
parser_object.set_defaults(handler=BisectObject)
|
|
|
|
parser_android = subparsers.add_parser('android')
|
|
parser_android.add_argument('android_src', help='Path to android source tree')
|
|
parser_android.add_argument(
|
|
'--dir',
|
|
help=('Bisection directory to use, sets '
|
|
'$BISECT_DIR if provided. Defaults to '
|
|
'current value of $BISECT_DIR (or '
|
|
'~/ANDROID_BISECT/ if $BISECT_DIR is '
|
|
'empty).'))
|
|
parser_android.add_argument(
|
|
'-j',
|
|
'--num_jobs',
|
|
type=int,
|
|
default=1,
|
|
help=('Number of jobs that make and various '
|
|
'scripts for bisector can spawn. Setting '
|
|
'this value too high can freeze up your '
|
|
'machine!'))
|
|
parser_android.add_argument(
|
|
'--device_id',
|
|
default='',
|
|
help=('Device id for device used for testing. '
|
|
'Use this if you have multiple Android '
|
|
'devices plugged into your machine.'))
|
|
parser_android.set_defaults(handler=BisectAndroid)
|
|
|
|
options, remaining = parser.parse_known_args(argv)
|
|
if remaining:
|
|
overrides = override_parser.parse_args(remaining)
|
|
overrides = vars(overrides)
|
|
else:
|
|
overrides = {}
|
|
|
|
subcmd = options.handler
|
|
del options.handler
|
|
|
|
bisector = subcmd(options, overrides)
|
|
return Run(bisector)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
os.chdir(os.path.dirname(__file__))
|
|
sys.exit(Main(sys.argv[1:]))
|