~brendan-donegan/checkbox/fix_network_sleep_test_never_exits

« back to all changes in this revision

Viewing changes to checkbox/parsers/submission.py

  • Committer: Jeff Lane
  • Date: 2011-05-31 21:18:19 UTC
  • mfrom: (916.1.1 checkbox.story_235)
  • Revision ID: jeffrey.lane@canonical.com-20110531211819-u7n89r76t2t98u0j
Applied Marc Tardifs changes to allow for remote submission (send results from a system other than the system under test)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#
 
2
# This file is part of Checkbox.
 
3
#
 
4
# Copyright 2011 Canonical Ltd.
 
5
#
 
6
# Checkbox is free software: you can redistribute it and/or modify
 
7
# it under the terms of the GNU General Public License as published by
 
8
# the Free Software Foundation, either version 3 of the License, or
 
9
# (at your option) any later version.
 
10
#
 
11
# Checkbox is distributed in the hope that it will be useful,
 
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
14
# GNU General Public License for more details.
 
15
#
 
16
# You should have received a copy of the GNU General Public License
 
17
# along with Checkbox.  If not, see <http://www.gnu.org/licenses/>.
 
18
#
 
19
import re
 
20
 
 
21
import logging
 
22
import pytz
 
23
 
 
24
from datetime import (
 
25
    datetime,
 
26
    timedelta,
 
27
    )
 
28
from cStringIO import StringIO
 
29
 
 
30
from checkbox.parsers.device import DeviceResult
 
31
from checkbox.parsers.udev import UdevParser
 
32
from checkbox.parsers.utils import implement_from_dict
 
33
 
 
34
try:
 
35
    import xml.etree.cElementTree as etree
 
36
except ImportError:
 
37
    import cElementTree as etree
 
38
 
 
39
 
 
40
_time_regex = re.compile(r"""
 
41
    ^(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d)
 
42
    T(?P<hour>\d\d):(?P<minute>\d\d):(?P<second>\d\d)
 
43
    (?:\.(?P<second_fraction>\d{0,6}))?
 
44
    (?P<tz>
 
45
        (?:(?P<tz_sign>[-+])(?P<tz_hour>\d\d):(?P<tz_minute>\d\d))
 
46
        | Z)?$
 
47
    """,
 
48
    re.VERBOSE)
 
49
 
 
50
 
 
51
class HALDevice(object):
 
52
 
 
53
    def __init__(self, id, udi, properties):
 
54
        self.id = id
 
55
        self.udi = udi
 
56
        self.properties = properties
 
57
 
 
58
 
 
59
class SubmissionParser(object):
 
60
 
 
61
    default_name = "unknown"
 
62
 
 
63
    def __init__(self, stream, name=None):
 
64
        self.stream = stream
 
65
        self.name = name or self.default_name
 
66
 
 
67
    def _getClient(self, node):
 
68
        return "_".join([node.get('name'), node.get('version')])
 
69
 
 
70
    def _getProperty(self, node):
 
71
        """Parse a <property> node.
 
72
 
 
73
        :return: (name, (value, type)) of a property.
 
74
        """
 
75
        return (node.get('name'), self._getValueAttribute(node))
 
76
 
 
77
    def _getProperties(self, node):
 
78
        """Parse <property> sub-nodes of node.
 
79
 
 
80
        :return: A dictionary, where each key is the name of a property;
 
81
                 the values are the tuples (value, type) of a property.
 
82
        """
 
83
        properties = {}
 
84
        for child in node.getchildren():
 
85
            name, value = self._getProperty(child)
 
86
            if name in properties:
 
87
                raise ValueError(
 
88
                    '<property name="%s"> found more than once in <%s>'
 
89
                    % (name, node.tag))
 
90
            properties[name] = value
 
91
 
 
92
        return properties
 
93
 
 
94
    def _getValueAttribute(self, node):
 
