Home | History | Annotate | Download | only in binary_search_tool
      1 """Common config and logic for binary search tool
      2 
      3 This 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 
      8 The 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
     10 the argument parsing, but treat them slightly differently. For example,
     11 bisect.py requires that all argument defaults are suppressed so that overriding
     12 can occur properly (i.e. only options that are explicitly entered by the user
     13 end up in the resultant options dictionary).
     14 
     15 ArgumentDict inherits OrderedDict in order to preserve the order the args are
     16 created so the help text is made properly.
     17 """
     18 
     19 from __future__ import print_function
     20 
     21 import collections
     22 import os
     23 import sys
     24 
     25 # Programatically adding utils python path to PYTHONPATH
     26 if os.path.isabs(sys.argv[0]):
     27   utils_pythonpath = os.path.abspath('{0}/..'.format(
     28       os.path.dirname(sys.argv[0])))
     29 else:
     30   wdir = os.getcwd()
     31   utils_pythonpath = os.path.abspath('{0}/{1}/..'.format(wdir, os.path.dirname(
     32       sys.argv[0])))
     33 sys.path.append(utils_pythonpath)
     34 
     35 
     36 class 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 
     89 def GetArgsDict():
     90   """_ArgsDict singleton method"""
     91   if not _ArgsDict:
     92     _BuildArgsDict(_ArgsDict)
     93   return _ArgsDict
     94 
     95 
     96 def 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 
    123 def 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 
    132 def _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