1# pylint: disable-msg=C0111 2# Copyright 2008 Google Inc. Released under the GPL v2 3 4import warnings 5with warnings.catch_warnings(): 6 # The 'compiler' module is gone in Python 3.0. Let's not say 7 # so in every log file. 8 warnings.simplefilter("ignore", DeprecationWarning) 9 import compiler 10import logging, textwrap 11 12from autotest_lib.client.common_lib import enum 13 14REQUIRED_VARS = set(['author', 'doc', 'name', 'time', 'test_type']) 15OBSOLETE_VARS = set(['experimental']) 16 17CONTROL_TYPE = enum.Enum('Server', 'Client', start_value=1) 18CONTROL_TYPE_NAMES = enum.Enum(*CONTROL_TYPE.names, string_values=True) 19 20class ControlVariableException(Exception): 21 pass 22 23 24class ControlData(object): 25 # Available TIME settings in control file, the list must be in lower case 26 # and in ascending order, test running faster comes first. 27 TEST_TIME_LIST = ['fast', 'short', 'medium', 'long', 'lengthy'] 28 TEST_TIME = enum.Enum(*TEST_TIME_LIST, string_values=False) 29 30 @staticmethod 31 def get_test_time_index(time): 32 """ 33 Get the order of estimated test time, based on the TIME setting in 34 Control file. Faster test gets a lower index number. 35 """ 36 try: 37 return ControlData.TEST_TIME.get_value(time.lower()) 38 except AttributeError: 39 # Raise exception if time value is not a valid TIME setting. 40 error_msg = '%s is not a valid TIME.' % time 41 logging.error(error_msg) 42 raise ControlVariableException(error_msg) 43 44 45 def __init__(self, vars, path, raise_warnings=False): 46 # Defaults 47 self.path = path 48 self.dependencies = set() 49 # TODO(jrbarnette): This should be removed once outside 50 # code that uses can be changed. 51 self.experimental = False 52 self.run_verify = True 53 self.sync_count = 1 54 self.test_parameters = set() 55 self.test_category = '' 56 self.test_class = '' 57 self.retries = 0 58 self.job_retries = 0 59 # Default to require server-side package. Unless require_ssp is 60 # explicitly set to False, server-side package will be used for the 61 # job. This can be overridden by global config 62 # AUTOSERV/enable_ssp_container 63 self.require_ssp = None 64 self.attributes = set() 65 66 diff = REQUIRED_VARS - set(vars) 67 if diff: 68 warning = ('WARNING: Not all required control ' 69 'variables were specified in %s. Please define ' 70 '%s.') % (self.path, ', '.join(diff)) 71 if raise_warnings: 72 raise ControlVariableException(warning) 73 print textwrap.wrap(warning, 80) 74 75 obsolete = OBSOLETE_VARS & set(vars) 76 if obsolete: 77 warning = ('WARNING: Obsolete variables were ' 78 'specified in %s. Please remove ' 79 '%s.') % (self.path, ', '.join(obsolete)) 80 if raise_warnings: 81 raise ControlVariableException(warning) 82 print textwrap.wrap(warning, 80) 83 84 for key, val in vars.iteritems(): 85 try: 86 self.set_attr(key, val, raise_warnings) 87 except Exception, e: 88 if raise_warnings: 89 raise 90 print 'WARNING: %s; skipping' % e 91 92 93 def set_attr(self, attr, val, raise_warnings=False): 94 attr = attr.lower() 95 try: 96 set_fn = getattr(self, 'set_%s' % attr) 97 set_fn(val) 98 except AttributeError: 99 # This must not be a variable we care about 100 pass 101 102 103 def _set_string(self, attr, val): 104 val = str(val) 105 setattr(self, attr, val) 106 107 108 def _set_option(self, attr, val, options): 109 val = str(val) 110 if val.lower() not in [x.lower() for x in options]: 111 raise ValueError("%s must be one of the following " 112 "options: %s" % (attr, 113 ', '.join(options))) 114 setattr(self, attr, val) 115 116 117 def _set_bool(self, attr, val): 118 val = str(val).lower() 119 if val == "false": 120 val = False 121 elif val == "true": 122 val = True 123 else: 124 msg = "%s must be either true or false" % attr 125 raise ValueError(msg) 126 setattr(self, attr, val) 127 128 129 def _set_int(self, attr, val, min=None, max=None): 130 val = int(val) 131 if min is not None and min > val: 132 raise ValueError("%s is %d, which is below the " 133 "minimum of %d" % (attr, val, min)) 134 if max is not None and max < val: 135 raise ValueError("%s is %d, which is above the " 136 "maximum of %d" % (attr, val, max)) 137 setattr(self, attr, val) 138 139 140 def _set_set(self, attr, val): 141 val = str(val) 142 items = [x.strip() for x in val.split(',')] 143 setattr(self, attr, set(items)) 144 145 146 def set_author(self, val): 147 self._set_string('author', val) 148 149 150 def set_dependencies(self, val): 151 self._set_set('dependencies', val) 152 153 154 def set_doc(self, val): 155 self._set_string('doc', val) 156 157 158 def set_name(self, val): 159 self._set_string('name', val) 160 161 162 def set_run_verify(self, val): 163 self._set_bool('run_verify', val) 164 165 166 def set_sync_count(self, val): 167 self._set_int('sync_count', val, min=1) 168 169 170 def set_suite(self, val): 171 self._set_string('suite', val) 172 173 174 def set_time(self, val): 175 self._set_option('time', val, ControlData.TEST_TIME_LIST) 176 177 178 def set_test_class(self, val): 179 self._set_string('test_class', val.lower()) 180 181 182 def set_test_category(self, val): 183 self._set_string('test_category', val.lower()) 184 185 186 def set_test_type(self, val): 187 self._set_option('test_type', val, list(CONTROL_TYPE.names)) 188 189 190 def set_test_parameters(self, val): 191 self._set_set('test_parameters', val) 192 193 194 def set_retries(self, val): 195 self._set_int('retries', val) 196 197 198 def set_job_retries(self, val): 199 self._set_int('job_retries', val) 200 201 202 def set_bug_template(self, val): 203 if type(val) == dict: 204 setattr(self, 'bug_template', val) 205 206 207 def set_require_ssp(self, val): 208 self._set_bool('require_ssp', val) 209 210 211 def set_attributes(self, val): 212 # Add subsystem:default if subsystem is not specified. 213 self._set_set('attributes', val) 214 if not any(a.startswith('subsystem') for a in self.attributes): 215 self.attributes.add('subsystem:default') 216 217 218def _extract_const(expr): 219 assert(expr.__class__ == compiler.ast.Const) 220 assert(expr.value.__class__ in (str, int, float, unicode)) 221 return str(expr.value).strip() 222 223 224def _extract_dict(expr): 225 assert(expr.__class__ == compiler.ast.Dict) 226 assert(expr.items.__class__ == list) 227 cf_dict = {} 228 for key, value in expr.items: 229 try: 230 key = _extract_const(key) 231 val = _extract_expression(value) 232 except (AssertionError, ValueError): 233 pass 234 else: 235 cf_dict[key] = val 236 return cf_dict 237 238 239def _extract_list(expr): 240 assert(expr.__class__ == compiler.ast.List) 241 list_values = [] 242 for value in expr.nodes: 243 try: 244 list_values.append(_extract_expression(value)) 245 except (AssertionError, ValueError): 246 pass 247 return list_values 248 249 250def _extract_name(expr): 251 assert(expr.__class__ == compiler.ast.Name) 252 assert(expr.name in ('False', 'True', 'None')) 253 return str(expr.name) 254 255 256def _extract_expression(expr): 257 if expr.__class__ == compiler.ast.Const: 258 return _extract_const(expr) 259 if expr.__class__ == compiler.ast.Name: 260 return _extract_name(expr) 261 if expr.__class__ == compiler.ast.Dict: 262 return _extract_dict(expr) 263 if expr.__class__ == compiler.ast.List: 264 return _extract_list(expr) 265 raise ValueError('Unknown rval %s' % expr) 266 267 268def _extract_assignment(n): 269 assert(n.__class__ == compiler.ast.Assign) 270 assert(n.nodes.__class__ == list) 271 assert(len(n.nodes) == 1) 272 assert(n.nodes[0].__class__ == compiler.ast.AssName) 273 assert(n.nodes[0].flags.__class__ == str) 274 assert(n.nodes[0].name.__class__ == str) 275 276 val = _extract_expression(n.expr) 277 key = n.nodes[0].name.lower() 278 279 return (key, val) 280 281 282def parse_control_string(control, raise_warnings=False): 283 try: 284 mod = compiler.parse(control) 285 except SyntaxError, e: 286 raise ControlVariableException("Error parsing data because %s" % e) 287 return finish_parse(mod, '', raise_warnings) 288 289 290def parse_control(path, raise_warnings=False): 291 try: 292 mod = compiler.parseFile(path) 293 except SyntaxError, e: 294 raise ControlVariableException("Error parsing %s because %s" % 295 (path, e)) 296 return finish_parse(mod, path, raise_warnings) 297 298 299def finish_parse(mod, path, raise_warnings): 300 assert(mod.__class__ == compiler.ast.Module) 301 assert(mod.node.__class__ == compiler.ast.Stmt) 302 assert(mod.node.nodes.__class__ == list) 303 304 vars = {} 305 for n in mod.node.nodes: 306 try: 307 key, val = _extract_assignment(n) 308 vars[key] = val 309 except (AssertionError, ValueError): 310 pass 311 return ControlData(vars, path, raise_warnings) 312