95
        """Return (value, type) of a <property> or <value> node."""
 
96
        type_ = node.get('type')
 
97
        if type_ in ('dbus.Boolean', 'bool'):
 
98
            value = node.text.strip() == 'True'
 
99
 
 
100
        elif type_ in ('str', 'dbus.String', 'dbus.UTF8String'):
 
101
            value = node.text.strip()
 
102
 
 
103
        elif type_ in ('dbus.Byte', 'dbus.Int16', 'dbus.Int32', 'dbus.Int64',
 
104
                       'dbus.UInt16', 'dbus.UInt32', 'dbus.UInt64', 'int',
 
105
                       'long'):
 
106
            value = int(node.text.strip())
 
107
 
 
108
        elif type_ in ('dbus.Double', 'float'):
 
109
            value = float(node.text.strip())
 
110
 
 
111
        elif type_ in ('dbus.Array', 'list'):
 
112
            value = [self._getValueAttribute(child)
 
113
                for child in node.getchildren()]
 
114
 
 
115
        elif type_ in ('dbus.Dictionary', 'dict'):
 
116
            value = dict((child.get('name'), self._getValueAttribute(child))
 
117
                for child in node.getchildren())
 
118
 
 
119
        else:
 
120
            # This should not happen.
 
121
            raise AssertionError(
 
122
                'Unexpected <property> or <value> type: %s' % type_)
 
123
 
 
124
        return value
 
125
 
 
126
    def _getValueAttributeAsBoolean(self, node):
 
127
        """Return the value of the attribute "value" as a boolean."""
 
128
        return node.attrib['value'] == "True"
 
129
 
 
130
    def _getValueAttributeAsString(self, node):
 
131
        """Return the value of the attribute "value"."""
 
132
        return node.attrib['value']
 
133
 
 
134
    def _getValueAttributeAsDateTime(self, node):
 
135
        """Convert a "value" attribute into a datetime object."""
 
136
        time_text = node.get('value')
 
137
 
 
138
        # we cannot use time.strptime: this function accepts neither fractions
 
139
        # of a second nor a time zone given e.g. as '+02:30'.
 
140
        match = _time_regex.search(time_text)
 
141
 
 
142
        if match is None:
 
143
            raise ValueError(
 
144
                'Timestamp with unreasonable value: %s' % time_text)
 
145
 
 
146
        time_parts = match.groupdict()
 
147
 
 
148
        year = int(time_parts['year'])
 
149
        month = int(time_parts['month'])
 
150
        day = int(time_parts['day'])
 
151
        hour = int(time_parts['hour'])
 
152
        minute = int(time_parts['minute'])
 
153
        second = int(time_parts['second'])
 
154
        second_fraction = time_parts['second_fraction']
 
155
        if second_fraction is not None:
 
156
            milliseconds = second_fraction + '0' * (6 - len(second_fraction))
 
157
            milliseconds = int(milliseconds)
 
158
        else:
 
159
            milliseconds = 0
 
160
 
 
161
        if second > 59:
 
162
            second = 59
 
163
            milliseconds = 999999
 
164
 
 
165
        timestamp = datetime(year, month, day, hour, minute, second,
 
166
                             milliseconds, tzinfo=pytz.timezone('utc'))
 
167
 
 
168
        tz_sign = time_parts['tz_sign']
 
169
        tz_hour = time_parts['tz_hour']
 
170
        tz_minute = time_parts['tz_minute']
 
171
        if tz_sign in ('-', '+'):
 
172
            delta = timedelta(hours=int(tz_hour), minutes=int(tz_minute))
 
173
            if tz_sign == '-':
 
174
                timestamp = timestamp + delta
 
175
            else:
 
176
                timestamp = timestamp - delta
 
177
 
 
178
        return timestamp
 
179
 
 
180
    def _parseHAL(self, result, node):
 
181
        result.startDevices()
 
182
        for child in node.getchildren():
 
