Cassidy Burden | 2e37b14 | 2016-06-24 13:14:01 -0700 | [diff] [blame] | 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 | |
Cassidy Burden | 2e37b14 | 2016-06-24 13:14:01 -0700 | [diff] [blame] | 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() |
Cassidy Burden | 4cc3dd8 | 2016-08-11 15:05:38 -0700 | [diff] [blame] | 31 | utils_pythonpath = os.path.abspath('{0}/{1}/..'.format(wdir, os.path.dirname( |
| 32 | sys.argv[0]))) |
Cassidy Burden | 2e37b14 | 2016-06-24 13:14:01 -0700 | [diff] [blame] | 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: |
Cassidy Burden | 4cc3dd8 | 2016-08-11 15:05:38 -0700 | [diff] [blame] | 81 | raise TypeError('Invalid option "%s" for argument %s' % (key, args[0])) |
Cassidy Burden | 2e37b14 | 2016-06-24 13:14:01 -0700 | [diff] [blame] | 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 | |
Cassidy Burden | b1d0c4e | 2016-08-03 09:46:20 -0700 | [diff] [blame] | 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 | |
Cassidy Burden | 2e37b14 | 2016-06-24 13:14:01 -0700 | [diff] [blame] | 132 | def _BuildArgsDict(args): |
| 133 | """Populate ArgumentDict with all arguments""" |
Cassidy Burden | 4cc3dd8 | 2016-08-11 15:05:38 -0700 | [diff] [blame] | 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.')) |
Cassidy Burden | b1d0c4e | 2016-08-03 09:46:20 -0700 | [diff] [blame] | 174 | # No input (evals to False), |
| 175 | # --prune (evals to True), |
| 176 | # --prune=False, |
| 177 | # --prune=True |
Cassidy Burden | 4cc3dd8 | 2016-08-11 15:05:38 -0700 | [diff] [blame] | 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.')) |
Cassidy Burden | b1d0c4e | 2016-08-03 09:46:20 -0700 | [diff] [blame] | 189 | # No input (evals to False), |
| 190 | # --noincremental (evals to True), |
| 191 | # --noincremental=False, |
| 192 | # --noincremental=True |
Cassidy Burden | 4cc3dd8 | 2016-08-11 15:05:38 -0700 | [diff] [blame] | 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.')) |
Cassidy Burden | b1d0c4e | 2016-08-03 09:46:20 -0700 | [diff] [blame] | 204 | # No input (evals to False), |
| 205 | # --file_args (evals to True), |
| 206 | # --file_args=False, |
| 207 | # --file_args=True |
Cassidy Burden | 4cc3dd8 | 2016-08-11 15:05:38 -0700 | [diff] [blame] | 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.')) |
Cassidy Burden | b1d0c4e | 2016-08-03 09:46:20 -0700 | [diff] [blame] | 219 | # No input (evals to True), |
| 220 | # --verify (evals to True), |
| 221 | # --verify=False, |
| 222 | # --verify=True |
Cassidy Burden | 4cc3dd8 | 2016-08-11 15:05:38 -0700 | [diff] [blame] | 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) |
Cassidy Burden | b1d0c4e | 2016-08-03 09:46:20 -0700 | [diff] [blame] | 240 | # No input (evals to False), |
| 241 | # --verbose (evals to True), |
| 242 | # --verbose=False, |
| 243 | # --verbose=True |
Cassidy Burden | 4cc3dd8 | 2016-08-11 15:05:38 -0700 | [diff] [blame] | 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.')) |