common.py revision 4cc3dd8a3b2ab04cf861d7c71967de0298746cb9
1"""Common config and logic for binary search tool
2
3This module serves two main purposes:
4  1. Programatically include the utils module in PYTHONPATH
5  2. Create the argument parsing shared between binary_search_state.py and
6     bisect.py
7
8The argument parsing is handled by populating _ArgsDict with all arguments.
9_ArgsDict is required so that binary_search_state.py and bisect.py can share
10the argument parsing, but treat them slightly differently. For example,
11bisect.py requires that all argument defaults are suppressed so that overriding
12can occur properly (i.e. only options that are explicitly entered by the user
13end up in the resultant options dictionary).
14
15ArgumentDict inherits OrderedDict in order to preserve the order the args are
16created so the help text is made properly.
17"""
18
19from __future__ import print_function
20
21import collections
22import os
23import sys
24
25# Programatically adding utils python path to PYTHONPATH
26if os.path.isabs(sys.argv[0]):
27  utils_pythonpath = os.path.abspath('{0}/..'.format(
28      os.path.dirname(sys.argv[0])))
29else:
30  wdir = os.getcwd()
31  utils_pythonpath = os.path.abspath('{0}/{1}/..'.format(wdir, os.path.dirname(
32      sys.argv[0])))
33sys.path.append(utils_pythonpath)
34
35
36class ArgumentDict(collections.OrderedDict):
37  """Wrapper around OrderedDict, represents CLI arguments for program.
38
39  AddArgument enforces the following layout:
40  {
41      ['-n', '--iterations'] : {
42          'dest': 'iterations',
43          'type': int,
44          'help': 'Number of iterations to try in the search.',
45          'default': 50
46      }
47      [arg_name1, arg_name2, ...] : {
48          arg_option1 : arg_option_val1,
49          ...
50      },
51      ...
52  }
53  """
54  _POSSIBLE_OPTIONS = ['action', 'nargs', 'const', 'default', 'type', 'choices',
55                       'required', 'help', 'metavar', 'dest']
56
57  def AddArgument(self, *args, **kwargs):
58    """Add argument to ArgsDict, has same signature as argparse.add_argument
59
60    Emulates the the argparse.add_argument method so the internal OrderedDict
61    can be safely and easily populated. Each call to this method will have a 1-1
62    corresponding call to argparse.add_argument once BuildArgParser is called.
63
64    Args:
65      *args: The names for the argument (-V, --verbose, etc.)
66      **kwargs: The options for the argument, corresponds to the args of
67                argparse.add_argument
68
69    Returns:
70      None
71
72    Raises:
73      TypeError: if args is empty or if option in kwargs is not a valid
74                 option for argparse.add_argument.
75    """
76    if len(args) == 0:
77      raise TypeError('Argument needs at least one name')
78
79    for key in kwargs:
80      if key not in self._POSSIBLE_OPTIONS:
81        raise TypeError('Invalid option "%s" for argument %s' % (key, args[0]))
82
83    self[args] = kwargs
84
85
86_ArgsDict = ArgumentDict()
87
88
89def GetArgsDict():
90  """_ArgsDict singleton method"""
91  if not _ArgsDict:
92    _BuildArgsDict(_ArgsDict)
93  return _ArgsDict
94
95
96def BuildArgParser(parser, override=False):
97  """Add all arguments from singleton ArgsDict to parser.
98
99  Will take argparse parser and add all arguments in ArgsDict. Will ignore
100  the default and required options if override is set to True.
101
102  Args:
103    parser: type argparse.ArgumentParser, will call add_argument for every item
104            in _ArgsDict
105    override: True if being called from bisect.py. Used to say that default and
106              required options are to be ignored
107
108  Returns:
109    None
110  """
111  ArgsDict = GetArgsDict()
112
113  # Have no defaults when overriding
114  for arg_names, arg_options in ArgsDict.iteritems():
115    if override:
116      arg_options = arg_options.copy()
117      arg_options.pop('default', None)
118      arg_options.pop('required', None)
119
120    parser.add_argument(*arg_names, **arg_options)
121
122
123def StrToBool(str_in):
124  if str_in.lower() in ['true', 't', '1']:
125    return True
126  if str_in.lower() in ['false', 'f', '0']:
127    return False
128
129  raise AttributeError('%s is not a valid boolean string' % str_in)
130
131
132def _BuildArgsDict(args):
133  """Populate ArgumentDict with all arguments"""
134  args.AddArgument(
135      '-n',
136      '--iterations',
137      dest='iterations',
138      type=int,
139      help='Number of iterations to try in the search.',
140      default=50)
141  args.AddArgument(
142      '-i',
143      '--get_initial_items',
144      dest='get_initial_items',
145      help=('Script to run to get the initial objects. '
146            'If your script requires user input '
147            'the --verbose option must be used'))
148  args.AddArgument(
149      '-g',
150      '--switch_to_good',
151      dest='switch_to_good',
152      help=('Script to run to switch to good. '
153            'If your switch script requires user input '
154            'the --verbose option must be used'))
155  args.AddArgument(
156      '-b',
157      '--switch_to_bad',
158      dest='switch_to_bad',
159      help=('Script to run to switch to bad. '
160            'If your switch script requires user input '
161            'the --verbose option must be used'))
162  args.AddArgument(
163      '-I',
164      '--test_setup_script',
165      dest='test_setup_script',
166      help=('Optional script to perform building, flashing, '
167            'and other setup before the test script runs.'))
168  args.AddArgument(
169      '-t',
170      '--test_script',
171      dest='test_script',
172      help=('Script to run to test the '
173            'output after packages are built.'))
174  # No input (evals to False),
175  # --prune (evals to True),
176  # --prune=False,
177  # --prune=True
178  args.AddArgument(
179      '-p',
180      '--prune',
181      dest='prune',
182      nargs='?',
183      const=True,
184      default=False,
185      type=StrToBool,
186      metavar='bool',
187      help=('If True, continue until all bad items are found. '
188            'Defaults to False.'))
189  # No input (evals to False),
190  # --noincremental (evals to True),
191  # --noincremental=False,
192  # --noincremental=True
193  args.AddArgument(
194      '-c',
195      '--noincremental',
196      dest='noincremental',
197      nargs='?',
198      const=True,
199      default=False,
200      type=StrToBool,
201      metavar='bool',
202      help=('If True, don\'t propagate good/bad changes '
203            'incrementally. Defaults to False.'))
204  # No input (evals to False),
205  # --file_args (evals to True),
206  # --file_args=False,
207  # --file_args=True
208  args.AddArgument(
209      '-f',
210      '--file_args',
211      dest='file_args',
212      nargs='?',
213      const=True,
214      default=False,
215      type=StrToBool,
216      metavar='bool',
217      help=('Whether to use a file to pass arguments to scripts. '
218            'Defaults to False.'))
219  # No input (evals to True),
220  # --verify (evals to True),
221  # --verify=False,
222  # --verify=True
223  args.AddArgument(
224      '--verify',
225      dest='verify',
226      nargs='?',
227      const=True,
228      default=True,
229      type=StrToBool,
230      metavar='bool',
231      help=('Whether to run verify iterations before searching. '
232            'Defaults to True.'))
233  args.AddArgument(
234      '-N',
235      '--prune_iterations',
236      dest='prune_iterations',
237      type=int,
238      help='Number of prune iterations to try in the search.',
239      default=100)
240  # No input (evals to False),
241  # --verbose (evals to True),
242  # --verbose=False,
243  # --verbose=True
244  args.AddArgument(
245      '-V',
246      '--verbose',
247      dest='verbose',
248      nargs='?',
249      const=True,
250      default=False,
251      type=StrToBool,
252      metavar='bool',
253      help='If True, print full output to console.')
254  args.AddArgument(
255      '-r',
256      '--resume',
257      dest='resume',
258      action='store_true',
259      help=('Resume bisection tool execution from state file.'
260            'Useful if the last bisection was terminated '
261            'before it could properly finish.'))
262