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