aboutsummaryrefslogtreecommitdiff
path: root/binary_search_tool/run_bisect.py
diff options
context:
space:
mode:
Diffstat (limited to 'binary_search_tool/run_bisect.py')
-rwxr-xr-xbinary_search_tool/run_bisect.py397
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:]))