bisect.py revision 471aae79081b9a9097e5f57c8d6792f72853b03f
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 217class BisectAndroid(Bisector): 218 """The class for Android bisection steps.""" 219 220 android_setup = 'android/setup.sh' 221 android_cleanup = 'android/cleanup.sh' 222 default_dir = os.path.expanduser('~/ANDROID_BISECT') 223 224 def __init__(self, options, overrides): 225 super(BisectAndroid, self).__init__(options, overrides) 226 self.method_name = 'Android' 227 self.default_kwargs = { 228 'get_initial_items': 'android/get_initial_items.sh', 229 'switch_to_good': 'android/switch_to_good.sh', 230 'switch_to_bad': 'android/switch_to_bad.sh', 231 'install_script': 'android/install.sh', 232 'test_script': 'android/interactive_test.sh', 233 'prune': True, 234 'file_args': True, 235 'noincremental': False, 236 } 237 self.options = options 238 if options.dir: 239 os.environ['BISECT_DIR'] = options.dir 240 self.options.dir = os.environ.get('BISECT_DIR', self.default_dir) 241 242 self.ArgOverride(self.default_kwargs, overrides) 243 244 def PreRun(self): 245 num_jobs = "NUM_JOBS='%s'" % self.options.num_jobs 246 device_id = "" 247 if self.options.device_id: 248 device_id = "ANDROID_SERIAL='%s'" % self.options.device_id 249 250 cmd = ('%s %s %s %s' % (num_jobs, device_id, self.android_setup, 251 self.options.android_src)) 252 ret, _, _ = self.ce.RunCommandWExceptionCleanup(cmd, print_to_console=True) 253 if ret: 254 self.logger.LogError('Android bisector setup failed w/ error %d' % ret) 255 return 1 256 257 os.environ['BISECT_STAGE'] = 'TRIAGE' 258 return 0 259 260 def Run(self): 261 return binary_search_state.Run(**self.default_kwargs) 262 263 def PostRun(self): 264 cmd = self.android_cleanup 265 ret, _, _ = self.ce.RunCommandWExceptionCleanup(cmd, print_to_console=True) 266 if ret: 267 self.logger.LogError('Android bisector cleanup failed w/ error %d' % ret) 268 return 1 269 return 0 270 271 272def Run(bisector): 273 log = logger.GetLogger() 274 275 log.LogOutput('Setting up Bisection tool') 276 ret = bisector.PreRun() 277 if ret: 278 return ret 279 280 log.LogOutput('Running Bisection tool') 281 ret = bisector.Run() 282 if ret: 283 return ret 284 285 log.LogOutput('Cleaning up Bisection tool') 286 ret = bisector.PostRun() 287 if ret: 288 return ret 289 290 return 0 291 292 293_HELP_EPILOG = """ 294Run ./bisect.py {method} --help for individual method help/args 295 296------------------ 297 298See README.bisect for examples on argument overriding 299 300See below for full override argument reference: 301""" 302 303 304def Main(argv): 305 override_parser = argparse.ArgumentParser(add_help=False, 306 argument_default=argparse.SUPPRESS, 307 usage='bisect.py {mode} [options]') 308 common.BuildArgParser(override_parser, override=True) 309 310 epilog = _HELP_EPILOG + override_parser.format_help() 311 parser = argparse.ArgumentParser(epilog=epilog, 312 formatter_class=RawTextHelpFormatter) 313 subparsers = parser.add_subparsers(title='Bisect mode', 314 description=('Which bisection method to ' 315 'use. Each method has ' 316 'specific setup and ' 317 'arguments. Please consult ' 318 'the README for more ' 319 'information.')) 320 321 parser_package = subparsers.add_parser('package') 322 parser_package.add_argument('board', help='Board to target') 323 parser_package.add_argument('remote', help='Remote machine to test on') 324 parser_package.set_defaults(handler=BisectPackage) 325 326 parser_object = subparsers.add_parser('object') 327 parser_object.add_argument('board', help='Board to target') 328 parser_object.add_argument('remote', help='Remote machine to test on') 329 parser_object.add_argument('package', help='Package to emerge and test') 330 parser_object.add_argument('--dir', 331 help=('Bisection directory to use, sets ' 332 '$BISECT_DIR if provided. Defaults to ' 333 'current value of $BISECT_DIR (or ' 334 '/tmp/sysroot_bisect if $BISECT_DIR is ' 335 'empty).')) 336 parser_object.set_defaults(handler=BisectObject) 337 338 parser_android = subparsers.add_parser('android') 339 parser_android.add_argument('android_src', help='Path to android source tree') 340 parser_android.add_argument('--dir', 341 help=('Bisection directory to use, sets ' 342 '$BISECT_DIR if provided. Defaults to ' 343 'current value of $BISECT_DIR (or ' 344 '~/ANDROID_BISECT/ if $BISECT_DIR is ' 345 'empty).')) 346 parser_android.add_argument('-j', '--num_jobs', 347 type=int, 348 default=1, 349 help=('Number of jobs that make and various ' 350 'scripts for bisector can spawn. Setting ' 351 'this value too high can freeze up your ' 352 'machine!')) 353 parser_android.add_argument('--device_id', 354 default='', 355 help=('Device id for device used for testing. ' 356 'Use this if you have multiple Android ' 357 'devices plugged into your machine.')) 358 parser_android.set_defaults(handler=BisectAndroid) 359 360 options, remaining = parser.parse_known_args(argv) 361 if remaining: 362 overrides = override_parser.parse_args(remaining) 363 overrides = vars(overrides) 364 else: 365 overrides = {} 366 367 subcmd = options.handler 368 del options.handler 369 370 bisector = subcmd(options, overrides) 371 return Run(bisector) 372 373 374if __name__ == '__main__': 375 os.chdir(os.path.dirname(__file__)) 376 sys.exit(Main(sys.argv[1:])) 377