bisect.py revision a8af9a7a2462b00e72deff99327bdb452a715277
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 cros_utils import command_executer 15from cros_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 sysroot_wrapper_setup = 'sysroot_wrapper/setup.sh' 172 sysroot_wrapper_cleanup = 'sysroot_wrapper/cleanup.sh' 173 174 def __init__(self, options, overrides): 175 super(BisectObject, self).__init__(options, overrides) 176 self.method_name = 'ChromeOS Object' 177 self.default_kwargs = { 178 'get_initial_items': 'sysroot_wrapper/get_initial_items.sh', 179 'switch_to_good': 'sysroot_wrapper/switch_to_good.sh', 180 'switch_to_bad': 'sysroot_wrapper/switch_to_bad.sh', 181 'install_script': 'sysroot_wrapper/install.sh', 182 'test_script': 'sysroot_wrapper/interactive_test.sh', 183 'noincremental': True, 184 'prune': True, 185 'file_args': True 186 } 187 self.options = options 188 if options.dir: 189 os.environ['BISECT_DIR'] = options.dir 190 self.options.dir = os.environ.get('BISECT_DIR', '/tmp/sysroot_bisect') 191 192 self.ArgOverride(self.default_kwargs, overrides) 193 194 def PreRun(self): 195 cmd = ('%s %s %s %s' % (self.sysroot_wrapper_setup, self.options.board, 196 self.options.remote, self.options.package)) 197 ret, _, _ = self.ce.RunCommandWExceptionCleanup(cmd, print_to_console=True) 198 if ret: 199 self.logger.LogError('Object bisector setup failed w/ error %d' % ret) 200 return 1 201 202 os.environ['BISECT_STAGE'] = 'TRIAGE' 203 return 0 204 205 def Run(self): 206 return binary_search_state.Run(**self.default_kwargs) 207 208 def PostRun(self): 209 cmd = self.sysroot_wrapper_cleanup 210 ret, _, _ = self.ce.RunCommandWExceptionCleanup(cmd, print_to_console=True) 211 if ret: 212 self.logger.LogError('Object bisector cleanup failed w/ error %d' % ret) 213 return 1 214 return 0 215 216 217def Run(bisector): 218 log = logger.GetLogger() 219 220 log.LogOutput('Setting up Bisection tool') 221 ret = bisector.PreRun() 222 if ret: 223 return ret 224 225 log.LogOutput('Running Bisection tool') 226 ret = bisector.Run() 227 if ret: 228 return ret 229 230 log.LogOutput('Cleaning up Bisection tool') 231 ret = bisector.PostRun() 232 if ret: 233 return ret 234 235 return 0 236 237 238_HELP_EPILOG = """ 239Run ./bisect.py {method} --help for individual method help/args 240 241------------------ 242 243See README.bisect for examples on argument overriding 244 245See below for full override argument reference: 246""" 247 248 249def Main(argv): 250 override_parser = argparse.ArgumentParser(add_help=False, 251 argument_default=argparse.SUPPRESS, 252 usage='bisect.py {mode} [options]') 253 common.BuildArgParser(override_parser, override=True) 254 255 epilog = _HELP_EPILOG + override_parser.format_help() 256 parser = argparse.ArgumentParser(epilog=epilog, 257 formatter_class=RawTextHelpFormatter) 258 subparsers = parser.add_subparsers(title='Bisect mode', 259 description=('Which bisection method to ' 260 'use. Each method has ' 261 'specific setup and ' 262 'arguments. Please consult ' 263 'the README for more ' 264 'information.')) 265 266 parser_package = subparsers.add_parser('package') 267 parser_package.add_argument('board', help='Board to target') 268 parser_package.add_argument('remote', help='Remote machine to test on') 269 parser_package.set_defaults(handler=BisectPackage) 270 271 parser_object = subparsers.add_parser('object') 272 parser_object.add_argument('board', help='Board to target') 273 parser_object.add_argument('remote', help='Remote machine to test on') 274 parser_object.add_argument('package', help='Package to emerge and test') 275 parser_object.add_argument('--dir', 276 help=('Bisection directory to use, sets ' 277 '$BISECT_DIR if provided. Defaults to ' 278 'current value of $BISECT_DIR (or ' 279 '/tmp/sysroot_bisect if $BISECT_DIR is ' 280 'empty).')) 281 parser_object.set_defaults(handler=BisectObject) 282 283 options, remaining = parser.parse_known_args(argv) 284 if remaining: 285 overrides = override_parser.parse_args(remaining) 286 overrides = vars(overrides) 287 else: 288 overrides = {} 289 290 subcmd = options.handler 291 del options.handler 292 293 bisector = subcmd(options, overrides) 294 return Run(bisector) 295 296 297if __name__ == '__main__': 298 os.chdir(os.path.dirname(__file__)) 299 sys.exit(Main(sys.argv[1:])) 300