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