diff options
Diffstat (limited to 'binary_search_tool/run_bisect.py')
-rwxr-xr-x | binary_search_tool/run_bisect.py | 397 |
1 files changed, 397 insertions, 0 deletions
diff --git a/binary_search_tool/run_bisect.py b/binary_search_tool/run_bisect.py new file mode 100755 index 00000000..ef1048bb --- /dev/null +++ b/binary_search_tool/run_bisect.py @@ -0,0 +1,397 @@ +#!/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:])) |