control_data.py revision 89d5d21ec72e55ca0ca9e50d488210e2c5bb34e7
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'])
15
16CONTROL_TYPE = enum.Enum('Server', 'Client', start_value=1)
17CONTROL_TYPE_NAMES =  enum.Enum(*CONTROL_TYPE.names, string_values=True)
18
19class ControlVariableException(Exception):
20    pass
21
22
23class ControlData(object):
24    # Available TIME settings in control file, the list must be in lower case
25    # and in ascending order, test running faster comes first.
26    TEST_TIME_LIST = ['fast', 'short', 'medium', 'long', 'lengthy']
27    TEST_TIME = enum.Enum(*TEST_TIME_LIST, string_values=False)
28
29    @staticmethod
30    def get_test_time_index(time):
31        """
32        Get the order of estimated test time, based on the TIME setting in
33        Control file. Faster test gets a lower index number.
34        """
35        try:
36            return ControlData.TEST_TIME.get_value(time.lower())
37        except AttributeError:
38            # Raise exception if time value is not a valid TIME setting.
39            error_msg = '%s is not a valid TIME.' % time
40            logging.error(error_msg)
41            raise ControlVariableException(error_msg)
42
43
44    def __init__(self, vars, path, raise_warnings=False):
45        # Defaults
46        self.path = path
47        self.dependencies = set()
48        self.experimental = False
49        self.run_verify = True
50        self.sync_count = 1
51        self.test_parameters = set()
52        self.test_category = ''
53        self.test_class = ''
54        self.retries = 0
55
56        diff = REQUIRED_VARS - set(vars)
57        if len(diff) > 0:
58            warning = ("WARNING: Not all required control "
59                       "variables were specified in %s.  Please define "
60                       "%s.") % (self.path, ', '.join(diff))
61            if raise_warnings:
62                raise ControlVariableException(warning)
63            print textwrap.wrap(warning, 80)
64
65        for key, val in vars.iteritems():
66            try:
67                self.set_attr(key, val, raise_warnings)
68            except Exception, e:
69                if raise_warnings:
70                    raise
71                print "WARNING: %s; skipping" % e
72
73
74    def set_attr(self, attr, val, raise_warnings=False):
75        attr = attr.lower()
76        try:
77            set_fn = getattr(self, 'set_%s' % attr)
78            set_fn(val)
79        except AttributeError:
80            # This must not be a variable we care about
81            pass
82
83
84    def _set_string(self, attr, val):
85        val = str(val)
86        setattr(self, attr, val)
87
88
89    def _set_option(self, attr, val, options):
90        val = str(val)
91        if val.lower() not in [x.lower() for x in options]:
92            raise ValueError("%s must be one of the following "
93                             "options: %s" % (attr,
94                             ', '.join(options)))
95        setattr(self, attr, val)
96
97
98    def _set_bool(self, attr, val):
99        val = str(val).lower()
100        if val == "false":
101            val = False
102        elif val == "true":
103            val = True
104        else:
105            msg = "%s must be either true or false" % attr
106            raise ValueError(msg)
107        setattr(self, attr, val)
108
109
110    def _set_int(self, attr, val, min=None, max=None):
111        val = int(val)
112        if min is not None and min > val:
113            raise ValueError("%s is %d, which is below the "
114                             "minimum of %d" % (attr, val, min))
115        if max is not None and max < val:
116            raise ValueError("%s is %d, which is above the "
117                             "maximum of %d" % (attr, val, max))
118        setattr(self, attr, val)
119
120
121    def _set_set(self, attr, val):
122        val = str(val)
123        items = [x.strip() for x in val.split(',')]
124        setattr(self, attr, set(items))
125
126
127    def set_author(self, val):
128        self._set_string('author', val)
129
130
131    def set_dependencies(self, val):
132        self._set_set('dependencies', val)
133
134
135    def set_doc(self, val):
136        self._set_string('doc', val)
137
138
139    def set_experimental(self, val):
140        self._set_bool('experimental', val)
141
142
143    def set_name(self, val):
144        self._set_string('name', val)
145
146
147    def set_run_verify(self, val):
148        self._set_bool('run_verify', val)
149
150
151    def set_sync_count(self, val):
152        self._set_int('sync_count', val, min=1)
153
154
155    def set_suite(self, val):
156        self._set_string('suite', val)
157
158
159    def set_time(self, val):
160        self._set_option('time', val, ControlData.TEST_TIME_LIST)
161
162
163    def set_test_class(self, val):
164        self._set_string('test_class', val.lower())
165
166
167    def set_test_category(self, val):
168        self._set_string('test_category', val.lower())
169
170
171    def set_test_type(self, val):
172        self._set_option('test_type', val, list(CONTROL_TYPE.names))
173
174
175    def set_test_parameters(self, val):
176        self._set_set('test_parameters', val)
177
178
179    def set_retries(self, val):
180        self._set_int('retries', val)
181
182
183def _extract_const(n):
184    assert(n.__class__ == compiler.ast.Assign)
185    assert(n.expr.__class__ == compiler.ast.Const)
186    assert(n.expr.value.__class__ in (str, int, float, unicode))
187    assert(n.nodes.__class__ == list)
188    assert(len(n.nodes) == 1)
189    assert(n.nodes[0].__class__ == compiler.ast.AssName)
190    assert(n.nodes[0].flags.__class__ == str)
191    assert(n.nodes[0].name.__class__ == str)
192
193    key = n.nodes[0].name.lower()
194    val = str(n.expr.value).strip()
195
196    return (key, val)
197
198
199def _extract_name(n):
200    assert(n.__class__ == compiler.ast.Assign)
201    assert(n.expr.__class__ == compiler.ast.Name)
202    assert(n.nodes.__class__ == list)
203    assert(len(n.nodes) == 1)
204    assert(n.nodes[0].__class__ == compiler.ast.AssName)
205    assert(n.nodes[0].flags.__class__ == str)
206    assert(n.nodes[0].name.__class__ == str)
207    assert(n.expr.name in ('False', 'True', 'None'))
208
209    key = n.nodes[0].name.lower()
210    val = str(n.expr.name)
211
212    return (key, val)
213
214
215def parse_control_string(control, raise_warnings=False):
216    try:
217        mod = compiler.parse(control)
218    except SyntaxError, e:
219        raise ControlVariableException("Error parsing data because %s" % e)
220    return finish_parse(mod, '', raise_warnings)
221
222
223def parse_control(path, raise_warnings=False):
224    try:
225        mod = compiler.parseFile(path)
226    except SyntaxError, e:
227        raise ControlVariableException("Error parsing %s because %s" %
228                                       (path, e))
229    return finish_parse(mod, path, raise_warnings)
230
231
232def finish_parse(mod, path, raise_warnings):
233    assert(mod.__class__ == compiler.ast.Module)
234    assert(mod.node.__class__ == compiler.ast.Stmt)
235    assert(mod.node.nodes.__class__ == list)
236
237    vars = {}
238    for n in mod.node.nodes:
239        for fn in (_extract_const, _extract_name):
240            try:
241                key, val = fn(n)
242
243                vars[key] = val
244            except AssertionError, e:
245                pass
246
247    return ControlData(vars, path, raise_warnings)
248