bisect.py revision caf9d96c94a914866c4cb9dfe401dfcee02c6047
1#!/usr/bin/python2 2"""The unified package/object bisecting tool.""" 3 4from __future__ import print_function 5 6import abc 7import argparse 8import os 9import sys 10from argparse import RawTextHelpFormatter 11 12import common 13 14from utils import command_executer 15from utils import logger 16 17import binary_search_state 18 19 20class Bisector(object): 21 """The abstract base class for Bisectors.""" 22 23 # Make Bisector an abstract class 24 __metaclass__ = abc.ABCMeta 25 26 def __init__(self, options, overrides=None): 27 """Constructor for Bisector abstract base class 28 29 Args: 30 options: positional arguments for specific mode (board, remote, etc.) 31 overrides: optional dict of overrides for argument defaults 32 """ 33 self.options = options 34 self.overrides = overrides 35 if not overrides: 36 self.overrides = {} 37 self.logger = logger.GetLogger() 38 self.ce = command_executer.GetCommandExecuter() 39 40 def _PrettyPrintArgs(self, args, overrides): 41 """Output arguments in a nice, human readable format 42 43 Will print and log all arguments for the bisecting tool and make note of 44 which arguments have been overridden. 45 46 Example output: 47 ./bisect.py package daisy 172.17.211.184 -I "" -t cros_pkg/my_test.sh 48 Performing ChromeOS Package bisection 49 Method Config: 50 board : daisy 51 remote : 172.17.211.184 52 53 Bisection Config: (* = overridden) 54 get_initial_items : cros_pkg/get_initial_items.sh 55 switch_to_good : cros_pkg/switch_to_good.sh 56 switch_to_bad : cros_pkg/switch_to_bad.sh 57 * install_script : 58 * test_script : cros_pkg/my_test.sh 59 prune : True 60 noincremental : False 61 file_args : True 62 63 Args: 64 args: The args to be given to binary_search_state.Run. This represents 65 how the bisection tool will run (with overridden arguments already 66 added in). 67 overrides: The dict of overriden arguments provided by the user. This is 68 provided so the user can be told which arguments were 69 overriden and with what value. 70 """ 71 # Output method config (board, remote, etc.) 72 options = vars(self.options) 73 out = '\nPerforming %s bisection\n' % self.method_name 74 out += 'Method Config:\n' 75 max_key_len = max([len(str(x)) for x in options.keys()]) 76 for key in sorted(options): 77 val = options[key] 78 key_str = str(key).rjust(max_key_len) 79 val_str = str(val) 80 out += ' %s : %s\n' % (key_str, val_str) 81 82 # Output bisection config (scripts, prune, etc.) 83 out += '\nBisection Config: (* = overridden)\n' 84 max_key_len = max([len(str(x)) for x in args.keys()]) 85 # Print args in common._ArgsDict order 86 args_order = [x['dest'] for x in common.GetArgsDict().itervalues()] 87 compare = lambda x, y: cmp(args_order.index(x), args_order.index(y)) 88 89 for key in sorted(args, cmp=compare): 90 val = args[key] 91 key_str = str(key).rjust(max_key_len) 92 val_str = str(val) 93 changed_str = '*' if key in overrides else ' ' 94 95 out += ' %s %s : %s\n' % (changed_str, key_str, val_str) 96 97 out += '\n' 98 self.logger.LogOutput(out) 99 100 def ArgOverride(self, args, overrides, pretty_print=True): 101 """Override arguments based on given overrides and provide nice output 102 103 Args: 104 args: dict of arguments to be passed to binary_search_state.Run (runs 105 dict.update, causing args to be mutated). 106 overrides: dict of arguments to update args with 107 pretty_print: if True print out args/overrides to user in pretty format 108 """ 109 args.update(overrides) 110 if pretty_print: 111 self._PrettyPrintArgs(args, overrides) 112 113 @abc.abstractmethod 114 def PreRun(self): 115 pass 116 117 @abc.abstractmethod 118 def Run(self): 119 pass 120 121 @abc.abstractmethod 122 def PostRun(self): 123 pass 124 125 126class BisectPackage(Bisector): 127 """The class for package bisection steps.""" 128 129 cros_pkg_setup = 'cros_pkg/setup.sh' 130 cros_pkg_cleanup = 'cros_pkg/%s_cleanup.sh' 131 132 def __init__(self, options, overrides): 133 super(BisectPackage, self).__init__(options, overrides) 134 self.method_name = 'ChromeOS Package' 135 self.default_kwargs = { 136 'get_initial_items': 'cros_pkg/get_initial_items.sh', 137 'switch_to_good': 'cros_pkg/switch_to_good.sh', 138 'switch_to_bad': 'cros_pkg/switch_to_bad.sh', 139 'install_script': 'cros_pkg/install.sh', 140 'test_script': 'cros_pkg/interactive_test.sh', 141 'noincremental': False, 142 'prune': True, 143 'file_args': True 144 } 145 self.ArgOverride(self.default_kwargs, self.overrides) 146 147 def PreRun(self): 148 cmd = ('%s %s %s' % 149 (self.cros_pkg_setup, self.options.board, self.options.remote)) 150 ret, _, _ = self.ce.RunCommandWExceptionCleanup(cmd, print_to_console=True) 151 if ret: 152 self.logger.LogError('Package bisector setup failed w/ error %d' % ret) 153 return 1 154 return 0 155 156 def Run(self): 157 return binary_search_state.Run(**self.default_kwargs) 158 159 def PostRun(self): 160 cmd = self.cros_pkg_cleanup % self.options.board 161 ret, _, _ = self.ce.RunCommandWExceptionCleanup(cmd, print_to_console=True) 162 if ret: 163 self.logger.LogError('Package bisector cleanup failed w/ error %d' % ret) 164 return 1 165 return 0 166 167 168class BisectObject(Bisector): 169 """The class for object bisection steps.""" 170 171 def __init__(self, options, overrides): 172 super(BisectObject, self).__init__(options, overrides) 173 174 def PreRun(self): 175 raise NotImplementedError('Object bisecting still WIP') 176 177 def Run(self): 178 return 1 179 180 def PostRun(self): 181 return 1 182 183 184def Run(bisector): 185 log = logger.GetLogger() 186 187 log.LogOutput('Setting up Bisection tool') 188 ret = bisector.PreRun() 189 if ret: 190 return ret 191 192 log.LogOutput('Running Bisection tool') 193 ret = bisector.Run() 194 if ret: 195 return ret 196 197 log.LogOutput('Cleaning up Bisection tool') 198 ret = bisector.PostRun() 199 if ret: 200 return ret 201 202 return 0 203 204 205_HELP_EPILOG = """ 206Run ./bisect.py {method} --help for individual method help/args 207 208------------------ 209 210See README.bisect for examples on argument overriding 211 212See below for full override argument reference: 213""" 214 215 216def Main(argv): 217 override_parser = argparse.ArgumentParser(add_help=False, 218 argument_default=argparse.SUPPRESS, 219 usage='bisect.py {mode} [options]') 220 common.BuildArgParser(override_parser, override=True) 221 222 epilog = _HELP_EPILOG + override_parser.format_help() 223 parser = argparse.ArgumentParser(epilog=epilog, 224 formatter_class=RawTextHelpFormatter) 225 subparsers = parser.add_subparsers(title='Bisect mode', 226 description=('Which bisection method to ' 227 'use. Each method has ' 228 'specific setup and ' 229 'arguments. Please consult ' 230 'the README for more ' 231 'information.')) 232 233 parser_package = subparsers.add_parser('package') 234 parser_package.add_argument('board', help='Board to target') 235 parser_package.add_argument('remote', help='Remote machine to test on') 236 parser_package.set_defaults(handler=BisectPackage) 237 238 parser_object = subparsers.add_parser('object') 239 parser_object.set_defaults(handler=BisectObject) 240 241 options, remaining = parser.parse_known_args(argv) 242 if remaining: 243 overrides = override_parser.parse_args(remaining) 244 overrides = vars(overrides) 245 else: 246 overrides = {} 247 248 subcmd = options.handler 249 del options.handler 250 251 bisector = subcmd(options, overrides) 252 return Run(bisector) 253 254 255if __name__ == '__main__': 256 os.chdir(os.path.dirname(__file__)) 257 sys.exit(Main(sys.argv[1:])) 258