control_data.py revision 3abbceee9421805bed8f5c2c6c6abb9299b32cbf
1#
2# Copyright 2008 Google Inc. Released under the GPL v2
3
4import compiler, textwrap, types
5
6
7REQUIRED_VARS = set(['author', 'doc', 'name', 'time', 'test_class',
8                     'test_category', 'test_type'])
9
10class ControlVariableException(Exception):
11    pass
12
13
14class ControlData(object):
15    def __init__(self, vars, path, raise_warnings=False):
16        # Defaults
17        self.path = path
18        self.dependencies = set()
19        self.experimental = False
20        self.run_verify = True
21        self.sync_count = 1
22
23        diff = REQUIRED_VARS - set(vars)
24        if len(diff) > 0:
25            warning = ("WARNING: Not all required control "
26                       "variables were specified in %s.  Please define "
27                       "%s.") % (self.path, ', '.join(diff))
28            if raise_warnings:
29                raise ControlVariableException(warning)
30            print textwrap.wrap(warning, 80)
31
32        for key, val in vars.iteritems():
33            try:
34                self.set_attr(key, val, raise_warnings)
35            except Exception, e:
36                if raise_warnings:
37                    raise
38                print "WARNING: %s; skipping" % e
39
40
41    def set_attr(self, attr, val, raise_warnings=False):
42        attr = attr.lower()
43        try:
44            set_fn = getattr(self, 'set_%s' % attr)
45            set_fn(val)
46        except AttributeError:
47            # This must not be a variable we care about
48            pass
49
50
51    def _set_string(self, attr, val):
52        val = str(val)
53        setattr(self, attr, val)
54
55
56    def _set_option(self, attr, val, options):
57        val = str(val)
58        if val.lower() not in [x.lower() for x in options]:
59            raise ValueError("%s must be one of the following "
60                             "options: %s" % (attr,
61                             ', '.join(options)))
62        setattr(self, attr, val)
63
64
65    def _set_bool(self, attr, val):
66        val = str(val).lower()
67        if val == "false":
68            val = False
69        elif val == "true":
70            val = True
71        else:
72            msg = "%s must be either true or false" % attr
73            raise ValueError(msg)
74        setattr(self, attr, val)
75
76
77    def _set_int(self, attr, val, min=None, max=None):
78        val = int(val)
79        if min is not None and min > val:
80            raise ValueError("%s is %d, which is below the "
81                             "minimum of %d" % (attr, val, min))
82        if max is not None and max < val:
83            raise ValueError("%s is %d, which is above the "
84                             "maximum of %d" % (attr, val, max))
85        setattr(self, attr, val)
86
87
88    def _set_set(self, attr, val):
89        val = str(val)
90        items = [x.strip() for x in val.split(',')]
91        setattr(self, attr, set(items))
92
93
94    def set_author(self, val):
95        self._set_string('author', val)
96
97
98    def set_dependencies(self, val):
99        self._set_set('dependencies', val)
100
101
102    def set_doc(self, val):
103        self._set_string('doc', val)
104
105
106    def set_experimental(self, val):
107        self._set_bool('experimental', val)
108
109
110    def set_name(self, val):
111        self._set_string('name', val)
112
113
114    def set_run_verify(self, val):
115        self._set_bool('run_verify', val)
116
117
118    def set_sync_count(self, val):
119        self._set_int('sync_count', val, min=1)
120
121
122    def set_time(self, val):
123        self._set_option('time', val, ['short', 'medium', 'long'])
124
125
126    def set_test_class(self, val):
127        self._set_string('test_class', val.lower())
128
129
130    def set_test_category(self, val):
131        self._set_string('test_category', val.lower())
132
133
134    def set_test_type(self, val):
135        self._set_option('test_type', val, ['client', 'server'])
136
137
138def _extract_const(n):
139    assert(n.__class__ == compiler.ast.Assign)
140    assert(n.expr.__class__ == compiler.ast.Const)
141    assert(n.expr.value.__class__ in (str, int, float, unicode))
142    assert(n.nodes.__class__ == list)
143    assert(len(n.nodes) == 1)
144    assert(n.nodes[0].__class__ == compiler.ast.AssName)
145    assert(n.nodes[0].flags.__class__ == str)
146    assert(n.nodes[0].name.__class__ == str)
147
148    key = n.nodes[0].name.lower()
149    val = str(n.expr.value).strip()
150
151    return (key, val)
152
153
154def _extract_name(n):
155    assert(n.__class__ == compiler.ast.Assign)
156    assert(n.expr.__class__ == compiler.ast.Name)
157    assert(n.nodes.__class__ == list)
158    assert(len(n.nodes) == 1)
159    assert(n.nodes[0].__class__ == compiler.ast.AssName)
160    assert(n.nodes[0].flags.__class__ == str)
161    assert(n.nodes[0].name.__class__ == str)
162    assert(n.expr.name in ('False', 'True', 'None'))
163
164    key = n.nodes[0].name.lower()
165    val = str(n.expr.name)
166
167    return (key, val)
168
169
170def parse_control(path, raise_warnings=False):
171    try:
172        mod = compiler.parseFile(path)
173    except SyntaxError, e:
174        raise ControlVariableException("Error parsing %s because %s" %
175                                       (path, e))
176
177    assert(mod.__class__ == compiler.ast.Module)
178    assert(mod.node.__class__ == compiler.ast.Stmt)
179    assert(mod.node.nodes.__class__ == list)
180
181    vars = {}
182    for n in mod.node.nodes:
183        for fn in (_extract_const, _extract_name):
184            try:
185                key, val = fn(n)
186
187                vars[key] = val
188            except AssertionError, e:
189                pass
190
191    return ControlData(vars, path, raise_warnings)
192