Home | History | Annotate | Download | only in common_lib
      1 # pylint: disable-msg=C0111
      2 # Copyright 2008 Google Inc. Released under the GPL v2
      3 
      4 import warnings
      5 with 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
     10 import logging, textwrap
     11 
     12 from autotest_lib.client.common_lib import enum
     13 
     14 REQUIRED_VARS = set(['author', 'doc', 'name', 'time', 'test_type'])
     15 OBSOLETE_VARS = set(['experimental'])
     16 
     17 CONTROL_TYPE = enum.Enum('Server', 'Client', start_value=1)
     18 CONTROL_TYPE_NAMES =  enum.Enum(*CONTROL_TYPE.names, string_values=True)
     19 
     20 class ControlVariableException(Exception):
     21     pass
     22 
     23 
     24 class 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 
    218 def _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 
    224 def _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 
    239 def _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 
    250 def _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 
    256 def _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 
    268 def _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 
    282 def 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 
    290 def 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 
    299 def 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