183
            id = int(child.get('id'))
 
184
            udi = child.get('udi')
 
185
            properties = self._getProperties(child)
 
186
            device = HALDevice(id, udi, properties)
 
187
            result.addDevice(device)
 
188
 
 
189
        result.endDevices()
 
190
 
 
191
    def _parseInfo(self, result, node):
 
192
        command = node.attrib['command']
 
193
        if command == "udevadm info --export-db":
 
194
            self._parseUdev(result, node)
 
195
 
 
196
        result.addInfo(command, node.text)
 
197
 
 
198
    def _parseUdev(self, result, node):
 
199
        result.startDevices()
 
200
 
 
201
        stream = StringIO(node.text)
 
202
        udev = UdevParser(stream)
 
203
        udev.run(result)
 
204
 
 
205
        result.endDevices()
 
206
 
 
207
    def _parseProcessors(self, result, node):
 
208
        result.startProcessors()
 
209
 
 
210
        for child in node.getchildren():
 
211
            id = int(child.get('id'))
 
212
            name = child.get('name')
 
213
            properties = self._getProperties(child)
 
214
            result.addProcessor(id, name, properties)
 
215
 
 
216
        result.endProcessors()
 
217
 
 
218
    def _parseRoot(self, result, node):
 
219
        parsers = {
 
220
            "summary": self._parseSummary,
 
221
            "hardware": self._parseHardware,
 
222
            "software": self._parseSoftware,
 
223
            "questions": self._parseQuestions,
 
224
            "context": self._parseContext,
 
225
            }
 
226
 
 
227
        for child in node.getchildren():
 
228
            parser = parsers.get(child.tag, self._parseNone)
 
229
            parser(result, child)
 
230
 
 
231
    def _parseSummary(self, result, node):
 
232
        parsers = {
 
233
            'live_cd': self._getValueAttributeAsBoolean,
 
234
            'system_id': self._getValueAttributeAsString,
 
235
            'distribution': self._getValueAttributeAsString,
 
236
            'distroseries': self._getValueAttributeAsString,
 
237
            'architecture': self._getValueAttributeAsString,
 
238
            'private': self._getValueAttributeAsBoolean,
 
239
            'contactable': self._getValueAttributeAsBoolean,
 
240
            'date_created': self._getValueAttributeAsDateTime,
 
241
            'client': self._getClient,
 
242
            'kernel-release': self._getValueAttributeAsString,
 
243
            }
 
244
 
 
245
        for child in node.getchildren():
 
246
            parser = parsers.get(child.tag, self._parseNone)
 
247
            value = parser(child)
 
248
            result.addSummary(child.tag, value)
 
249
 
 
250
    def _parseHardware(self, result, node):
 
251
        parsers = {
 
252
            'hal': self._parseHAL,
 
253
            'processors': self._parseProcessors,
 
254
            'udev': self._parseUdev,
 
255
            }
 
256
 
 
257
        for child in node.getchildren():
 
258
            parser = parsers.get(child.tag, self._parseNone)
 
259
            parser(result, child)
 
260
 
 
261
    def _parseLSBRelease(self, result, node):
 
262
        properties = self._getProperties(node)
 
263
        result.setDistribution(**properties)
 
264
 
 
265
    def _parsePackages(self, result, node):
 
266
        result.startPackages()
 
267
 
 
268
        for child in node.getchildren():
 
269
            id = int(child.get('id'))
 
270
            name = child.get('name')
 
271
            properties = self._getProperties(child)
 
272
 
 
273
            result.addPackage(id, name, properties)
 
274
 
 
275
        result.endPackages()
 
276
 
 
277
    def _parseXOrg(self, result, node):
 
278
        drivers = {}
 
279
        for child in node.getchildren():
 
280
            info = dict(child.attrib)
 
281
            if 'device' in info:
 
282
                info['device'] = int(info['device'])
 
283
 
 
284
            name = info['name']
 
