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.
473 lines
15 KiB
473 lines
15 KiB
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
# Copyright 2020 The ChromiumOS Authors
|
|
# 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."""
|
|
|
|
|
|
import abc
|
|
import argparse
|
|
from argparse import RawTextHelpFormatter
|
|
import os
|
|
import shlex
|
|
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 = " ".join(
|
|
(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 = " ".join(
|
|
(
|
|
self.sysroot_wrapper_setup,
|
|
self.options.board,
|
|
self.options.remote,
|
|
self.options.package,
|
|
str(self.options.reboot).lower(),
|
|
shlex.quote(self.options.use_flags),
|
|
)
|
|
)
|
|
|
|
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 = " ".join(
|
|
(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(
|
|
"--use_flags",
|
|
required=False,
|
|
default="",
|
|
help="Use flags passed to emerge",
|
|
)
|
|
parser_object.add_argument(
|
|
"--noreboot",
|
|
action="store_false",
|
|
dest="reboot",
|
|
help="Do not reboot after updating the package (default: False)",
|
|
)
|
|
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:]))
|