common.py revision b1d0c4e00ed787befa9591231ac5d04d2cf6795d
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(
32      wdir, os.path.dirname(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' %
82                        (key, args[0]))
83
84    self[args] = kwargs
85
86
87_ArgsDict = ArgumentDict()
88
89
90def GetArgsDict():
91  """_ArgsDict singleton method"""
92  if not _ArgsDict:
93    _BuildArgsDict(_ArgsDict)
94  return _ArgsDict
95
96
97def BuildArgParser(parser, override=False):
98  """Add all arguments from singleton ArgsDict to parser.
99
100  Will take argparse parser and add all arguments in ArgsDict. Will ignore
101  the default and required options if override is set to True.
102
103  Args:
104    parser: type argparse.ArgumentParser, will call add_argument for every item
105            in _ArgsDict
106    override: True if being called from bisect.py. Used to say that default and
107              required options are to be ignored
108
109  Returns:
110    None
111  """
112  ArgsDict = GetArgsDict()
113
114  # Have no defaults when overriding
115  for arg_names, arg_options in ArgsDict.iteritems():
116    if override:
117      arg_options = arg_options.copy()
118      arg_options.pop('default', None)
119      arg_options.pop('required', None)
120
121    parser.add_argument(*arg_names, **arg_options)
122
123
124def StrToBool(str_in):
125  if str_in.lower() in ['true', 't', '1']:
126    return True
127  if str_in.lower() in ['false', 'f', '0']:
128    return False
129
130  raise AttributeError('%s is not a valid boolean string' % str_in)
131
132
133def _BuildArgsDict(args):
134  """Populate ArgumentDict with all arguments"""
135  args.AddArgument('-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('-i',
142                   '--get_initial_items',
143                   dest='get_initial_items',
144                   help=('Script to run to get the initial objects. '
145                         'If your script requires user input '
146                         'the --verbose option must be used'))
147  args.AddArgument('-g',
148                   '--switch_to_good',
149                   dest='switch_to_good',
150                   help=('Script to run to switch to good. '
151                         'If your switch script requires user input '
152                         'the --verbose option must be used'))
153  args.AddArgument('-b',
154                   '--switch_to_bad',
155                   dest='switch_to_bad',
156                   help=('Script to run to switch to bad. '
157                         'If your switch script requires user input '
158                         'the --verbose option must be used'))
159  args.AddArgument('-I',
160                   '--install_script',
161                   dest='install_script',
162                   help=('Optional script to perform building, flashing, '
163                         'and other setup before the test script runs.'))
164  args.AddArgument('-t',
165                   '--test_script',
166                   dest='test_script',
167                   help=('Script to run to test the '
168                         'output after packages are built.'))
169  # No input (evals to False),
170  # --prune (evals to True),
171  # --prune=False,
172  # --prune=True
173  args.AddArgument('-p',
174                   '--prune',
175                   dest='prune',
176                   nargs='?',
177                   const=True,
178                   default=False,
179                   type=StrToBool,
180                   metavar='bool',
181                   help=('If True, continue until all bad items are found. '
182                         'Defaults to False.'))
183  # No input (evals to False),
184  # --noincremental (evals to True),
185  # --noincremental=False,
186  # --noincremental=True
187  args.AddArgument('-c',
188                   '--noincremental',
189                   dest='noincremental',
190                   nargs='?',
191                   const=True,
192                   default=False,
193                   type=StrToBool,
194                   metavar='bool',
195                   help=('If True, don\'t propagate good/bad changes '
196                         'incrementally. Defaults to False.'))
197  # No input (evals to False),
198  # --file_args (evals to True),
199  # --file_args=False,
200  # --file_args=True
201  args.AddArgument('-f',
202                   '--file_args',
203                   dest='file_args',
204                   nargs='?',
205                   const=True,
206                   default=False,
207                   type=StrToBool,
208                   metavar='bool',
209                   help=('Whether to use a file to pass arguments to scripts. '
210                         'Defaults to False.'))
211  # No input (evals to True),
212  # --verify (evals to True),
213  # --verify=False,
214  # --verify=True
215  args.AddArgument('--verify',
216                   dest='verify',
217                   nargs='?',
218                   const=True,
219                   default=True,
220                   type=StrToBool,
221                   metavar='bool',
222                   help=('Whether to run verify iterations before searching. '
223                         'Defaults to True.'))
224  args.AddArgument('-N',
225                   '--prune_iterations',
226                   dest='prune_iterations',
227                   type=int,
228                   help='Number of prune iterations to try in the search.',
229                   default=100)
230  # No input (evals to False),
231  # --verbose (evals to True),
232  # --verbose=False,
233  # --verbose=True
234  args.AddArgument('-V',
235                   '--verbose',
236                   dest='verbose',
237                   nargs='?',
238                   const=True,
239                   default=False,
240                   type=StrToBool,
241                   metavar='bool',
242                   help='If True, print full output to console.')
243  args.AddArgument('-r',
244                   '--resume',
245                   dest='resume',
246                   action='store_true',
247                   help=('Resume bisection tool execution from state file.'
248                         'Useful if the last bisection was terminated '
249                         'before it could properly finish.'))
250