285
            if name in drivers:
 
286
                raise ValueError(
 
287
                    '<driver name="%s"> appears more than once in <xorg>'
 
288
                    % name)
 
289
 
 
290
            drivers[name] = info
 
291
 
 
292
        version = node.get('version')
 
293
        result.addXorg(version, drivers)
 
294
 
 
295
    def _parseSoftware(self, result, node):
 
296
        parsers = {
 
297
            'lsbrelease': self._parseLSBRelease,
 
298
            'packages': self._parsePackages,
 
299
            'xorg': self._parseXOrg,
 
300
            }
 
301
 
 
302
        for child in node.getchildren():
 
303
            parser = parsers.get(child.tag, self._parseNone)
 
304
            parser(result, child)
 
305
 
 
306
    def _parseQuestions(self, result, node):
 
307
        result.startQuestions()
 
308
 
 
309
        for child in node.getchildren():
 
310
            question = {'name': child.get('name')}
 
311
            plugin = child.get('plugin', None)
 
312
            if plugin is not None:
 
313
                question['plugin'] = plugin
 
314
            question['targets'] = targets = []
 
315
            answer_choices = []
 
316
 
 
317
            for sub_node in child.getchildren():
 
318
                sub_tag = sub_node.tag
 
319
                if sub_tag == 'answer':
 
320
                    question['answer'] = answer = {}
 
321
                    answer['type'] = sub_node.get('type')
 
322
                    if answer['type'] == 'multiple_choice':
 
323
                        question['answer_choices'] = answer_choices
 
324
                    unit = sub_node.get('unit', None)
 
325
                    if unit is not None:
 
326
                        answer['unit'] = unit
 
327
                    answer['value'] = sub_node.text.strip()
 
328
                elif sub_tag == 'answer_choices':
 
329
                    for value_node in sub_node.getchildren():
 
330
                        answer_choices.append(
 
331
                            self._getValueAttribute(value_node))
 
332
                elif sub_tag == 'target':
 
333
                    target = {'id': int(sub_node.get('id'))}
 
334
                    target['drivers'] = drivers = []
 
335
                    for driver_node in sub_node.getchildren():
 
336
                        drivers.append(driver_node.text.strip())
 
337
                    targets.append(target)
 
338
                elif sub_tag in('comment', 'command'):
 
339
                    data = sub_node.text
 
340
                    if data is not None:
 
341
                        question[sub_tag] = data.strip()
 
342
 
 
343
            result.addQuestion(question)
 
344
 
 
345
        result.endQuestions()
 
346
 
 
347
    def _parseContext(self, result, node):
 
348
        parsers = {
 
349
            'info': self._parseInfo,
 
350
            }
 
351
 
 
352
        for child in node.getchildren():
 
353
            parser = parsers.get(child.tag, self._parseNone)
 
354
            parser(result, child)
 
355
 
 
356
    def _parseNone(self, result, node):
 
357
        pass
 
358
 
 
359
    def run(self, result):
 
360
        parser = etree.XMLParser()
 
361
 
 
362
        try:
 
363
            tree = etree.parse(self.stream, parser=parser)
 
364
        except SyntaxError, error:
 
365
            logging.error(error)
 
366
            return
 
367
 
 
368
        root = tree.getroot()
 
369
        if root.tag != "system":
 
370
            logging.error("Root node is not '<system>'")
 
371
            return
 
372
 
 
373
        self._parseRoot(result, root)
 
374
 
 
375
 
 
376
SubmissionResult = implement_from_dict("SubmissionResult", [
 
377
    "startDevices", "endDevices", "addDevice", "startPackages",
 
378
    "endPackages", "addPackage", "startProcessors", "endProcessors",
 
379
    "addProcessor", "startQuestions", "endQuestions", "addQuestion",
 
380
    "addInfo", "addSummary", "addXorg", "setDistribution"], DeviceResult)