Merge lp:~cr3/checkbox/story_384 into lp:checkbox
- story_384
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 1091 |
Proposed branch: | lp:~cr3/checkbox/story_384 |
Merge into: | lp:checkbox |
Diff against target: |
2872 lines (+1561/-856) 20 files modified
checkbox/dispatcher.py (+191/-0) checkbox/lib/bit.py (+5/-4) checkbox/lib/conversion.py (+140/-42) checkbox/lib/dmi.py (+169/-18) checkbox/parsers/cpuinfo.py (+30/-41) checkbox/parsers/cputable (+40/-0) checkbox/parsers/cputable.py (+42/-0) checkbox/parsers/deferred.py (+27/-0) checkbox/parsers/dmidecode.py (+123/-0) checkbox/parsers/meminfo.py (+46/-0) checkbox/parsers/submission.py (+474/-383) checkbox/parsers/udevadm.py (+178/-317) jobs/info.txt.in (+4/-0) jobs/resource.txt.in (+5/-0) scripts/cpuinfo_resource (+13/-17) scripts/dmi_resource (+55/-0) scripts/hal_resource (+0/-5) scripts/meminfo_resource (+15/-27) scripts/udev_resource (+2/-2) setup.py (+2/-0) |
To merge this branch: | bzr merge lp:~cr3/checkbox/story_384 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Daniel Manrique (community) | Approve | ||
Review via email: mp+79147@code.launchpad.net |
Commit message
Description of the change
This branch refactors the parsers significantly so that the generated submission.xml can be processed offline from another system with a different architecture. To test the checkbox and checkbox-
1. Get the latest source either from the branches, or the packages currently building in ppa:cr3 (might not be ready for a few hours), or use the packages by on chinstrap:
2. Run the command: checkbox-
3. All tests can be unselected, finish clicking on the Next button and don't worry about the invalid submission.
4. Open the URL of the submission that was created, like this one for example: https:/
To test that the generated submission.xml can be processed offline:
5. Run the command: checkbox-
6. Open the URL of the submission that was created, like this one for example: https:/
Finally, compare that both submissions have the same details for: release, architecture, attachments, devices, packages, processor and memory.
Preview Diff
1 | === added file 'checkbox/dispatcher.py' |
2 | --- checkbox/dispatcher.py 1970-01-01 00:00:00 +0000 |
3 | +++ checkbox/dispatcher.py 2011-10-12 15:56:08 +0000 |
4 | @@ -0,0 +1,191 @@ |
5 | +# Copyright 2010-2011 Canonical Ltd. This software is licensed under the |
6 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
7 | + |
8 | +__metaclass__ = type |
9 | + |
10 | +__all__ = [ |
11 | + "Dispatcher", |
12 | + "DispatcherList", |
13 | + "DispatcherQueue", |
14 | + ] |
15 | + |
16 | +import logging |
17 | + |
18 | +from itertools import product |
19 | + |
20 | + |
21 | +class Event: |
22 | + """Event payload containing the positional and keywoard arguments |
23 | + passed to the handler in the event listener.""" |
24 | + |
25 | + def __init__(self, type, *args, **kwargs): |
26 | + self.type = type |
27 | + self.args = args |
28 | + self.kwargs = kwargs |
29 | + |
30 | + |
31 | +class Listener: |
32 | + """Event listener notified when events are published by the dispatcher.""" |
33 | + |
34 | + def __init__(self, event_type, handler, count): |
35 | + self.event_type = event_type |
36 | + self.handler = handler |
37 | + self.count = count |
38 | + |
39 | + def notify(self, event): |
40 | + """Notify the handler with the payload of the event. |
41 | + |
42 | + :param event: The event containint the payload for the handler. |
43 | + """ |
44 | + if self.count is None or self.count: |
45 | + self.handler(*event.args, **event.kwargs) |
46 | + if self.count: |
47 | + self.count -= 1 |
48 | + |
49 | + |
50 | +class ListenerList(Listener): |
51 | + """Event listener notified for lists of events.""" |
52 | + |
53 | + def __init__(self, *args, **kwargs): |
54 | + super(ListenerList, self).__init__(*args, **kwargs) |
55 | + self.event_types = set(self.event_type) |
56 | + self.kwargs = {} |
57 | + |
58 | + def notify(self, event): |
59 | + """Only notify the handler when all the events for this listener |
60 | + have been published by the dispatcher. When duplicate events |
61 | + occur, the latest event is preserved and the previous one are |
62 | + overwritten until all events have been published. |
63 | + """ |
64 | + if self.count is None or self.count: |
65 | + self.kwargs[event.type] = event.args[0] |
66 | + if self.event_types.issubset(self.kwargs): |
67 | + self.handler(**self.kwargs) |
68 | + if self.count: |
69 | + self.count -= 1 |
70 | + |
71 | + |
72 | +class ListenerQueue(ListenerList): |
73 | + |
74 | + def notify(self, event): |
75 | + """Only notify the handler when all the events for this listener |
76 | + have been published by the dispatcher. Duplicate events are enqueued |
77 | + and dequeued only when all events have been published. |
78 | + """ |
79 | + arg = event.args[0] |
80 | + queue = self.kwargs.setdefault(event.type, []) |
81 | + |
82 | + # Strip duplicates from the queue. |
83 | + if arg not in queue: |
84 | + queue.append(arg) |
85 | + |
86 | + # Once the queue has handler has been called, the queue |
87 | + # then behaves like a list using the latest events. |
88 | + if self.event_types.issubset(self.kwargs): |
89 | + self.notify = notify = super(ListenerQueue, self).notify |
90 | + keys = self.kwargs.keys() |
91 | + for values in product(*self.kwargs.values()): |
92 | + self.kwargs = dict(zip(keys, values)) |
93 | + notify(event) |
94 | + |
95 | + |
96 | +class Dispatcher: |
97 | + """Register handlers and publish events for them identified by strings.""" |
98 | + |
99 | + listener_factory = Listener |
100 | + |
101 | + def __init__(self, listener_factory=None): |
102 | + self._event_listeners = {} |
103 | + |
104 | + if listener_factory is not None: |
105 | + self.listener_factory = listener_factory |
106 | + |
107 | + def registerHandler(self, event_type, handler, count=None): |
108 | + """Register an event handler and return its listener. |
109 | + |
110 | + :param event_type: The name of the event type to handle. |
111 | + :param handler: The function handling the given event type. |
112 | + :param count: Optionally, the number times to call the handler. |
113 | + """ |
114 | + listener = self.listener_factory(event_type, handler, count) |
115 | + |
116 | + listeners = self._event_listeners.setdefault(event_type, []) |
117 | + listeners.append(listener) |
118 | + |
119 | + return listener |
120 | + |
121 | + def unregisterHandler(self, handler): |
122 | + """Unregister a handler. |
123 | + |
124 | + :param handler: The handler to unregister. |
125 | + """ |
126 | + for event_type, listeners in self._event_listeners.items(): |
127 | + listeners = [ |
128 | + listener for listener in listeners |
129 | + if listener.handler == handler] |
130 | + if listeners: |
131 | + self._event_listeners[event_type] = listeners |
132 | + else: |
133 | + del self._event_listeners[event_type] |
134 | + |
135 | + def unregisterListener(self, listener): |
136 | + """Unregister a listener. |
137 | + |
138 | + :param listener: The listener of the handler to unregister. |
139 | + """ |
140 | + self._event_listeners[listener.event_type].remove(listener) |
141 | + |
142 | + def publishEvent(self, event_type, *args, **kwargs): |
143 | + """Publish an event of a given type and notify all listeners. |
144 | + |
145 | + :param event_type: The name of the event type to publish. |
146 | + :param args: Positional arguments to pass to the registered handlers. |
147 | + :param kwargs: Keyword arguments to pass to the registered handlers. |
148 | + """ |
149 | + event = Event(event_type, *args, **kwargs) |
150 | + for listener in self._event_listeners.get(event_type, ()): |
151 | + try: |
152 | + listener.notify(event) |
153 | + except: |
154 | + logging.exception( |
155 | + "Error running event handler for %r with args %r %r", |
156 | + event_type, args, kwargs) |
157 | + |
158 | + |
159 | +class DispatcherList(Dispatcher): |
160 | + """ |
161 | + Register handlers and publish events for them identified by lists |
162 | + of strings. |
163 | + """ |
164 | + |
165 | + listener_factory = ListenerList |
166 | + |
167 | + def registerHandler(self, event_types, handler, count=None): |
168 | + """See Dispatcher.""" |
169 | + if not isinstance(event_types, (list, tuple,)): |
170 | + event_types = (event_types,) |
171 | + |
172 | + listener = self.listener_factory(event_types, handler, count) |
173 | + for event_type in event_types: |
174 | + listeners = self._event_listeners.setdefault(event_type, []) |
175 | + listeners.append(listener) |
176 | + |
177 | + return listener |
178 | + |
179 | + def unregisterListener(self, listener): |
180 | + """See Dispatcher.""" |
181 | + for event_type in listener.event_types: |
182 | + self._event_listeners[event_type].remove(listener) |
183 | + |
184 | + def publishEvent(self, event_type, arg): |
185 | + """See Dispatcher.""" |
186 | + super(DispatcherList, self).publishEvent(event_type, arg) |
187 | + |
188 | + |
189 | +class DispatcherQueue(DispatcherList): |
190 | + """ |
191 | + Register handlers and publish events for them identified by lists |
192 | + of strings in queue order. |
193 | + """ |
194 | + |
195 | + listener_factory = ListenerQueue |
196 | |
197 | === modified file 'checkbox/lib/bit.py' |
198 | --- checkbox/lib/bit.py 2011-01-27 19:35:50 +0000 |
199 | +++ checkbox/lib/bit.py 2011-10-12 15:56:08 +0000 |
200 | @@ -36,10 +36,11 @@ |
201 | |
202 | return bitcount |
203 | |
204 | -def test_bit(bit, bitmask): |
205 | - bits_per_long = calcsize("l") * 8 |
206 | - offset = bit % bits_per_long |
207 | - long = int(bit / bits_per_long) |
208 | +def test_bit(bit, bitmask, bits=None): |
209 | + if bits is None: |
210 | + bits = calcsize("l") * 8 |
211 | + offset = bit % bits |
212 | + long = int(bit / bits) |
213 | if long >= len(bitmask): |
214 | return 0 |
215 | return (bitmask[long] >> offset) & 1 |
216 | |
217 | === modified file 'checkbox/lib/conversion.py' |
218 | --- checkbox/lib/conversion.py 2009-02-16 15:48:24 +0000 |
219 | +++ checkbox/lib/conversion.py 2011-10-12 15:56:08 +0000 |
220 | @@ -18,48 +18,110 @@ |
221 | # |
222 | import re |
223 | |
224 | - |
225 | -def string_to_type(value): |
226 | - conversion_table = ( |
227 | - ("(yes|true)", lambda v: True), |
228 | - ("(no|false)", lambda v: False), |
229 | - ("\d+", lambda v: int(v.group(0))), |
230 | - ("\d+\.\d+", lambda v: float(v.group(0))), |
231 | - ("(\d+) ?([kmgt]?b?)", lambda v: int(v.group(1))), |
232 | - ("(\d+\.\d+) ?([kmgt]?b?)", lambda v: float(v.group(1))), |
233 | - ("(\d+) ?([kmgt]?hz?)", lambda v: int(v.group(1))), |
234 | - ("(\d+\.\d+) ?([kmgt]?hz?)", lambda v: float(v.group(1)))) |
235 | - |
236 | - multiplier_table = ( |
237 | - ("b", 1), |
238 | - ("kb?", 1024), |
239 | - ("mb?", 1024 * 1024), |
240 | - ("gb?", 1024 * 1024 * 1024), |
241 | - ("tb?", 1024 * 1024 * 1024 * 1024), |
242 | - ("hz", 1), |
243 | - ("khz?", 1024), |
244 | - ("mhz?", 1024 * 1024), |
245 | - ("ghz?", 1024 * 1024 * 1024), |
246 | - ("thz?", 1024 * 1024 * 1024 * 1024)) |
247 | - |
248 | - if isinstance(value, basestring): |
249 | - for regex, conversion in conversion_table: |
250 | - match = re.match("^%s$" % regex, value, re.IGNORECASE) |
251 | - if match: |
252 | - value = conversion(match) |
253 | - if len(match.groups()) < 2: |
254 | - return value |
255 | - |
256 | - unit = match.group(2) |
257 | - for regex, multiplier in multiplier_table: |
258 | - match = re.match("^%s$" % regex, unit, re.IGNORECASE) |
259 | - if match: |
260 | - value *= multiplier |
261 | - return value |
262 | - else: |
263 | - raise Exception, "Unknown multiplier: %s" % unit |
264 | - |
265 | - return value |
266 | +from dateutil import tz |
267 | +from datetime import ( |
268 | + datetime, |
269 | + timedelta, |
270 | + ) |
271 | + |
272 | + |
273 | +DATETIME_RE = re.compile(r""" |
274 | + ^(?P<year>\d\d\d\d)-?(?P<month>\d\d)-?(?P<day>\d\d) |
275 | + T(?P<hour>\d\d):?(?P<minute>\d\d):?(?P<second>\d\d) |
276 | + (?:\.(?P<second_fraction>\d{0,6}))? |
277 | + (?P<tz> |
278 | + (?:(?P<tz_sign>[-+])(?P<tz_hour>\d\d):(?P<tz_minute>\d\d)) |
279 | + | Z)?$ |
280 | + """, re.VERBOSE) |
281 | + |
282 | +TYPE_FORMATS = ( |
283 | + (r"(yes|true)", lambda v: True), |
284 | + (r"(no|false)", lambda v: False), |
285 | + (r"-?\d+", lambda v: int(v.group(0))), |
286 | + (r"-?\d+\.\d+", lambda v: float(v.group(0))), |
287 | + (r"(-?\d+) ?([kmgt]?b?)", lambda v: int(v.group(1))), |
288 | + (r"(-?\d+\.\d+) ?([kmgt]?b?)", lambda v: float(v.group(1))), |
289 | + (r"(-?\d+) ?([kmgt]?hz)", lambda v: int(v.group(1))), |
290 | + (r"(-?\d+\.\d+) ?([kmgt]?hz)", lambda v: float(v.group(1)))) |
291 | +TYPE_FORMATS = tuple( |
292 | + (re.compile(r"^%s$" % pattern, re.IGNORECASE), format) |
293 | + for pattern, format in TYPE_FORMATS) |
294 | + |
295 | +TYPE_MULTIPLIERS = ( |
296 | + (r"b", 1), |
297 | + (r"kb?", 1024), |
298 | + (r"mb?", 1024 * 1024), |
299 | + (r"gb?", 1024 * 1024 * 1024), |
300 | + (r"tb?", 1024 * 1024 * 1024 * 1024), |
301 | + (r"hz", 1), |
302 | + (r"khz?", 1024), |
303 | + (r"mhz?", 1024 * 1024), |
304 | + (r"ghz?", 1024 * 1024 * 1024), |
305 | + (r"thz?", 1024 * 1024 * 1024 * 1024)) |
306 | +TYPE_MULTIPLIERS = tuple( |
307 | + (re.compile(r"^%s$" % pattern, re.IGNORECASE), multiplier) |
308 | + for pattern, multiplier in TYPE_MULTIPLIERS) |
309 | + |
310 | + |
311 | +def datetime_to_string(dt): |
312 | + """Return a consistent string representation for a given datetime. |
313 | + |
314 | + :param dt: The datetime object. |
315 | + """ |
316 | + return dt.isoformat() |
317 | + |
318 | +def string_to_datetime(string): |
319 | + """Return a datetime object from a consistent string representation. |
320 | + |
321 | + :param string: The string representation. |
322 | + """ |
323 | + # we cannot use time.strptime: this function accepts neither fractions |
324 | + # of a second nor a time zone given e.g. as '+02:30'. |
325 | + match = DATETIME_RE.match(string) |
326 | + |
327 | + # The Relax NG schema allows a leading minus sign and year numbers |
328 | + # with more than four digits, which are not "covered" by _time_regex. |
329 | + if not match: |
330 | + raise ValueError("Datetime with unreasonable value: %s" % string) |
331 | + |
332 | + time_parts = match.groupdict() |
333 | + |
334 | + year = int(time_parts['year']) |
335 | + month = int(time_parts['month']) |
336 | + day = int(time_parts['day']) |
337 | + hour = int(time_parts['hour']) |
338 | + minute = int(time_parts['minute']) |
339 | + second = int(time_parts['second']) |
340 | + second_fraction = time_parts['second_fraction'] |
341 | + if second_fraction is not None: |
342 | + milliseconds = second_fraction + '0' * (6 - len(second_fraction)) |
343 | + milliseconds = int(milliseconds) |
344 | + else: |
345 | + milliseconds = 0 |
346 | + |
347 | + # The Relax NG validator accepts leap seconds, but the datetime |
348 | + # constructor rejects them. The time values submitted by the HWDB |
349 | + # client are not necessarily very precise, hence we can round down |
350 | + # to 59.999999 seconds without losing any real precision. |
351 | + if second > 59: |
352 | + second = 59 |
353 | + milliseconds = 999999 |
354 | + |
355 | + dt = datetime( |
356 | + year, month, day, hour, minute, second, milliseconds, |
357 | + tzinfo=tz.tzutc()) |
358 | + |
359 | + tz_sign = time_parts['tz_sign'] |
360 | + tz_hour = time_parts['tz_hour'] |
361 | + tz_minute = time_parts['tz_minute'] |
362 | + if tz_sign in ('-', '+'): |
363 | + delta = timedelta(hours=int(tz_hour), minutes=int(tz_minute)) |
364 | + if tz_sign == '-': |
365 | + dt = dt + delta |
366 | + else: |
367 | + dt = dt - delta |
368 | + |
369 | + return dt |
370 | |
371 | def sizeof_bytes(bytes): |
372 | for x in ["bytes", "KB", "MB", "GB", "TB"]: |
373 | @@ -78,3 +140,39 @@ |
374 | hertz /= 1000.0 |
375 | |
376 | return string |
377 | + |
378 | +def string_to_type(string, string_type=None): |
379 | + """Return a typed representation for the given string. |
380 | + |
381 | + The result might be a bool, int or float. The string might also be |
382 | + supplemented by a multiplier like KB which would return an int or |
383 | + float multiplied by 1024 for example. |
384 | + |
385 | + :param string: The string representation. |
386 | + :param string_type: Optional type to cast the result. |
387 | + """ |
388 | + if not isinstance(string, basestring): |
389 | + raise TypeError( |
390 | + "string_to_type() argument 1 must be string, not %s" |
391 | + % type(string)) |
392 | + |
393 | + for regex, formatter in TYPE_FORMATS: |
394 | + match = regex.match(string) |
395 | + if match: |
396 | + string = formatter(match) |
397 | + if len(match.groups()) > 1: |
398 | + unit = match.group(2) |
399 | + for regex, multiplier in TYPE_MULTIPLIERS: |
400 | + match = regex.match(unit) |
401 | + if match: |
402 | + string *= multiplier |
403 | + break |
404 | + else: |
405 | + raise ValueError("Unknown multiplier: %s" % unit) |
406 | + |
407 | + break |
408 | + |
409 | + if string_type is not None: |
410 | + string = string_type(string) |
411 | + |
412 | + return string |
413 | |
414 | === modified file 'checkbox/lib/dmi.py' |
415 | --- checkbox/lib/dmi.py 2009-09-06 14:19:16 +0000 |
416 | +++ checkbox/lib/dmi.py 2011-10-12 15:56:08 +0000 |
417 | @@ -16,13 +16,15 @@ |
418 | # You should have received a copy of the GNU General Public License |
419 | # along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
420 | # |
421 | +import os |
422 | + |
423 | |
424 | # See also 3.3.4.1 of the "System Management BIOS Reference Specification, |
425 | # Version 2.6.1" (Preliminary Standard) document, available from |
426 | # http://www.dmtf.org/standards/smbios. |
427 | class Dmi: |
428 | chassis = ( |
429 | - ("Undefined", "unknown"), # 0x00 |
430 | + ("Undefined", "unknown"), # 0x00 |
431 | ("Other", "unknown"), |
432 | ("Unknown", "unknown"), |
433 | ("Desktop", "desktop"), |
434 | @@ -53,22 +55,171 @@ |
435 | ("Blade", "server"), |
436 | ("Blade Enclosure", "unknown")) |
437 | |
438 | - chassis_names = [c[0] for c in chassis] |
439 | - chassis_types = [c[1] for c in chassis] |
440 | + chassis_names = tuple(c[0] for c in chassis) |
441 | + chassis_types = tuple(c[1] for c in chassis) |
442 | chassis_name_to_type = dict(chassis) |
443 | |
444 | - |
445 | -class DmiNotAvailable(object): |
446 | - def __init__(self, function): |
447 | - self._function = function |
448 | - |
449 | - def __get__(self, instance, cls=None): |
450 | - self._instance = instance |
451 | - return self |
452 | - |
453 | - def __call__(self, *args, **kwargs): |
454 | - name = self._function(self._instance, *args, **kwargs) |
455 | - if name == "Not Available": |
456 | - name = None |
457 | - |
458 | - return name |
459 | + type_names = ( |
460 | + "BIOS", # 0x00 |
461 | + "System", |
462 | + "Base Board", |
463 | + "Chassis", |
464 | + "Processor", |
465 | + "Memory Controller", |
466 | + "Memory Module", |
467 | + "Cache", |
468 | + "Port Connector", |
469 | + "System Slots", |
470 | + "On Board Devices", |
471 | + "OEM Strings", |
472 | + "System Configuration Options", |
473 | + "BIOS Language", |
474 | + "Group Associations", |
475 | + "System Event Log", |
476 | + "Physical Memory Array", |
477 | + "Memory Device", |
478 | + "32-bit Memory Error", |
479 | + "Memory Array Mapped Address", |
480 | + "Memory Device Mapped Address", |
481 | + "Built-in Pointing Device", |
482 | + "Portable Battery", |
483 | + "System Reset", |
484 | + "Hardware Security", |
485 | + "System Power Controls", |
486 | + "Voltage Probe", |
487 | + "Cooling Device", |
488 | + "Temperature Probe", |
489 | + "Electrical Current Probe", |
490 | + "Out-of-band Remote Access", |
491 | + "Boot Integrity Services", |
492 | + "System Boot", |
493 | + "64-bit Memory Error", |
494 | + "Management Device", |
495 | + "Management Device Component", |
496 | + "Management Device Threshold Data", |
497 | + "Memory Channel", |
498 | + "IPMI Device", |
499 | + "Power Supply", |
500 | + ) |
501 | + |
502 | + |
503 | +class DmiDevice: |
504 | + |
505 | + bus = "dmi" |
506 | + driver = None |
507 | + product_id = None |
508 | + vendor_id = None |
509 | + |
510 | + _product_blacklist = ( |
511 | + "<BAD INDEX>", |
512 | + "N/A", |
513 | + "Not Available", |
514 | + "INVALID", |
515 | + "OEM", |
516 | + "Product Name", |
517 | + "System Product Name", |
518 | + "To be filled by O.E.M.", |
519 | + "To Be Filled By O.E.M.", |
520 | + "To Be Filled By O.E.M. by More String", |
521 | + "Unknown", |
522 | + "Uknown", |
523 | + "Unknow", |
524 | + "xxxxxxxxxxxxxx", |
525 | + ) |
526 | + _vendor_blacklist = ( |
527 | + "<BAD INDEX>", |
528 | + "Not Available", |
529 | + "OEM", |
530 | + "OEM Manufacturer", |
531 | + "System manufacturer", |
532 | + "System Manufacturer", |
533 | + "System Name", |
534 | + "To be filled by O.E.M.", |
535 | + "To Be Filled By O.E.M.", |
536 | + "To Be Filled By O.E.M. by More String", |
537 | + "Unknow", # XXX This is correct mispelling |
538 | + "Unknown", |
539 | + ) |
540 | + _serial_blacklist = ( |
541 | + "0", |
542 | + "00000000", |
543 | + "00 00 00 00 00 00 00 00", |
544 | + "0123456789", |
545 | + "Base Board Serial Number", |
546 | + "Chassis Serial Number", |
547 | + "N/A", |
548 | + "None", |
549 | + "Not Applicable", |
550 | + "Not Available", |
551 | + "Not Specified", |
552 | + "OEM", |
553 | + "System Serial Number", |
554 | + ) |
555 | + _version_blacklist = ( |
556 | + "-1", |
557 | + "<BAD INDEX>", |
558 | + "N/A", |
559 | + "None", |
560 | + "Not Applicable", |
561 | + "Not Available", |
562 | + "Not Specified", |
563 | + "OEM", |
564 | + "System Version", |
565 | + "Unknown", |
566 | + "x.x", |
567 | + ) |
568 | + |
569 | + def __init__(self, attributes, category): |
570 | + self._attributes = attributes |
571 | + self.category = category |
572 | + |
573 | + @property |
574 | + def path(self): |
575 | + path = "/devices/virtual/dmi/id" |
576 | + return os.path.join(path, self.category.lower()) |
577 | + |
578 | + @property |
579 | + def product(self): |
580 | + if self.category == "CHASSIS": |
581 | + type_string = self._attributes.get("chassis_type", "0") |
582 | + try: |
583 | + type_index = int(type_string) |
584 | + return Dmi.chassis_names[type_index] |
585 | + except ValueError: |
586 | + return type_string |
587 | + |
588 | + for name in "name", "version": |
589 | + attribute = "%s_%s" % (self.category.lower(), name) |
590 | + product = self._attributes.get(attribute) |
591 | + if product and product not in self._product_blacklist: |
592 | + return product |
593 | + |
594 | + return None |
595 | + |
596 | + @property |
597 | + def vendor(self): |
598 | + for name in "manufacturer", "vendor": |
599 | + attribute = "%s_%s" % (self.category.lower(), name) |
600 | + vendor = self._attributes.get(attribute) |
601 | + if vendor and vendor not in self._vendor_blacklist: |
602 | + return vendor |
603 | + |
604 | + return None |
605 | + |
606 | + @property |
607 | + def serial(self): |
608 | + attribute = "%s_serial" % self.category.lower() |
609 | + serial = self._attributes.get(attribute) |
610 | + if serial and serial not in self._serial_blacklist: |
611 | + return serial |
612 | + |
613 | + return None |
614 | + |
615 | + @property |
616 | + def version(self): |
617 | + attribute = "%s_version" % self.category.lower() |
618 | + version = self._attributes.get(attribute) |
619 | + if version and version not in self._version_blacklist: |
620 | + return version |
621 | + |
622 | + return None |
623 | |
624 | === modified file 'checkbox/parsers/cpuinfo.py' |
625 | --- checkbox/parsers/cpuinfo.py 2011-05-30 20:42:48 +0000 |
626 | +++ checkbox/parsers/cpuinfo.py 2011-10-12 15:56:08 +0000 |
627 | @@ -16,24 +16,26 @@ |
628 | # You should have received a copy of the GNU General Public License |
629 | # along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
630 | # |
631 | -import os |
632 | import re |
633 | |
634 | +from os import uname |
635 | + |
636 | from checkbox.lib.conversion import string_to_type |
637 | -from checkbox.parsers.utils import implement_from_dict |
638 | - |
639 | - |
640 | -class CpuinfoParser(object): |
641 | - |
642 | - def __init__(self, stream, uname=None): |
643 | + |
644 | + |
645 | +class CpuinfoParser: |
646 | + """Parser for the /proc/cpuinfo file.""" |
647 | + |
648 | + def __init__(self, stream, machine=None): |
649 | self.stream = stream |
650 | - self.uname = uname or os.uname()[4].lower() |
651 | + self.machine = machine or uname()[4].lower() |
652 | |
653 | def getAttributes(self): |
654 | count = 0 |
655 | attributes = {} |
656 | cpuinfo = self.stream.read() |
657 | - for block in cpuinfo.split("\n\n"): |
658 | + for block in re.split(r"\n{2,}", cpuinfo): |
659 | + block = block.strip() |
660 | if not block: |
661 | continue |
662 | |
663 | @@ -54,19 +56,23 @@ |
664 | |
665 | attributes[key.lower()] = value |
666 | |
667 | - attributes["count"] = count |
668 | + if attributes: |
669 | + attributes["count"] = count |
670 | + |
671 | return attributes |
672 | |
673 | def run(self, result): |
674 | - uname = self.uname |
675 | attributes = self.getAttributes() |
676 | + if not attributes: |
677 | + return |
678 | |
679 | # Default values |
680 | + machine = self.machine |
681 | processor = { |
682 | - "platform": uname, |
683 | + "platform": machine, |
684 | "count": 1, |
685 | - "type": self.uname, |
686 | - "model": self.uname, |
687 | + "type": machine, |
688 | + "model": machine, |
689 | "model_number": "", |
690 | "model_version": "", |
691 | "model_revision": "", |
692 | @@ -113,7 +119,6 @@ |
693 | "type": "platform", |
694 | "model": "cpu", |
695 | "model_version": "revision", |
696 | - "vendor": "machine", |
697 | "speed": "clock"}, |
698 | ("sparc64", "sparc",): { |
699 | "count": "ncpus probed", |
700 | @@ -126,7 +131,7 @@ |
701 | bogompips_string = attributes.get("bogomips", "0.0") |
702 | processor["bogomips"] = int(round(float(bogompips_string))) |
703 | for platform, conversion in platform_to_conversion.iteritems(): |
704 | - if uname in platform: |
705 | + if machine in platform: |
706 | for pkey, ckey in conversion.iteritems(): |
707 | if isinstance(ckey, (list, tuple)): |
708 | processor[pkey] = "/".join([attributes[k] |
709 | @@ -134,13 +139,11 @@ |
710 | elif ckey in attributes: |
711 | processor[pkey] = attributes[ckey] |
712 | |
713 | - # Adjust platform and vendor |
714 | - if uname[0] == "i" and uname[-2:] == "86": |
715 | - processor["platform"] = "i386" |
716 | - elif uname[:5] == "alpha": |
717 | - processor["platform"] = "alpha" |
718 | - elif uname[:5] == "sparc": |
719 | - processor["vendor"] = "sun" |
720 | + # Adjust platform |
721 | + if machine[0] == "i" and machine[-2:] == "86": |
722 | + processor["platform"] = u"i386" |
723 | + elif machine[:5] == "alpha": |
724 | + processor["platform"] = u"alpha" |
725 | |
726 | # Adjust cache |
727 | if processor["cache"]: |
728 | @@ -148,14 +151,14 @@ |
729 | |
730 | # Adjust speed |
731 | try: |
732 | - if uname[:5] == "alpha": |
733 | + if machine[:5] == "alpha": |
734 | speed = processor["speed"].split()[0] |
735 | processor["speed"] = int(round(float(speed))) / 1000000 |
736 | - elif uname[:5] == "sparc": |
737 | + elif machine[:5] == "sparc": |
738 | speed = processor["speed"] |
739 | processor["speed"] = int(round(float(speed))) / 2 |
740 | else: |
741 | - if uname[:3] == "ppc": |
742 | + if machine[:3] == "ppc": |
743 | # String is appended with "mhz" |
744 | speed = processor["speed"][:-3] |
745 | else: |
746 | @@ -174,18 +177,4 @@ |
747 | if processor["count"] == 0: |
748 | processor["count"] = 1 |
749 | |
750 | - for key, value in processor.iteritems(): |
751 | - camel_case = key.replace("_", " ").title().replace(" ", "") |
752 | - method_name = "set%s" % camel_case |
753 | - method = getattr(result, method_name) |
754 | - method(value) |
755 | - |
756 | - |
757 | -class CpuinfoResult(dict): |
758 | - |
759 | - def __getattr__(self, name): |
760 | - name = re.sub(r"([A-Z])", "_\\1", name)[4:].lower() |
761 | - def setAll(value): |
762 | - self[name] = value |
763 | - |
764 | - return setAll |
765 | + result.setProcessor(processor) |
766 | |
767 | === added file 'checkbox/parsers/cputable' |
768 | --- checkbox/parsers/cputable 1970-01-01 00:00:00 +0000 |
769 | +++ checkbox/parsers/cputable 2011-10-12 15:56:08 +0000 |
770 | @@ -0,0 +1,40 @@ |
771 | +# This file contains the table of known CPU names. |
772 | +# |
773 | +# Architecture names are formed as a combination of the system name |
774 | +# (from ostable) and CPU name (from this table) after mapping from |
775 | +# the Debian triplet (from triplettable). A list of architecture |
776 | +# names in the Debian ‘sid’ distribution can be found in the archtable |
777 | +# file. |
778 | +# |
779 | +# Column 1 is the Debian name for the CPU, used to form the cpu part in |
780 | +# the Debian triplet. |
781 | +# Column 2 is the GNU name for the CPU, used to output build and host |
782 | +# targets in ‘dpkg-architecture’. |
783 | +# Column 3 is an extended regular expression used to match against the |
784 | +# CPU part of the output of the GNU config.guess script. |
785 | +# Column 4 is the size (in bits) of the integers/pointers |
786 | +# Column 5 is the endianness (byte ordering in numbers) |
787 | +# |
788 | +# <Debian name> <GNU name> <config.guess regex> <Bits> <Endianness> |
789 | +i386 i686 (i[3456]86|pentium) 32 little |
790 | +ia64 ia64 ia64 64 little |
791 | +alpha alpha alpha.* 64 little |
792 | +amd64 x86_64 x86_64 64 little |
793 | +armeb armeb arm.*b 32 big |
794 | +arm arm arm.* 32 little |
795 | +avr32 avr32 avr32 32 big |
796 | +hppa hppa hppa.* 32 big |
797 | +m32r m32r m32r 32 big |
798 | +m68k m68k m68k 32 big |
799 | +mips mips mips(eb)? 32 big |
800 | +mipsel mipsel mipsel 32 little |
801 | +powerpc powerpc (powerpc|ppc) 32 big |
802 | +ppc64 powerpc64 (powerpc|ppc)64 64 big |
803 | +s390 s390 s390 32 big |
804 | +s390x s390x s390x 64 big |
805 | +sh3 sh3 sh3 32 little |
806 | +sh3eb sh3eb sh3eb 32 big |
807 | +sh4 sh4 sh4 32 little |
808 | +sh4eb sh4eb sh4eb 32 big |
809 | +sparc sparc sparc 32 big |
810 | +sparc64 sparc64 sparc64 64 big |
811 | |
812 | === added file 'checkbox/parsers/cputable.py' |
813 | --- checkbox/parsers/cputable.py 1970-01-01 00:00:00 +0000 |
814 | +++ checkbox/parsers/cputable.py 2011-10-12 15:56:08 +0000 |
815 | @@ -0,0 +1,42 @@ |
816 | +# |
817 | +# This file is part of Checkbox. |
818 | +# |
819 | +# Copyright 2011 Canonical Ltd. |
820 | +# |
821 | +# Checkbox is free software: you can redistribute it and/or modify |
822 | +# it under the terms of the GNU General Public License as published by |
823 | +# the Free Software Foundation, either version 3 of the License, or |
824 | +# (at your option) any later version. |
825 | +# |
826 | +# Checkbox is distributed in the hope that it will be useful, |
827 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
828 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
829 | +# GNU General Public License for more details. |
830 | +# |
831 | +# You should have received a copy of the GNU General Public License |
832 | +# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
833 | +# |
834 | +import re |
835 | + |
836 | + |
837 | +CPUTABLE_RE = re.compile( |
838 | + r"^(?!\#)(?P<debian_name>\S+)" |
839 | + r"\s+(?P<gnu_name>\S+)" |
840 | + r"\s+(?P<regex>\S+)" |
841 | + r"\s+(?P<bits>\d+)" |
842 | + r"\s+(?P<endianness>big|little)") |
843 | + |
844 | + |
845 | +class CputableParser: |
846 | + """Parser for the /usr/share/dpkg/cputable file.""" |
847 | + |
848 | + def __init__(self, stream): |
849 | + self.stream = stream |
850 | + |
851 | + def run(self, result): |
852 | + for line in self.stream.readlines(): |
853 | + match = CPUTABLE_RE.match(line) |
854 | + if match: |
855 | + cpu = match.groupdict() |
856 | + cpu["bits"] = int(cpu["bits"]) |
857 | + result.addCpu(cpu) |
858 | |
859 | === added file 'checkbox/parsers/deferred.py' |
860 | --- checkbox/parsers/deferred.py 1970-01-01 00:00:00 +0000 |
861 | +++ checkbox/parsers/deferred.py 2011-10-12 15:56:08 +0000 |
862 | @@ -0,0 +1,27 @@ |
863 | +# |
864 | +# This file is part of Checkbox. |
865 | +# |
866 | +# Copyright 2011 Canonical Ltd. |
867 | +# |
868 | +# Checkbox is free software: you can redistribute it and/or modify |
869 | +# it under the terms of the GNU General Public License as published by |
870 | +# the Free Software Foundation, either version 3 of the License, or |
871 | +# (at your option) any later version. |
872 | +# |
873 | +# Checkbox is distributed in the hope that it will be useful, |
874 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
875 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
876 | +# GNU General Public License for more details. |
877 | +# |
878 | +# You should have received a copy of the GNU General Public License |
879 | +# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
880 | +# |
881 | +class DeferredParser: |
882 | + """Parser for deferred dispatching of events.""" |
883 | + |
884 | + def __init__(self, dispatcher, event_type="result"): |
885 | + self.dispatcher = dispatcher |
886 | + self.event_type = event_type |
887 | + |
888 | + def run(self, result): |
889 | + self.dispatcher.publishEvent(self.event_type, result) |
890 | |
891 | === added file 'checkbox/parsers/dmidecode.py' |
892 | --- checkbox/parsers/dmidecode.py 1970-01-01 00:00:00 +0000 |
893 | +++ checkbox/parsers/dmidecode.py 2011-10-12 15:56:08 +0000 |
894 | @@ -0,0 +1,123 @@ |
895 | +# |
896 | +# This file is part of Checkbox. |
897 | +# |
898 | +# Copyright 2011 Canonical Ltd. |
899 | +# |
900 | +# Checkbox is free software: you can redistribute it and/or modify |
901 | +# it under the terms of the GNU General Public License as published by |
902 | +# the Free Software Foundation, either version 3 of the License, or |
903 | +# (at your option) any later version. |
904 | +# |
905 | +# Checkbox is distributed in the hope that it will be useful, |
906 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
907 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
908 | +# GNU General Public License for more details. |
909 | +# |
910 | +# You should have received a copy of the GNU General Public License |
911 | +# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
912 | +# |
913 | +import re |
914 | + |
915 | +from string import ( |
916 | + hexdigits, |
917 | + uppercase, |
918 | + ) |
919 | + |
920 | +from checkbox.lib.dmi import ( |
921 | + Dmi, |
922 | + DmiDevice, |
923 | + ) |
924 | + |
925 | + |
926 | +HANDLE_RE = re.compile( |
927 | + r"^Handle (?P<handle>0x[%s]{4}), " |
928 | + r"DMI type (?P<type>\d+), " |
929 | + r"(?P<size>\d+) bytes$" |
930 | + % hexdigits) |
931 | +KEY_VALUE_RE = re.compile( |
932 | + r"^\t(?P<key>[%s].+):( (?P<value>.+))?$" |
933 | + % uppercase) |
934 | + |
935 | + |
936 | +class DmidecodeParser: |
937 | + """Parser for the dmidecode command.""" |
938 | + |
939 | + _key_map = { |
940 | + "ID": "serial", |
941 | + "Manufacturer": "vendor", |
942 | + "Product Name": "name", |
943 | + "Serial Number": "serial", |
944 | + "Type": "type", |
945 | + "Vendor": "vendor", |
946 | + "Version": "version", |
947 | + } |
948 | + |
949 | + def __init__(self, stream): |
950 | + self.stream = stream |
951 | + |
952 | + def _parseKey(self, key): |
953 | + return self._key_map.get(key) |
954 | + |
955 | + def _parseValue(self, value): |
956 | + if value is not None: |
957 | + value = value.strip() |
958 | + if not value: |
959 | + value = None |
960 | + |
961 | + return value |
962 | + |
963 | + def run(self, result): |
964 | + output = self.stream.read() |
965 | + for record in re.split(r"\n{2,}", output): |
966 | + record = record.strip() |
967 | + # Skip empty records |
968 | + if not record: |
969 | + continue |
970 | + |
971 | + # Skip header record |
972 | + lines = record.split("\n") |
973 | + line = lines.pop(0) |
974 | + if line.startswith("#"): |
975 | + continue |
976 | + |
977 | + # Skip records with an unsupported handle |
978 | + match = HANDLE_RE.match(line) |
979 | + if not match: |
980 | + continue |
981 | + |
982 | + # Skip records that are empty or inactive |
983 | + if not lines or lines.pop(0) == "Inactive": |
984 | + continue |
985 | + |
986 | + # Skip disabled entries and end-of-table marker |
987 | + type_index = int(match.group("type")) |
988 | + if type_index >= len(Dmi.type_names): |
989 | + continue |
990 | + |
991 | + category = Dmi.type_names[type_index] |
992 | + category = category.upper().split(" ")[-1] |
993 | + if category not in ( |
994 | + "BOARD", "BIOS", "CHASSIS", "PROCESSOR", "SYSTEM"): |
995 | + continue |
996 | + |
997 | + # Parse attributes |
998 | + attributes = {} |
999 | + for line in lines: |
1000 | + # Skip lines with an unsupported key/value pair |
1001 | + match = KEY_VALUE_RE.match(line) |
1002 | + if not match: |
1003 | + continue |
1004 | + |
1005 | + # Skip lines with an unsupported key |
1006 | + key = self._parseKey(match.group("key")) |
1007 | + if not key: |
1008 | + continue |
1009 | + |
1010 | + key = "%s_%s" % (category.lower(), key) |
1011 | + value = self._parseValue(match.group("value")) |
1012 | + attributes[key] = value |
1013 | + |
1014 | + device = DmiDevice(attributes, category) |
1015 | + result.addDmiDevice(device) |
1016 | + |
1017 | + return result |
1018 | |
1019 | === added file 'checkbox/parsers/meminfo.py' |
1020 | --- checkbox/parsers/meminfo.py 1970-01-01 00:00:00 +0000 |
1021 | +++ checkbox/parsers/meminfo.py 2011-10-12 15:56:08 +0000 |
1022 | @@ -0,0 +1,46 @@ |
1023 | +# |
1024 | +# This file is part of Checkbox. |
1025 | +# |
1026 | +# Copyright 2011 Canonical Ltd. |
1027 | +# |
1028 | +# Checkbox is free software: you can redistribute it and/or modify |
1029 | +# it under the terms of the GNU General Public License as published by |
1030 | +# the Free Software Foundation, either version 3 of the License, or |
1031 | +# (at your option) any later version. |
1032 | +# |
1033 | +# Checkbox is distributed in the hope that it will be useful, |
1034 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1035 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1036 | +# GNU General Public License for more details. |
1037 | +# |
1038 | +# You should have received a copy of the GNU General Public License |
1039 | +# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
1040 | +# |
1041 | +import re |
1042 | + |
1043 | + |
1044 | +class MeminfoParser: |
1045 | + """Parser for the /proc/meminfo file.""" |
1046 | + |
1047 | + def __init__(self, stream): |
1048 | + self.stream = stream |
1049 | + |
1050 | + def run(self, result): |
1051 | + key_value_pattern = re.compile(r"(?P<key>.*):\s+(?P<value>.*)") |
1052 | + meminfo_map = { |
1053 | + "MemTotal": "total", |
1054 | + "SwapTotal": "swap"} |
1055 | + |
1056 | + meminfo = {} |
1057 | + for line in self.stream.readlines(): |
1058 | + line = line.strip() |
1059 | + match = key_value_pattern.match(line) |
1060 | + if match: |
1061 | + key = match.group("key") |
1062 | + if key in meminfo_map: |
1063 | + key = meminfo_map[key] |
1064 | + value = match.group("value") |
1065 | + (integer, factor) = value.split() |
1066 | + meminfo[key] = int(integer) * 1024 |
1067 | + |
1068 | + result.setMemory(meminfo) |
1069 | |
1070 | === modified file 'checkbox/parsers/submission.py' |
1071 | --- checkbox/parsers/submission.py 2011-06-08 15:16:37 +0000 |
1072 | +++ checkbox/parsers/submission.py 2011-10-12 15:56:08 +0000 |
1073 | @@ -16,430 +16,521 @@ |
1074 | # You should have received a copy of the GNU General Public License |
1075 | # along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
1076 | # |
1077 | -import re |
1078 | - |
1079 | -import logging |
1080 | - |
1081 | -from datetime import ( |
1082 | - datetime, |
1083 | - timedelta, |
1084 | - ) |
1085 | -from dateutil import tz |
1086 | - |
1087 | -from checkbox.parsers.device import DeviceResult |
1088 | -from checkbox.parsers.udev import UdevParser |
1089 | -from checkbox.parsers.utils import implement_from_dict |
1090 | - |
1091 | try: |
1092 | import xml.etree.cElementTree as etree |
1093 | except ImportError: |
1094 | import cElementTree as etree |
1095 | |
1096 | - |
1097 | -_time_regex = re.compile(r""" |
1098 | - ^(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d) |
1099 | - T(?P<hour>\d\d):(?P<minute>\d\d):(?P<second>\d\d) |
1100 | - (?:\.(?P<second_fraction>\d{0,6}))? |
1101 | - (?P<tz> |
1102 | - (?:(?P<tz_sign>[-+])(?P<tz_hour>\d\d):(?P<tz_minute>\d\d)) |
1103 | - | Z)?$ |
1104 | - """, |
1105 | - re.VERBOSE) |
1106 | - |
1107 | -_xml_illegal_regex = re.compile( |
1108 | - u"([\u0000-\u0008\u000b-\u000c\u000e-\u001f\ufffe-\uffff])" |
1109 | - + u"|([%s-%s][^%s-%s])|([^%s-%s][%s-%s])|([%s-%s]$)|(^[%s-%s])" % ( |
1110 | - unichr(0xd800),unichr(0xdbff),unichr(0xdc00),unichr(0xdfff), |
1111 | - unichr(0xd800),unichr(0xdbff),unichr(0xdc00),unichr(0xdfff), |
1112 | - unichr(0xd800),unichr(0xdbff),unichr(0xdc00),unichr(0xdfff))) |
1113 | - |
1114 | - |
1115 | -class HALDevice(object): |
1116 | - |
1117 | - def __init__(self, id, udi, properties): |
1118 | - self.id = id |
1119 | - self.udi = udi |
1120 | - self.properties = properties |
1121 | - |
1122 | - |
1123 | -class SubmissionStream(object): |
1124 | - |
1125 | - default_size = 4096 |
1126 | - |
1127 | - def __init__(self, stream): |
1128 | - self.stream = stream |
1129 | - self._buffer = "" |
1130 | - self._buffers = [] |
1131 | - |
1132 | - def read(self, size=None): |
1133 | - if size is None: |
1134 | - size = self.default_size |
1135 | - |
1136 | - info_start_regex = re.compile("^<info .*>$") |
1137 | - info_end_regex = re.compile("^</info>$") |
1138 | - |
1139 | - in_info = False |
1140 | - length = sum(len(buffer) for buffer in self._buffers) |
1141 | - |
1142 | - while length < size: |
1143 | - try: |
1144 | - buffer = self.stream.next() |
1145 | - except StopIteration: |
1146 | - break |
1147 | - |
1148 | - if not in_info: |
1149 | - if info_start_regex.match(buffer): |
1150 | - in_info = True |
1151 | - self._buffer += "".join(self._buffers) |
1152 | - self._buffers = [buffer] |
1153 | - else: |
1154 | - length += len(buffer) |
1155 | - self._buffers.append(buffer) |
1156 | - else: |
1157 | - self._buffers.append(buffer) |
1158 | - if info_end_regex.match(buffer): |
1159 | - in_info = False |
1160 | - |
1161 | - buffer = "".join(self._buffers) |
1162 | - self._buffers = [] |
1163 | - |
1164 | - if not _xml_illegal_regex.search(buffer): |
1165 | - length += len(buffer) |
1166 | - self._buffer += buffer |
1167 | - |
1168 | - if self._buffers: |
1169 | - self._buffer += "".join(self._buffers) |
1170 | - self._buffers = [] |
1171 | - |
1172 | - if not self._buffer: |
1173 | - return None |
1174 | - |
1175 | - data = self._buffer[:size] |
1176 | - self._buffers = [self._buffer[size:]] |
1177 | - self._buffer = "" |
1178 | - |
1179 | - return data |
1180 | - |
1181 | - |
1182 | -class SubmissionParser(object): |
1183 | - |
1184 | - default_name = "unknown" |
1185 | - |
1186 | - def __init__(self, stream, name=None): |
1187 | - self.stream = SubmissionStream(stream) |
1188 | - self.name = name or self.default_name |
1189 | +from StringIO import StringIO |
1190 | +from logging import getLogger |
1191 | +from pkg_resources import resource_string |
1192 | + |
1193 | +from checkbox.lib.conversion import string_to_datetime |
1194 | + |
1195 | +from checkbox import parsers |
1196 | +from checkbox.dispatcher import DispatcherQueue |
1197 | +from checkbox.parsers.cpuinfo import CpuinfoParser |
1198 | +from checkbox.parsers.cputable import CputableParser |
1199 | +from checkbox.parsers.deferred import DeferredParser |
1200 | +from checkbox.parsers.dmidecode import DmidecodeParser |
1201 | +from checkbox.parsers.meminfo import MeminfoParser |
1202 | +from checkbox.parsers.udevadm import UdevadmParser |
1203 | +from checkbox.job import (FAIL, PASS, UNINITIATED, UNRESOLVED, |
1204 | + UNSUPPORTED, UNTESTED) |
1205 | + |
1206 | + |
1207 | +class SubmissionResult: |
1208 | + |
1209 | + def __init__(self, test_run_factory, **kwargs): |
1210 | + self.test_run_factory = test_run_factory |
1211 | + self.test_run_kwargs = kwargs |
1212 | + self.dispatcher = DispatcherQueue() |
1213 | + |
1214 | + # Register handlers to incrementally add information |
1215 | + register = self.dispatcher.registerHandler |
1216 | + register(("cpu", "architecture",), self.addCpuArchitecture) |
1217 | + register(("identifier",), self.addIdentifier) |
1218 | + register(("test_run", "attachment",), self.addAttachment) |
1219 | + register(("test_run", "device",), self.addDeviceState) |
1220 | + register(("test_run", "distribution",), self.setDistribution) |
1221 | + register(("test_run", "package_version",), self.addPackageVersion) |
1222 | + register(("test_run", "test_result",), self.addTestResult) |
1223 | + |
1224 | + # Register handlers to set information once |
1225 | + register(("architecture",), self.setArchitecture, count=1) |
1226 | + register( |
1227 | + ("cpuinfo", "machine", "cpuinfo_result",), |
1228 | + self.setCpuinfo, count=1) |
1229 | + register( |
1230 | + ("meminfo", "meminfo_result",), |
1231 | + self.setMeminfo, count=1) |
1232 | + register( |
1233 | + ("project", "series",), |
1234 | + self.setTestRun, count=1) |
1235 | + register( |
1236 | + ("test_run", "architecture",), |
1237 | + self.setArchitectureState, count=1) |
1238 | + register( |
1239 | + ("test_run", "memory",), |
1240 | + self.setMemoryState, count=1) |
1241 | + register( |
1242 | + ("test_run", "processor",), |
1243 | + self.setProcessorState, count=1) |
1244 | + register( |
1245 | + ("udevadm", "bits", "udevadm_result",), |
1246 | + self.setUdevadm, count=1) |
1247 | + |
1248 | + # Publish events passed as keyword arguments |
1249 | + if "project" in kwargs: |
1250 | + self.dispatcher.publishEvent("project", kwargs.pop("project")) |
1251 | + self.dispatcher.publishEvent("series", kwargs.pop("series", None)) |
1252 | + |
1253 | + def addAttachment(self, test_run, attachment): |
1254 | + test_run.addAttachment(**attachment) |
1255 | + |
1256 | + def addContext(self, text, command=None): |
1257 | + if text.strip() == "Command not found.": |
1258 | + return |
1259 | + |
1260 | + self.dispatcher.publishEvent( |
1261 | + "attachment", {"name": command, "content": text }) |
1262 | + |
1263 | + parsers = { |
1264 | + "cat /proc/cpuinfo": self.parseCpuinfo, |
1265 | + "cat /proc/meminfo": self.parseMeminfo, |
1266 | + "dmidecode": DmidecodeParser, |
1267 | + "udevadm info --export-db": self.parseUdevadm, |
1268 | + } |
1269 | + parser = parsers.get(command) |
1270 | + if parser: |
1271 | + if not isinstance(text, unicode): |
1272 | + text = text.decode("utf-8") |
1273 | + stream = StringIO(text) |
1274 | + p = parser(stream) |
1275 | + p.run(self) |
1276 | + |
1277 | + def addCpu(self, cpu): |
1278 | + self.dispatcher.publishEvent("cpu", cpu) |
1279 | + |
1280 | + def addCpuArchitecture(self, cpu, architecture): |
1281 | + if cpu["debian_name"] == architecture: |
1282 | + self.dispatcher.publishEvent("machine", cpu["gnu_name"]) |
1283 | + self.dispatcher.publishEvent("bits", cpu["bits"]) |
1284 | + |
1285 | + def addDevice(self, device): |
1286 | + self.dispatcher.publishEvent("device", device) |
1287 | + |
1288 | + def addDeviceState(self, test_run, device): |
1289 | + test_run.addDeviceState( |
1290 | + bus_name=device.bus, category_name=device.category, |
1291 | + product_name=device.product, vendor_name=device.vendor, |
1292 | + product_id=device.product_id, vendor_id=device.vendor_id, |
1293 | + subproduct_id=device.subproduct_id, |
1294 | + subvendor_id=device.subvendor_id, |
1295 | + driver_name=device.driver, path=device.path) |
1296 | + |
1297 | + def addDmiDevice(self, device): |
1298 | + if device.serial: |
1299 | + self.dispatcher.publishEvent("identifier", device.serial) |
1300 | + |
1301 | + if device.category in ("BOARD", "SYSTEM") \ |
1302 | + and device.vendor != device.product \ |
1303 | + and device.product is not None: |
1304 | + self.dispatcher.publishEvent("model", device.product) |
1305 | + self.dispatcher.publishEvent("make", device.vendor) |
1306 | + self.dispatcher.publishEvent("version", device.version) |
1307 | + |
1308 | + def addIdentifier(self, identifier): |
1309 | + try: |
1310 | + self.identifiers.append(identifier) |
1311 | + except AttributeError: |
1312 | + self.identifiers = [identifier] |
1313 | + self.dispatcher.publishEvent("identifiers", self.identifiers) |
1314 | + |
1315 | + def addPackage(self, package): |
1316 | + package_version = { |
1317 | + "name": package["name"], |
1318 | + "version": package["properties"]["version"], |
1319 | + } |
1320 | + self.dispatcher.publishEvent("package_version", package_version) |
1321 | + |
1322 | + def addPackageVersion(self, test_run, package_version): |
1323 | + test_run.addPackageVersion(**package_version) |
1324 | + |
1325 | + def addQuestion(self, question): |
1326 | + answer_to_status = { |
1327 | + "fail": FAIL, |
1328 | + "no": FAIL, |
1329 | + "pass": PASS, |
1330 | + "skip": UNTESTED, |
1331 | + "uninitiated": UNINITIATED, |
1332 | + "unresolved": UNRESOLVED, |
1333 | + "unsupported": UNSUPPORTED, |
1334 | + "untested": UNTESTED, |
1335 | + "yes": PASS, |
1336 | + } |
1337 | + |
1338 | + test_result = dict( |
1339 | + name=question["name"], |
1340 | + output=question["comment"], |
1341 | + status=answer_to_status[question["answer"]["value"]], |
1342 | + ) |
1343 | + test_result.update(self.test_run_kwargs) |
1344 | + self.dispatcher.publishEvent("test_result", test_result) |
1345 | + |
1346 | + def addTestResult(self, test_run, test_result): |
1347 | + test_run.addTestResult(**test_result) |
1348 | + |
1349 | + def addSummary(self, name, value): |
1350 | + if name == "architecture": |
1351 | + self.dispatcher.publishEvent("architecture", value) |
1352 | + elif name == "distribution": |
1353 | + self.dispatcher.publishEvent("project", value) |
1354 | + elif name == "distroseries": |
1355 | + self.dispatcher.publishEvent("series", value) |
1356 | + |
1357 | + def parseCpuinfo(self, cpuinfo): |
1358 | + self.dispatcher.publishEvent("cpuinfo", cpuinfo) |
1359 | + return DeferredParser(self.dispatcher, "cpuinfo_result") |
1360 | + |
1361 | + def parseMeminfo(self, meminfo): |
1362 | + self.dispatcher.publishEvent("meminfo", meminfo) |
1363 | + return DeferredParser(self.dispatcher, "meminfo_result") |
1364 | + |
1365 | + def parseUdevadm(self, udevadm): |
1366 | + self.dispatcher.publishEvent("udevadm", udevadm) |
1367 | + return DeferredParser(self.dispatcher, "udevadm_result") |
1368 | + |
1369 | + def setArchitecture(self, architecture): |
1370 | + string = resource_string(parsers.__name__, "cputable") |
1371 | + stream = StringIO(string.decode("utf-8")) |
1372 | + parser = CputableParser(stream) |
1373 | + parser.run(self) |
1374 | + |
1375 | + def setArchitectureState(self, test_run, architecture): |
1376 | + test_run.setArchitectureState(architecture) |
1377 | + |
1378 | + def setCpuinfo(self, cpuinfo, machine, cpuinfo_result): |
1379 | + parser = CpuinfoParser(cpuinfo, machine) |
1380 | + parser.run(cpuinfo_result) |
1381 | + |
1382 | + def setMeminfo(self, meminfo, meminfo_result): |
1383 | + parser = MeminfoParser(meminfo) |
1384 | + parser.run(meminfo_result) |
1385 | + |
1386 | + def setDistribution(self, test_run, distribution): |
1387 | + test_run.setDistribution(**distribution) |
1388 | + |
1389 | + def setLSBRelease(self, lsb_release): |
1390 | + self.dispatcher.publishEvent("distribution", lsb_release) |
1391 | + |
1392 | + def setMemory(self, memory): |
1393 | + self.dispatcher.publishEvent("memory", memory) |
1394 | + |
1395 | + def setMemoryState(self, test_run, memory): |
1396 | + test_run.setMemoryState(**memory) |
1397 | + |
1398 | + def setProcessor(self, processor): |
1399 | + self.dispatcher.publishEvent("processor", processor) |
1400 | + |
1401 | + def setProcessorState(self, test_run, processor): |
1402 | + test_run.setProcessorState( |
1403 | + platform_name=processor["platform"], |
1404 | + make=processor["type"], model=processor["model"], |
1405 | + model_number=processor["model_number"], |
1406 | + model_version=processor["model_version"], |
1407 | + model_revision=processor["model_revision"], |
1408 | + cache=processor["cache"], other=processor["other"], |
1409 | + bogomips=processor["bogomips"], speed=processor["speed"], |
1410 | + count=processor["count"]) |
1411 | + |
1412 | + def setTestRun(self, project, series): |
1413 | + test_run = self.test_run_factory( |
1414 | + **self.test_run_kwargs) |
1415 | + self.dispatcher.publishEvent("test_run", test_run) |
1416 | + |
1417 | + def setUdevadm(self, udevadm, bits, udevadm_result): |
1418 | + parser = UdevadmParser(udevadm, bits) |
1419 | + parser.run(udevadm_result) |
1420 | + |
1421 | + |
1422 | +class SubmissionParser: |
1423 | + |
1424 | + def __init__(self, file): |
1425 | + self.file = file |
1426 | + self.logger = getLogger() |
1427 | |
1428 | def _getClient(self, node): |
1429 | - return "_".join([node.get('name'), node.get('version')]) |
1430 | + """Return a dictionary with the name and version of the client.""" |
1431 | + return { |
1432 | + "name": node.get("name"), |
1433 | + "version": node.get("version"), |
1434 | + } |
1435 | |
1436 | def _getProperty(self, node): |
1437 | - """Parse a <property> node. |
1438 | - |
1439 | - :return: (name, (value, type)) of a property. |
1440 | - """ |
1441 | - return (node.get('name'), self._getValueAttribute(node)) |
1442 | + """Return the (name, value) of a property.""" |
1443 | + return (node.get("name"), self._getValueAsType(node)) |
1444 | |
1445 | def _getProperties(self, node): |
1446 | - """Parse <property> sub-nodes of node. |
1447 | - |
1448 | - :return: A dictionary, where each key is the name of a property; |
1449 | - the values are the tuples (value, type) of a property. |
1450 | - """ |
1451 | + """Return a dictionary of properties.""" |
1452 | properties = {} |
1453 | for child in node.getchildren(): |
1454 | + assert child.tag == "property", \ |
1455 | + "Unexpected tag <%s>, expected <property>" % child.tag |
1456 | name, value = self._getProperty(child) |
1457 | - if name in properties: |
1458 | - raise ValueError( |
1459 | - '<property name="%s"> found more than once in <%s>' |
1460 | - % (name, node.tag)) |
1461 | properties[name] = value |
1462 | |
1463 | return properties |
1464 | |
1465 | - def _getValueAttribute(self, node): |
1466 | - """Return (value, type) of a <property> or <value> node.""" |
1467 | - type_ = node.get('type') |
1468 | - if type_ in ('dbus.Boolean', 'bool'): |
1469 | - value = node.text.strip() == 'True' |
1470 | - |
1471 | - elif type_ in ('str', 'dbus.String', 'dbus.UTF8String'): |
1472 | + def _getValueAsType(self, node): |
1473 | + """Return value of a node as the type attribute.""" |
1474 | + type_ = node.get("type") |
1475 | + if type_ in ("bool",): |
1476 | value = node.text.strip() |
1477 | - |
1478 | - elif type_ in ('dbus.Byte', 'dbus.Int16', 'dbus.Int32', 'dbus.Int64', |
1479 | - 'dbus.UInt16', 'dbus.UInt32', 'dbus.UInt64', 'int', |
1480 | - 'long'): |
1481 | - value = int(node.text.strip()) |
1482 | - |
1483 | - elif type_ in ('dbus.Double', 'float'): |
1484 | - value = float(node.text.strip()) |
1485 | - |
1486 | - elif type_ in ('dbus.Array', 'list'): |
1487 | - value = [self._getValueAttribute(child) |
1488 | - for child in node.getchildren()] |
1489 | - |
1490 | - elif type_ in ('dbus.Dictionary', 'dict'): |
1491 | - value = dict((child.get('name'), self._getValueAttribute(child)) |
1492 | - for child in node.getchildren()) |
1493 | - |
1494 | + assert value in ("True", "False",), \ |
1495 | + "Unexpected boolean value '%s' in <%s>" % (value, node.tag) |
1496 | + return value == "True" |
1497 | + elif type_ in ("str",): |
1498 | + return unicode(node.text.strip()) |
1499 | + elif type_ in ("int", "long",): |
1500 | + return int(node.text.strip()) |
1501 | + elif type_ in ("float",): |
1502 | + return float(node.text.strip()) |
1503 | + elif type_ in ("list",): |
1504 | + return list(self._getValueAsType(child) |
1505 | + for child in node.getchildren()) |
1506 | + elif type_ in ("dict",): |
1507 | + return dict((child.get("name"), self._getValueAsType(child)) |
1508 | + for child in node.getchildren()) |
1509 | else: |
1510 | - # This should not happen. |
1511 | raise AssertionError( |
1512 | - 'Unexpected <property> or <value> type: %s' % type_) |
1513 | - |
1514 | - return value |
1515 | - |
1516 | - def _getValueAttributeAsBoolean(self, node): |
1517 | + "Unexpected type '%s' in <%s>" % (type_, node.tag)) |
1518 | + |
1519 | + def _getValueAsBoolean(self, node): |
1520 | """Return the value of the attribute "value" as a boolean.""" |
1521 | - return node.attrib['value'] == "True" |
1522 | - |
1523 | - def _getValueAttributeAsString(self, node): |
1524 | + value = node.attrib["value"] |
1525 | + assert value in ("True", "False",), \ |
1526 | + "Unexpected boolean value '%s' in tag <%s>" % (value, node.tag) |
1527 | + return value == "True" |
1528 | + |
1529 | + def _getValueAsDatetime(self, node): |
1530 | + """Return the value of the attribute "value" as a datetime.""" |
1531 | + string = node.attrib["value"] |
1532 | + return string_to_datetime(string) |
1533 | + |
1534 | + def _getValueAsString(self, node): |
1535 | """Return the value of the attribute "value".""" |
1536 | - return node.attrib['value'] |
1537 | - |
1538 | - def _getValueAttributeAsDateTime(self, node): |
1539 | - """Convert a "value" attribute into a datetime object.""" |
1540 | - time_text = node.get('value') |
1541 | - |
1542 | - # we cannot use time.strptime: this function accepts neither fractions |
1543 | - # of a second nor a time zone given e.g. as '+02:30'. |
1544 | - match = _time_regex.search(time_text) |
1545 | - |
1546 | - if match is None: |
1547 | - raise ValueError( |
1548 | - 'Timestamp with unreasonable value: %s' % time_text) |
1549 | - |
1550 | - time_parts = match.groupdict() |
1551 | - |
1552 | - year = int(time_parts['year']) |
1553 | - month = int(time_parts['month']) |
1554 | - day = int(time_parts['day']) |
1555 | - hour = int(time_parts['hour']) |
1556 | - minute = int(time_parts['minute']) |
1557 | - second = int(time_parts['second']) |
1558 | - second_fraction = time_parts['second_fraction'] |
1559 | - if second_fraction is not None: |
1560 | - milliseconds = second_fraction + '0' * (6 - len(second_fraction)) |
1561 | - milliseconds = int(milliseconds) |
1562 | - else: |
1563 | - milliseconds = 0 |
1564 | - |
1565 | - if second > 59: |
1566 | - second = 59 |
1567 | - milliseconds = 999999 |
1568 | - |
1569 | - timestamp = datetime(year, month, day, hour, minute, second, |
1570 | - milliseconds, tzinfo=tz.tzutc()) |
1571 | - |
1572 | - tz_sign = time_parts['tz_sign'] |
1573 | - tz_hour = time_parts['tz_hour'] |
1574 | - tz_minute = time_parts['tz_minute'] |
1575 | - if tz_sign in ('-', '+'): |
1576 | - delta = timedelta(hours=int(tz_hour), minutes=int(tz_minute)) |
1577 | - if tz_sign == '-': |
1578 | - timestamp = timestamp + delta |
1579 | - else: |
1580 | - timestamp = timestamp - delta |
1581 | - |
1582 | - return timestamp |
1583 | - |
1584 | - def _parseHAL(self, result, node): |
1585 | - result.startDevices() |
1586 | - for child in node.getchildren(): |
1587 | - id = int(child.get('id')) |
1588 | - udi = child.get('udi') |
1589 | - properties = self._getProperties(child) |
1590 | - device = HALDevice(id, udi, properties) |
1591 | - result.addDevice(device) |
1592 | - |
1593 | - result.endDevices() |
1594 | - |
1595 | - def _parseInfo(self, result, node): |
1596 | - command = node.attrib['command'] |
1597 | - if command == "udevadm info --export-db": |
1598 | - self._parseUdev(result, node) |
1599 | - |
1600 | - result.addInfo(command, node.text) |
1601 | - |
1602 | - def _parseUdev(self, result, node): |
1603 | - result.startDevices() |
1604 | - |
1605 | - stream = StringIO(node.text) |
1606 | - udev = UdevParser(stream) |
1607 | - udev.run(result) |
1608 | - |
1609 | - result.endDevices() |
1610 | - |
1611 | - def _parseProcessors(self, result, node): |
1612 | - result.startProcessors() |
1613 | - |
1614 | - for child in node.getchildren(): |
1615 | - id = int(child.get('id')) |
1616 | - name = child.get('name') |
1617 | - properties = self._getProperties(child) |
1618 | - result.addProcessor(id, name, properties) |
1619 | - |
1620 | - result.endProcessors() |
1621 | - |
1622 | - def _parseRoot(self, result, node): |
1623 | - parsers = { |
1624 | - "summary": self._parseSummary, |
1625 | - "hardware": self._parseHardware, |
1626 | - "software": self._parseSoftware, |
1627 | - "questions": self._parseQuestions, |
1628 | - "context": self._parseContext, |
1629 | - } |
1630 | - |
1631 | - for child in node.getchildren(): |
1632 | - parser = parsers.get(child.tag, self._parseNone) |
1633 | - parser(result, child) |
1634 | - |
1635 | - def _parseSummary(self, result, node): |
1636 | - parsers = { |
1637 | - 'live_cd': self._getValueAttributeAsBoolean, |
1638 | - 'system_id': self._getValueAttributeAsString, |
1639 | - 'distribution': self._getValueAttributeAsString, |
1640 | - 'distroseries': self._getValueAttributeAsString, |
1641 | - 'architecture': self._getValueAttributeAsString, |
1642 | - 'private': self._getValueAttributeAsBoolean, |
1643 | - 'contactable': self._getValueAttributeAsBoolean, |
1644 | - 'date_created': self._getValueAttributeAsDateTime, |
1645 | - 'client': self._getClient, |
1646 | - 'kernel-release': self._getValueAttributeAsString, |
1647 | - } |
1648 | - |
1649 | - for child in node.getchildren(): |
1650 | - parser = parsers.get(child.tag, self._parseNone) |
1651 | - value = parser(child) |
1652 | - result.addSummary(child.tag, value) |
1653 | - |
1654 | - def _parseHardware(self, result, node): |
1655 | - parsers = { |
1656 | - 'hal': self._parseHAL, |
1657 | - 'processors': self._parseProcessors, |
1658 | - 'udev': self._parseUdev, |
1659 | - } |
1660 | - |
1661 | - for child in node.getchildren(): |
1662 | - parser = parsers.get(child.tag, self._parseNone) |
1663 | - parser(result, child) |
1664 | - |
1665 | - def _parseLSBRelease(self, result, node): |
1666 | + return unicode(node.attrib["value"]) |
1667 | + |
1668 | + def parseContext(self, result, node): |
1669 | + """Parse the <context> part of a submission.""" |
1670 | + duplicates = set() |
1671 | + for child in node.getchildren(): |
1672 | + assert child.tag == "info", \ |
1673 | + "Unexpected tag <%s>, expected <info>" % child.tag |
1674 | + command = child.get("command") |
1675 | + if command not in duplicates: |
1676 | + duplicates.add(command) |
1677 | + result.addContext(child.text, command) |
1678 | + else: |
1679 | + self.logger.debug( |
1680 | + "Duplicate command found in tag <info>: %s" % command) |
1681 | + |
1682 | + def parseHardware(self, result, node): |
1683 | + """Parse the <hardware> section of a submission.""" |
1684 | + parsers = { |
1685 | + "dmi": DmidecodeParser, |
1686 | + "processors": self.parseProcessors, |
1687 | + "udev": result.parseUdevadm, |
1688 | + } |
1689 | + |
1690 | + for child in node.getchildren(): |
1691 | + parser = parsers.get(child.tag) |
1692 | + if parser: |
1693 | + if child.getchildren(): |
1694 | + parser(result, child) |
1695 | + else: |
1696 | + text = child.text |
1697 | + if not isinstance(text, unicode): |
1698 | + text = text.decode("utf-8") |
1699 | + stream = StringIO(text) |
1700 | + p = parser(stream) |
1701 | + p.run(result) |
1702 | + else: |
1703 | + self.logger.debug( |
1704 | + "Unsupported tag <%s> in <hardware>" % child.tag) |
1705 | + |
1706 | + def parseLSBRelease(self, result, node): |
1707 | + """Parse the <lsbrelease> part of a submission.""" |
1708 | properties = self._getProperties(node) |
1709 | - result.setDistribution(**properties) |
1710 | - |
1711 | - def _parsePackages(self, result, node): |
1712 | - result.startPackages() |
1713 | - |
1714 | - for child in node.getchildren(): |
1715 | - id = int(child.get('id')) |
1716 | - name = child.get('name') |
1717 | + result.setLSBRelease(properties) |
1718 | + |
1719 | + def parsePackages(self, result, node): |
1720 | + """Parse the <packages> part of a submission.""" |
1721 | + for child in node.getchildren(): |
1722 | + assert child.tag == "package", \ |
1723 | + "Unexpected tag <%s>, expected <package>" % child.tag |
1724 | + |
1725 | + package = { |
1726 | + "name": child.get("name"), |
1727 | + "properties": self._getProperties(child), |
1728 | + } |
1729 | + result.addPackage(package) |
1730 | + |
1731 | + def parseProcessors(self, result, node): |
1732 | + """Parse the <processors> part of a submission.""" |
1733 | + processors = [] |
1734 | + for child in node.getchildren(): |
1735 | + assert child.tag == "processor", \ |
1736 | + "Unexpected tag <%s>, expected <processor>" % child.tag |
1737 | + |
1738 | + # Convert lists to space separated strings. |
1739 | properties = self._getProperties(child) |
1740 | - |
1741 | - result.addPackage(id, name, properties) |
1742 | - |
1743 | - result.endPackages() |
1744 | - |
1745 | - def _parseXOrg(self, result, node): |
1746 | - drivers = {} |
1747 | - for child in node.getchildren(): |
1748 | - info = dict(child.attrib) |
1749 | - if 'device' in info: |
1750 | - info['device'] = int(info['device']) |
1751 | - |
1752 | - name = info['name'] |
1753 | - if name in drivers: |
1754 | - raise ValueError( |
1755 | - '<driver name="%s"> appears more than once in <xorg>' |
1756 | - % name) |
1757 | - |
1758 | - drivers[name] = info |
1759 | - |
1760 | - version = node.get('version') |
1761 | - result.addXorg(version, drivers) |
1762 | - |
1763 | - def _parseSoftware(self, result, node): |
1764 | - parsers = { |
1765 | - 'lsbrelease': self._parseLSBRelease, |
1766 | - 'packages': self._parsePackages, |
1767 | - 'xorg': self._parseXOrg, |
1768 | - } |
1769 | - |
1770 | - for child in node.getchildren(): |
1771 | - parser = parsers.get(child.tag, self._parseNone) |
1772 | - parser(result, child) |
1773 | - |
1774 | - def _parseQuestions(self, result, node): |
1775 | - result.startQuestions() |
1776 | - |
1777 | - for child in node.getchildren(): |
1778 | - question = {'name': child.get('name')} |
1779 | - plugin = child.get('plugin', None) |
1780 | + for key, value in properties.iteritems(): |
1781 | + if key in ("bogomips", "cache", "count", "speed",): |
1782 | + properties[key] = int(value) |
1783 | + elif isinstance(value, list): |
1784 | + properties[key] = u" ".join(value) |
1785 | + processors.append(properties) |
1786 | + |
1787 | + # Check if /proc/cpuinfo was parsed already. |
1788 | + if any("platform" in processor for processor in processors): |
1789 | + result.setProcessor(processors[0]) |
1790 | + else: |
1791 | + lines = [] |
1792 | + for processor in processors: |
1793 | + # Convert some keys with underscores to spaces instead. |
1794 | + for key, value in processor.iteritems(): |
1795 | + if "_" in key and key != "vendor_id": |
1796 | + key = key.replace("_", " ") |
1797 | + |
1798 | + lines.append(u"%s: %s" % (key, value)) |
1799 | + |
1800 | + lines.append(u"") |
1801 | + |
1802 | + stream = StringIO(u"\n".join(lines)) |
1803 | + parser = result.parseCpuinfo(stream) |
1804 | + parser.run(result) |
1805 | + |
1806 | + def parseQuestions(self, result, node): |
1807 | + """Parse the <questions> part of a submission.""" |
1808 | + for child in node.getchildren(): |
1809 | + assert child.tag == "question", \ |
1810 | + "Unexpected tag <%s>, expected <question>" % child.tag |
1811 | + question = { |
1812 | + "name": child.get("name"), |
1813 | + "targets": [], |
1814 | + } |
1815 | + plugin = child.get("plugin", None) |
1816 | if plugin is not None: |
1817 | - question['plugin'] = plugin |
1818 | - question['targets'] = targets = [] |
1819 | + question["plugin"] = plugin |
1820 | + |
1821 | answer_choices = [] |
1822 | - |
1823 | for sub_node in child.getchildren(): |
1824 | sub_tag = sub_node.tag |
1825 | - if sub_tag == 'answer': |
1826 | - question['answer'] = answer = {} |
1827 | - answer['type'] = sub_node.get('type') |
1828 | - if answer['type'] == 'multiple_choice': |
1829 | - question['answer_choices'] = answer_choices |
1830 | - unit = sub_node.get('unit', None) |
1831 | + if sub_tag == "answer": |
1832 | + question["answer"] = answer = {} |
1833 | + answer["type"] = sub_node.get("type") |
1834 | + if answer["type"] == "multiple_choice": |
1835 | + question["answer_choices"] = answer_choices |
1836 | + unit = sub_node.get("unit", None) |
1837 | if unit is not None: |
1838 | - answer['unit'] = unit |
1839 | - answer['value'] = sub_node.text.strip() |
1840 | - elif sub_tag == 'answer_choices': |
1841 | + answer["unit"] = unit |
1842 | + answer["value"] = sub_node.text.strip() |
1843 | + |
1844 | + elif sub_tag == "answer_choices": |
1845 | for value_node in sub_node.getchildren(): |
1846 | answer_choices.append( |
1847 | - self._getValueAttribute(value_node)) |
1848 | - elif sub_tag == 'target': |
1849 | - target = {'id': int(sub_node.get('id'))} |
1850 | - target['drivers'] = drivers = [] |
1851 | + self._getValueAsType(value_node)) |
1852 | + |
1853 | + elif sub_tag == "target": |
1854 | + # The Relax NG schema ensures that the attribute |
1855 | + # id exists and that it is an integer |
1856 | + target = {"id": int(sub_node.get("id"))} |
1857 | + target["drivers"] = drivers = [] |
1858 | for driver_node in sub_node.getchildren(): |
1859 | drivers.append(driver_node.text.strip()) |
1860 | - targets.append(target) |
1861 | - elif sub_tag in('comment', 'command'): |
1862 | + question["targets"].append(target) |
1863 | + |
1864 | + elif sub_tag in ("comment", "command",): |
1865 | data = sub_node.text |
1866 | if data is not None: |
1867 | question[sub_tag] = data.strip() |
1868 | |
1869 | + else: |
1870 | + raise AssertionError( |
1871 | + "Unexpected tag <%s> in <question>" % sub_tag) |
1872 | + |
1873 | result.addQuestion(question) |
1874 | |
1875 | - result.endQuestions() |
1876 | - |
1877 | - def _parseContext(self, result, node): |
1878 | - parsers = { |
1879 | - 'info': self._parseInfo, |
1880 | - } |
1881 | - |
1882 | - for child in node.getchildren(): |
1883 | - parser = parsers.get(child.tag, self._parseNone) |
1884 | - parser(result, child) |
1885 | - |
1886 | - def _parseNone(self, result, node): |
1887 | - pass |
1888 | - |
1889 | - def run(self, result): |
1890 | + def parseSoftware(self, result, node): |
1891 | + """Parse the <software> section of a submission.""" |
1892 | + parsers = { |
1893 | + "lsbrelease": self.parseLSBRelease, |
1894 | + "packages": self.parsePackages, |
1895 | + } |
1896 | + |
1897 | + for child in node.getchildren(): |
1898 | + parser = parsers.get(child.tag) |
1899 | + if parser: |
1900 | + parser(result, child) |
1901 | + else: |
1902 | + self.logger.debug( |
1903 | + "Unsupported tag <%s> in <software>" % child.tag) |
1904 | + |
1905 | + def parseSummary(self, result, node): |
1906 | + """Parse the <summary> section of a submission.""" |
1907 | + parsers = { |
1908 | + "architecture": self._getValueAsString, |
1909 | + "client": self._getClient, |
1910 | + "contactable": self._getValueAsBoolean, |
1911 | + "date_created": self._getValueAsDatetime, |
1912 | + "distribution": self._getValueAsString, |
1913 | + "distroseries": self._getValueAsString, |
1914 | + "kernel-release": self._getValueAsString, |
1915 | + "live_cd": self._getValueAsBoolean, |
1916 | + "private": self._getValueAsBoolean, |
1917 | + "system_id": self._getValueAsString, |
1918 | + } |
1919 | + |
1920 | + for child in node.getchildren(): |
1921 | + parser = parsers.get(child.tag) |
1922 | + if parser: |
1923 | + value = parser(child) |
1924 | + result.addSummary(child.tag, value) |
1925 | + else: |
1926 | + self.logger.debug( |
1927 | + "Unsupported tag <%s> in <summary>" % child.tag) |
1928 | + |
1929 | + def parseRoot(self, result, node): |
1930 | + """Parse the <system> root of a submission.""" |
1931 | + parsers = { |
1932 | + "context": self.parseContext, |
1933 | + "hardware": self.parseHardware, |
1934 | + "questions": self.parseQuestions, |
1935 | + "software": self.parseSoftware, |
1936 | + "summary": self.parseSummary, |
1937 | + } |
1938 | + |
1939 | + # Iterate over the root children, "summary" first |
1940 | + for child in node.getchildren(): |
1941 | + parser = parsers.get(child.tag) |
1942 | + if parser: |
1943 | + parser(result, child) |
1944 | + else: |
1945 | + self.logger.debug( |
1946 | + "Unsupported tag <%s> in <system>" % child.tag) |
1947 | + |
1948 | + def run(self, test_run_factory, **kwargs): |
1949 | parser = etree.XMLParser() |
1950 | |
1951 | - try: |
1952 | - tree = etree.parse(self.stream, parser=parser) |
1953 | - except SyntaxError, error: |
1954 | - logging.error(error) |
1955 | - return |
1956 | - |
1957 | + tree = etree.parse(self.file, parser=parser) |
1958 | root = tree.getroot() |
1959 | if root.tag != "system": |
1960 | - logging.error("Root node is not '<system>'") |
1961 | - return |
1962 | - |
1963 | - self._parseRoot(result, root) |
1964 | - |
1965 | - |
1966 | -SubmissionResult = implement_from_dict("SubmissionResult", [ |
1967 | - "startDevices", "endDevices", "addDevice", "startPackages", |
1968 | - "endPackages", "addPackage", "startProcessors", "endProcessors", |
1969 | - "addProcessor", "startQuestions", "endQuestions", "addQuestion", |
1970 | - "addInfo", "addSummary", "addXorg", "setDistribution"], DeviceResult) |
1971 | + raise AssertionError( |
1972 | + "Unexpected tag <%s> at root, expected <system>" % root.tag) |
1973 | + |
1974 | + result = SubmissionResult(test_run_factory, **kwargs) |
1975 | + self.parseRoot(result, root) |
1976 | + |
1977 | + return result |
1978 | |
1979 | === renamed file 'checkbox/parsers/udev.py' => 'checkbox/parsers/udevadm.py' |
1980 | --- checkbox/parsers/udev.py 2011-09-09 19:24:58 +0000 |
1981 | +++ checkbox/parsers/udevadm.py 2011-10-12 15:56:08 +0000 |
1982 | @@ -16,38 +16,78 @@ |
1983 | # You should have received a copy of the GNU General Public License |
1984 | # along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
1985 | # |
1986 | -import os |
1987 | import re |
1988 | import string |
1989 | -import posixpath |
1990 | - |
1991 | -from curses.ascii import isprint |
1992 | - |
1993 | -from checkbox.lib.bit import get_bitmask, test_bit |
1994 | -from checkbox.lib.dmi import Dmi, DmiNotAvailable |
1995 | + |
1996 | +from checkbox.lib.bit import ( |
1997 | + get_bitmask, |
1998 | + test_bit, |
1999 | + ) |
2000 | from checkbox.lib.input import Input |
2001 | from checkbox.lib.pci import Pci |
2002 | from checkbox.lib.usb import Usb |
2003 | |
2004 | |
2005 | -class UdevDevice(object): |
2006 | - __slots__ = ("_environment", "_attributes", "_stack") |
2007 | - |
2008 | - def __init__(self, environment, attributes, stack=[]): |
2009 | - super(UdevDevice, self).__init__() |
2010 | +PCI_RE = re.compile( |
2011 | + r"^pci:" |
2012 | + r"v(?P<vendor_id>[%(hex)s]{8})" |
2013 | + r"d(?P<product_id>[%(hex)s]{8})" |
2014 | + r"sv(?P<subvendor_id>[%(hex)s]{8})" |
2015 | + r"sd(?P<subproduct_id>[%(hex)s]{8})" |
2016 | + r"bc(?P<class>[%(hex)s]{2})" |
2017 | + r"sc(?P<subclass>[%(hex)s]{2})" |
2018 | + r"i(?P<interface>[%(hex)s]{2})" |
2019 | + % {"hex": string.hexdigits}) |
2020 | +PNP_RE = re.compile( |
2021 | + r"^acpi:" |
2022 | + r"(?P<vendor_name>[%(upper)s]{3})" |
2023 | + r"(?P<product_id>[%(hex)s]{4}):" |
2024 | + % {"upper": string.uppercase, "hex": string.hexdigits}) |
2025 | +USB_RE = re.compile( |
2026 | + r"^usb:" |
2027 | + r"v(?P<vendor_id>[%(hex)s]{4})" |
2028 | + r"p(?P<product_id>[%(hex)s]{4})" |
2029 | + r"d(?P<revision>[%(hex)s]{4})" |
2030 | + r"dc(?P<class>[%(hex)s]{2})" |
2031 | + r"dsc(?P<subclass>[%(hex)s]{2})" |
2032 | + r"dp(?P<protocol>[%(hex)s]{2})" |
2033 | + r"ic(?P<interface_class>[%(hex)s]{2})" |
2034 | + r"isc(?P<interface_subclass>[%(hex)s]{2})" |
2035 | + r"ip(?P<interface_protocol>[%(hex)s]{2})" |
2036 | + % {"hex": string.hexdigits}) |
2037 | +SCSI_RE = re.compile( |
2038 | + r"^scsi:" |
2039 | + r"t-0x(?P<type>[%(hex)s]{2})" |
2040 | + % {"hex": string.hexdigits}) |
2041 | + |
2042 | + |
2043 | +class UdevadmDevice: |
2044 | + __slots__ = ("_environment", "_bits", "_stack",) |
2045 | + |
2046 | + def __init__(self, environment, bits=None, stack=[]): |
2047 | self._environment = environment |
2048 | - self._attributes = attributes |
2049 | + self._bits = bits |
2050 | self._stack = stack |
2051 | |
2052 | @property |
2053 | def bus(self): |
2054 | - return self._environment.get("SUBSYSTEM") |
2055 | + # Change the bus from 'acpi' to 'pnp' for some devices |
2056 | + if PNP_RE.match(self._environment.get("MODALIAS", "")) \ |
2057 | + and self.path.endswith(":00"): |
2058 | + return "pnp" |
2059 | + |
2060 | + # Change the bus from 'block' to parent |
2061 | + if self._environment.get("DEVTYPE") == "disk" and self._stack: |
2062 | + return self._stack[-1]._environment.get("SUBSYSTEM") |
2063 | + |
2064 | + bus = self._environment.get("SUBSYSTEM") |
2065 | + if bus == "pnp": |
2066 | + return None |
2067 | + |
2068 | + return bus |
2069 | |
2070 | @property |
2071 | def category(self): |
2072 | - if "sys_vendor" in self._attributes: |
2073 | - return "SYSTEM" |
2074 | - |
2075 | if "IFINDEX" in self._environment: |
2076 | return "NETWORK" |
2077 | |
2078 | @@ -69,7 +109,10 @@ |
2079 | return "NETWORK" |
2080 | |
2081 | if class_id == Pci.BASE_CLASS_DISPLAY: |
2082 | - return "VIDEO" |
2083 | + if subclass_id == Pci.CLASS_DISPLAY_VGA: |
2084 | + return "VIDEO" |
2085 | + else: |
2086 | + return "OTHER" |
2087 | |
2088 | if class_id == Pci.BASE_CLASS_SERIAL \ |
2089 | and subclass_id == Pci.CLASS_SERIAL_USB: |
2090 | @@ -113,13 +156,9 @@ |
2091 | or subclass_id == Pci.CLASS_BRIDGE_CARDBUS): |
2092 | return "SOCKET" |
2093 | |
2094 | - if "bInterfaceClass" in self._attributes: |
2095 | - interface_class = int( |
2096 | - self._attributes["bInterfaceClass"], 16) |
2097 | - interface_subclass = int( |
2098 | - self._attributes["bInterfaceSubClass"], 16) |
2099 | - interface_protocol = int( |
2100 | - self._attributes["bInterfaceProtocol"], 16) |
2101 | + if "TYPE" in self._environment and "INTERFACE" in self._environment: |
2102 | + interface_class, interface_subclass, interface_protocol = ( |
2103 | + int(i) for i in self._environment["INTERFACE"].split("/")) |
2104 | |
2105 | if interface_class == Usb.BASE_CLASS_AUDIO: |
2106 | return "AUDIO" |
2107 | @@ -143,6 +182,25 @@ |
2108 | else: |
2109 | return "WIRELESS" |
2110 | |
2111 | + if "KEY" in self._environment: |
2112 | + key = self._environment["KEY"].strip("=") |
2113 | + bitmask = get_bitmask(key) |
2114 | + |
2115 | + for i in range(Input.KEY_Q, Input.KEY_P + 1): |
2116 | + if not test_bit(i, bitmask, self._bits): |
2117 | + break |
2118 | + else: |
2119 | + return "KEYBOARD" |
2120 | + |
2121 | + if test_bit(Input.KEY_CAMERA, bitmask, self._bits): |
2122 | + return "CAPTURE" |
2123 | + |
2124 | + if test_bit(Input.BTN_TOUCH, bitmask, self._bits): |
2125 | + return "TOUCH" |
2126 | + |
2127 | + if test_bit(Input.BTN_MOUSE, bitmask, self._bits): |
2128 | + return "MOUSE" |
2129 | + |
2130 | if "ID_TYPE" in self._environment: |
2131 | id_type = self._environment["ID_TYPE"] |
2132 | |
2133 | @@ -155,22 +213,6 @@ |
2134 | if id_type == "video": |
2135 | return "VIDEO" |
2136 | |
2137 | - if "KEY" in self._environment: |
2138 | - key = self._environment["KEY"].strip("=") |
2139 | - bitmask = get_bitmask(key) |
2140 | - |
2141 | - for i in range(Input.KEY_Q, Input.KEY_P + 1): |
2142 | - if not test_bit(i, bitmask): |
2143 | - break |
2144 | - else: |
2145 | - return "KEYBOARD" |
2146 | - |
2147 | - if test_bit(Input.BTN_TOUCH, bitmask): |
2148 | - return "TOUCH" |
2149 | - |
2150 | - if test_bit(Input.BTN_MOUSE, bitmask): |
2151 | - return "MOUSE" |
2152 | - |
2153 | if "DEVTYPE" in self._environment: |
2154 | devtype = self._environment["DEVTYPE"] |
2155 | if devtype == "disk": |
2156 | @@ -181,8 +223,10 @@ |
2157 | return "FLOPPY" |
2158 | |
2159 | if devtype == "scsi_device": |
2160 | - type = int(self._attributes.get("type", "-1")) |
2161 | - # Check for FLASH drives, see /lib/udev/rules.d/80-udisks.rules |
2162 | + match = SCSI_RE.match(self._environment.get("MODALIAS", "")) |
2163 | + type = int(match.group("type"), 16) if match else -1 |
2164 | + |
2165 | + # Check FLASH drives, see /lib/udev/rules.d/80-udisks.rules |
2166 | if type in (0, 7, 14) \ |
2167 | and not any(d.driver == "rts_pstor" for d in self._stack): |
2168 | return "DISK" |
2169 | @@ -216,8 +260,11 @@ |
2170 | if "DRIVER" in self._environment: |
2171 | return self._environment["DRIVER"] |
2172 | |
2173 | - if "ID_USB_DRIVER" in self._environment: |
2174 | - return self._environment["ID_USB_DRIVER"] |
2175 | + # Check parent device for driver |
2176 | + if self._stack: |
2177 | + parent = self._stack[-1] |
2178 | + if "DRIVER" in parent._environment: |
2179 | + return parent._environment["DRIVER"] |
2180 | |
2181 | return None |
2182 | |
2183 | @@ -228,48 +275,36 @@ |
2184 | @property |
2185 | def product_id(self): |
2186 | # pci |
2187 | - if "PCI_ID" in self._environment: |
2188 | - vendor_id, product_id = self._environment["PCI_ID"].split(":") |
2189 | - return int(product_id, 16) |
2190 | - |
2191 | - # usb interface |
2192 | - if "PRODUCT" in self._environment \ |
2193 | - and self._environment.get("DEVTYPE") == "usb_interface": |
2194 | - vendor_id, product_id, revision \ |
2195 | - = self._environment["PRODUCT"].split("/") |
2196 | - return int(product_id, 16) |
2197 | - |
2198 | - # usb device and ieee1394 |
2199 | - for attribute in "idProduct", "model_id": |
2200 | - if attribute in self._attributes: |
2201 | - return int(self._attributes[attribute], 16) |
2202 | + match = PCI_RE.match(self._environment.get("MODALIAS", "")) |
2203 | + if match: |
2204 | + return int(match.group("product_id"), 16) |
2205 | + |
2206 | + # usb |
2207 | + match = USB_RE.match(self._environment.get("MODALIAS", "")) |
2208 | + if match: |
2209 | + return int(match.group("product_id"), 16) |
2210 | |
2211 | # pnp |
2212 | - if "id" in self._attributes: |
2213 | - match = re.match(r"^(?P<vendor_name>.*)(?P<product_id>[%s]{4})$" |
2214 | - % string.hexdigits, self._attributes["id"]) |
2215 | - if match: |
2216 | - return int(match.group("product_id"), 16) |
2217 | + match = PNP_RE.match(self._environment.get("MODALIAS", "")) |
2218 | + if match: |
2219 | + product_id = int(match.group("product_id"), 16) |
2220 | + # Ignore interrupt controllers |
2221 | + if product_id > 0x0100: |
2222 | + return product_id |
2223 | |
2224 | return None |
2225 | |
2226 | @property |
2227 | def vendor_id(self): |
2228 | # pci |
2229 | - if "PCI_ID" in self._environment: |
2230 | - vendor_id, product_id = self._environment["PCI_ID"].split(":") |
2231 | - return int(vendor_id, 16) |
2232 | - |
2233 | - # usb interface |
2234 | - if "PRODUCT" in self._environment \ |
2235 | - and self._environment.get("DEVTYPE") == "usb_interface": |
2236 | - vendor_id, product_id, revision \ |
2237 | - = self._environment["PRODUCT"].split("/") |
2238 | - return int(vendor_id, 16) |
2239 | - |
2240 | - # usb device |
2241 | - if "idVendor" in self._attributes: |
2242 | - return int(self._attributes["idVendor"], 16) |
2243 | + match = PCI_RE.match(self._environment.get("MODALIAS", "")) |
2244 | + if match: |
2245 | + return int(match.group("vendor_id"), 16) |
2246 | + |
2247 | + # usb |
2248 | + match = USB_RE.match(self._environment.get("MODALIAS", "")) |
2249 | + if match: |
2250 | + return int(match.group("vendor_id"), 16) |
2251 | |
2252 | return None |
2253 | |
2254 | @@ -299,16 +334,19 @@ |
2255 | if element in self._environment: |
2256 | return self._environment[element].strip('"') |
2257 | |
2258 | - for attribute in ("description", |
2259 | - "model_name_kv", |
2260 | - "model", |
2261 | - "product_name"): |
2262 | - if attribute in self._attributes: |
2263 | - return self._attributes[attribute] |
2264 | + # disk |
2265 | + if self._environment.get("DEVTYPE") == "scsi_device": |
2266 | + for device in reversed(self._stack): |
2267 | + if device._environment.get("ID_BUS") == "usb": |
2268 | + return decode_id(device._environment["ID_MODEL_ENC"]) |
2269 | + |
2270 | + if self._environment.get("DEVTYPE") == "disk" \ |
2271 | + and self._environment.get("ID_BUS") == "ata": |
2272 | + return decode_id(self._environment["ID_MODEL_ENC"]) |
2273 | |
2274 | # floppy |
2275 | if self.driver == "floppy": |
2276 | - return "Platform Device" |
2277 | + return u"Platform Device" |
2278 | |
2279 | return None |
2280 | |
2281 | @@ -320,168 +358,28 @@ |
2282 | if "POWER_SUPPLY_MANUFACTURER" in self._environment: |
2283 | return self._environment["POWER_SUPPLY_MANUFACTURER"] |
2284 | |
2285 | - if "vendor" in self._attributes: |
2286 | - vendor = self._attributes["vendor"] |
2287 | - if not re.match(r"^0x[%s]{4}$" % string.hexdigits, vendor): |
2288 | - return vendor |
2289 | - |
2290 | - # dmi |
2291 | - if "sys_vendor" in self._attributes: |
2292 | - return self._attributes["sys_vendor"] |
2293 | - |
2294 | # pnp |
2295 | - if "id" in self._attributes: |
2296 | - match = re.match(r"^(?P<vendor_name>.*)(?P<product_id>[%s]{4})$" |
2297 | - % string.hexdigits, self._attributes["id"]) |
2298 | - if match: |
2299 | - return match.group("vendor_name") |
2300 | - |
2301 | - return None |
2302 | - |
2303 | - |
2304 | -class UdevLocalDevice(UdevDevice): |
2305 | - |
2306 | - @property |
2307 | - def bus(self): |
2308 | - sys_path = posixpath.join( |
2309 | - "/sys%s" % self._environment["DEVPATH"], "subsystem") |
2310 | - if posixpath.islink(sys_path): |
2311 | - link = os.readlink(sys_path) |
2312 | - if "/" in link: |
2313 | - return posixpath.basename(link) |
2314 | - |
2315 | - return None |
2316 | - |
2317 | - @property |
2318 | - def vendor_id(self): |
2319 | - vendor_id = super(UdevLocalDevice, self).vendor_id |
2320 | - if vendor_id is not None: |
2321 | - return vendor_id |
2322 | - |
2323 | - # ieee1394 |
2324 | - vendor_id_path = posixpath.join(self.path, "../vendor_id") |
2325 | - if posixpath.exists(vendor_id_path): |
2326 | - vendor_id = open(vendor_id_path, "r").read().strip() |
2327 | - return int(vendor_id, 16) |
2328 | - |
2329 | - return None |
2330 | - |
2331 | - @property |
2332 | - def product(self): |
2333 | - product = super(UdevLocalDevice, self).product |
2334 | - if product is not None: |
2335 | - return product |
2336 | - |
2337 | - # sound |
2338 | - bus = self.bus |
2339 | - if bus == "sound": |
2340 | - device = posixpath.basename(self._environment["DEVPATH"]) |
2341 | - match = re.match( |
2342 | - r"(card|controlC|hwC|midiC)(?P<card>\d+)", device) |
2343 | - if match: |
2344 | - card = match.group("card") |
2345 | - in_card = False |
2346 | - file = open("/proc/asound/cards", "r") |
2347 | - for line in file.readlines(): |
2348 | - line = line.strip() |
2349 | - match = re.match(r"(?P<card>\d+) \[", line) |
2350 | - if match: |
2351 | - in_card = match.group("card") == card |
2352 | - |
2353 | - if in_card: |
2354 | - match = re.match(r"""(?P<name>.*) """ |
2355 | - """at (?P<address>0x[%s]{8}) """ |
2356 | - """irq (?P<irq>\d+)""" % string.hexdigits, line) |
2357 | - if match: |
2358 | - return match.group("name") |
2359 | - |
2360 | - path = None |
2361 | - match = re.match( |
2362 | - r"pcmC(?P<card>\d+)D(?P<device>\d+)(?P<type>\w)", device) |
2363 | - if match: |
2364 | - path = "/proc/asound/card%s/pcm%s%c/info" % match.groups() |
2365 | - |
2366 | - match = re.match( |
2367 | - r"(dsp|adsp|midi|amidi|audio|mixer)(?P<card>\d+)?", device) |
2368 | - if match: |
2369 | - card = match.group("card") or 0 |
2370 | - path = "/proc/asound/card%s/pcm0p/info" % card |
2371 | - |
2372 | - if path and posixpath.exists(path): |
2373 | - file = open(path, "r") |
2374 | - for line in file.readlines(): |
2375 | - match = re.match(r"name: (?P<name>.*)", line) |
2376 | - if match: |
2377 | - return match.group("name") |
2378 | - |
2379 | - return None |
2380 | - |
2381 | - @property |
2382 | - def vendor(self): |
2383 | - vendor = super(UdevLocalDevice, self).vendor |
2384 | - if vendor is not None: |
2385 | - return vendor |
2386 | - |
2387 | - # ieee1394 |
2388 | - vendor_path = posixpath.join(self.path, "../vendor_oui") |
2389 | - if posixpath.exists(vendor_path): |
2390 | - return open(vendor_path, "r").read().strip() |
2391 | - |
2392 | - return None |
2393 | - |
2394 | - |
2395 | -class UdevDmiDevice(UdevDevice): |
2396 | - |
2397 | - def __init__(self, environment, attributes, category): |
2398 | - super(UdevDmiDevice, self).__init__(environment, attributes) |
2399 | - self._category = category |
2400 | - |
2401 | - @property |
2402 | - def category(self): |
2403 | - return self._category |
2404 | - |
2405 | - @property |
2406 | - def path(self): |
2407 | - path = super(UdevDmiDevice, self).path |
2408 | - return posixpath.join(path, self._category.lower()) |
2409 | - |
2410 | - @property |
2411 | - def product(self): |
2412 | - if self._category == "CHASSIS": |
2413 | - type_string = self._attributes.get("chassis_type", "0") |
2414 | - type_index = int(type_string) |
2415 | - return Dmi.chassis_names[type_index] |
2416 | - |
2417 | - for name in "name", "version": |
2418 | - attribute = "%s_%s" % (self._category.lower(), name) |
2419 | - product = self._attributes.get(attribute) |
2420 | - if product and product != "Not Available": |
2421 | - return product |
2422 | - |
2423 | - return None |
2424 | - |
2425 | - @DmiNotAvailable |
2426 | - def _getVendor(self): |
2427 | - attribute = "%s_vendor" % self._category.lower() |
2428 | - if attribute in self._attributes: |
2429 | - return self._attributes[attribute] |
2430 | - |
2431 | - return None |
2432 | - |
2433 | - @property |
2434 | - def vendor(self): |
2435 | - return self._getVendor() |
2436 | - |
2437 | - |
2438 | -class UdevParser(object): |
2439 | - """udevadm parser.""" |
2440 | - |
2441 | - device_factory = UdevDevice |
2442 | - dmi_device_factory = UdevDmiDevice |
2443 | - |
2444 | - def __init__(self, stream): |
2445 | + match = PNP_RE.match(self._environment.get("MODALIAS", "")) |
2446 | + if match: |
2447 | + return match.group("vendor_name") |
2448 | + |
2449 | + # disk |
2450 | + if self._environment.get("DEVTYPE") == "scsi_device": |
2451 | + for device in reversed(self._stack): |
2452 | + if device._environment.get("ID_BUS") == "usb": |
2453 | + return decode_id(device._environment["ID_VENDOR_ENC"]) |
2454 | + |
2455 | + return None |
2456 | + |
2457 | + |
2458 | +class UdevadmParser: |
2459 | + """Parser for the udevadm command.""" |
2460 | + |
2461 | + device_factory = UdevadmDevice |
2462 | + |
2463 | + def __init__(self, stream, bits=None): |
2464 | self.stream = stream |
2465 | - self.stack = [] |
2466 | + self.bits = bits |
2467 | |
2468 | def _ignoreDevice(self, device): |
2469 | # Ignore devices without bus information |
2470 | @@ -499,9 +397,8 @@ |
2471 | and device.subvendor_id is None)): |
2472 | return True |
2473 | |
2474 | - # Ignore virtual devices except for dmi information |
2475 | - if device.bus != "dmi" \ |
2476 | - and "virtual" in device.path.split(posixpath.sep): |
2477 | + # Ignore ACPI devices |
2478 | + if device.bus == "acpi": |
2479 | return True |
2480 | |
2481 | return False |
2482 | @@ -510,96 +407,60 @@ |
2483 | return {} |
2484 | |
2485 | def run(self, result): |
2486 | - line_pattern = re.compile(r"(?P<key>\w):\s*(?P<value>.*)") |
2487 | + # Some attribute lines have a space character after the |
2488 | + # ':', others don't have it (see udevadm-info.c). |
2489 | + line_pattern = re.compile(r"(?P<key>[A-Z]):\s*(?P<value>.*)") |
2490 | multi_pattern = re.compile(r"(?P<key>[^=]+)=(?P<value>.*)") |
2491 | |
2492 | + stack = [] |
2493 | output = self.stream.read() |
2494 | - for record in output.split("\n\n"): |
2495 | + for record in re.split("\n{2,}", output): |
2496 | + record = record.strip() |
2497 | if not record: |
2498 | continue |
2499 | |
2500 | # Determine path and environment |
2501 | path = None |
2502 | + element = None |
2503 | environment = {} |
2504 | for line in record.split("\n"): |
2505 | - if not line: |
2506 | + line_match = line_pattern.match(line) |
2507 | + if not line_match: |
2508 | + if environment: |
2509 | + # Append to last environment element |
2510 | + environment[element] += line |
2511 | continue |
2512 | |
2513 | - match = line_pattern.match(line) |
2514 | - if not match: |
2515 | - raise Exception( |
2516 | - "Device line not supported: %s" % line) |
2517 | - |
2518 | - key = match.group("key") |
2519 | - value = match.group("value") |
2520 | + key = line_match.group("key") |
2521 | + value = line_match.group("value") |
2522 | |
2523 | if key == "P": |
2524 | path = value |
2525 | elif key == "E": |
2526 | - match = multi_pattern.match(value) |
2527 | - if not match: |
2528 | + key_match = multi_pattern.match(value) |
2529 | + if not key_match: |
2530 | raise Exception( |
2531 | "Device property not supported: %s" % value) |
2532 | - environment[match.group("key")] = match.group("value") |
2533 | + element = key_match.group("key") |
2534 | + environment[element] = key_match.group("value") |
2535 | |
2536 | # Update stack |
2537 | - while self.stack: |
2538 | - if self.stack[-1].path + "/" in path: |
2539 | + while stack: |
2540 | + if stack[-1].path + "/" in path: |
2541 | break |
2542 | - self.stack.pop() |
2543 | + stack.pop() |
2544 | |
2545 | # Set default DEVPATH |
2546 | environment.setdefault("DEVPATH", path) |
2547 | |
2548 | - # Determine attributes |
2549 | - attributes = self.getAttributes(path) |
2550 | - |
2551 | - if path == "/devices/virtual/dmi/id": |
2552 | - device = self.device_factory(environment, attributes) |
2553 | - if not self._ignoreDevice(device): |
2554 | - result.addDevice(device) |
2555 | - for category in "BIOS", "BOARD", "CHASSIS": |
2556 | - device = self.dmi_device_factory( |
2557 | - environment, attributes, category) |
2558 | - if not self._ignoreDevice(device): |
2559 | - result.addDevice(device) |
2560 | - else: |
2561 | - device = self.device_factory(environment, attributes, self.stack) |
2562 | - if not self._ignoreDevice(device): |
2563 | - result.addDevice(device) |
2564 | - |
2565 | - self.stack.append(device) |
2566 | - |
2567 | - |
2568 | -class UdevLocalParser(UdevParser): |
2569 | - |
2570 | - device_factory = UdevLocalDevice |
2571 | - |
2572 | - def getAttributes(self, path): |
2573 | - attributes = {} |
2574 | - sys_path = "/sys%s" % path |
2575 | - try: |
2576 | - names = os.listdir(sys_path) |
2577 | - except OSError: |
2578 | - return attributes |
2579 | - |
2580 | - for name in names: |
2581 | - name_path = posixpath.join(sys_path, name) |
2582 | - if name[0] == "." \ |
2583 | - or name in ["dev", "uevent"] \ |
2584 | - or posixpath.isdir(name_path) \ |
2585 | - or posixpath.islink(name_path): |
2586 | - continue |
2587 | - |
2588 | - try: |
2589 | - value = open(name_path, "r").read().strip() |
2590 | - except IOError: |
2591 | - continue |
2592 | - |
2593 | - value = value.split("\n")[0] |
2594 | - if [c for c in value if not isprint(c)]: |
2595 | - continue |
2596 | - |
2597 | - attributes[name] = value |
2598 | - |
2599 | - return attributes |
2600 | + device = self.device_factory(environment, self.bits, list(stack)) |
2601 | + if not self._ignoreDevice(device): |
2602 | + result.addDevice(device) |
2603 | + |
2604 | + stack.append(device) |
2605 | + |
2606 | + |
2607 | +def decode_id(id): |
2608 | + encoded_id = id.encode("utf-8") |
2609 | + decoded_id = encoded_id.decode("string-escape").decode("utf-8") |
2610 | + return decoded_id.strip() |
2611 | |
2612 | === modified file 'jobs/info.txt.in' |
2613 | --- jobs/info.txt.in 2011-09-08 14:52:33 +0000 |
2614 | +++ jobs/info.txt.in 2011-10-12 15:56:08 +0000 |
2615 | @@ -25,6 +25,10 @@ |
2616 | plugin: attachment |
2617 | command: lspci -vvnn |
2618 | |
2619 | +name: info/meminfo_attachment |
2620 | +plugin: attachment |
2621 | +command: cat /proc/meminfo |
2622 | + |
2623 | name: info/modprobe_attachment |
2624 | plugin: attachment |
2625 | command: find /etc/modprobe.* -name \*.conf | xargs cat |
2626 | |
2627 | === modified file 'jobs/resource.txt.in' |
2628 | --- jobs/resource.txt.in 2010-04-08 21:07:43 +0000 |
2629 | +++ jobs/resource.txt.in 2011-10-12 15:56:08 +0000 |
2630 | @@ -35,6 +35,11 @@ |
2631 | plugin: resource |
2632 | command: udev_resource |
2633 | |
2634 | +name: dmi |
2635 | +plugin: resource |
2636 | +user: root |
2637 | +command: dmi_resource |
2638 | + |
2639 | name: uname |
2640 | plugin: resource |
2641 | command: uname_resource |
2642 | |
2643 | === modified file 'scripts/cpuinfo_resource' |
2644 | --- scripts/cpuinfo_resource 2011-05-30 20:42:48 +0000 |
2645 | +++ scripts/cpuinfo_resource 2011-10-12 15:56:08 +0000 |
2646 | @@ -20,10 +20,7 @@ |
2647 | import sys |
2648 | import posixpath |
2649 | |
2650 | -from checkbox.parsers.cpuinfo import ( |
2651 | - CpuinfoParser, |
2652 | - CpuinfoResult, |
2653 | - ) |
2654 | +from checkbox.parsers.cpuinfo import CpuinfoParser |
2655 | |
2656 | # Filename where cpuinfo is stored. |
2657 | CPUINFO_FILENAME = "/proc/cpuinfo" |
2658 | @@ -32,25 +29,24 @@ |
2659 | FREQUENCY_FILENAME = "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq" |
2660 | |
2661 | |
2662 | -class ResourceResult(CpuinfoResult): |
2663 | - |
2664 | - def __setitem__(self, key, value): |
2665 | - print "%s: %s" % (key, value) |
2666 | - |
2667 | - def setSpeed(self, speed): |
2668 | - # Check for frequency scaling |
2669 | - if posixpath.exists(FREQUENCY_FILENAME): |
2670 | - speed = open(FREQUENCY_FILENAME).read().strip() |
2671 | - speed = int(speed) / 1000 |
2672 | - |
2673 | - self["speed"] = speed |
2674 | +class CpuinfoResult: |
2675 | + |
2676 | + def setProcessor(self, processor): |
2677 | + for key, value in processor.iteritems(): |
2678 | + if key == "speed": |
2679 | + # Check for frequency scaling |
2680 | + if posixpath.exists(FREQUENCY_FILENAME): |
2681 | + value = open(FREQUENCY_FILENAME).read().strip() |
2682 | + value = int(value) / 1000 |
2683 | + |
2684 | + print "%s: %s" % (key, value) |
2685 | |
2686 | |
2687 | def main(): |
2688 | stream = open(CPUINFO_FILENAME) |
2689 | parser = CpuinfoParser(stream) |
2690 | |
2691 | - result = ResourceResult() |
2692 | + result = CpuinfoResult() |
2693 | parser.run(result) |
2694 | |
2695 | return 0 |
2696 | |
2697 | === added file 'scripts/dmi_resource' |
2698 | --- scripts/dmi_resource 1970-01-01 00:00:00 +0000 |
2699 | +++ scripts/dmi_resource 2011-10-12 15:56:08 +0000 |
2700 | @@ -0,0 +1,55 @@ |
2701 | +#!/usr/bin/python |
2702 | +# |
2703 | +# This file is part of Checkbox. |
2704 | +# |
2705 | +# Copyright 2011 Canonical Ltd. |
2706 | +# |
2707 | +# Checkbox is free software: you can redistribute it and/or modify |
2708 | +# it under the terms of the GNU General Public License as published by |
2709 | +# the Free Software Foundation, either version 3 of the License, or |
2710 | +# (at your option) any later version. |
2711 | +# |
2712 | +# Checkbox is distributed in the hope that it will be useful, |
2713 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2714 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2715 | +# GNU General Public License for more details. |
2716 | +# |
2717 | +# You should have received a copy of the GNU General Public License |
2718 | +# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
2719 | +# |
2720 | +import os |
2721 | +import sys |
2722 | + |
2723 | +from checkbox.parsers.dmidecode import DmidecodeParser |
2724 | + |
2725 | + |
2726 | +# Command to retrieve dmi information. |
2727 | +COMMAND = "dmidecode" |
2728 | + |
2729 | + |
2730 | +class DmiResult(object): |
2731 | + |
2732 | + attributes = ("path", "category", "product", "vendor", "serial", |
2733 | + "version",) |
2734 | + |
2735 | + def addDmiDevice(self, device): |
2736 | + for attribute in self.attributes: |
2737 | + value = getattr(device, attribute) |
2738 | + if value is not None: |
2739 | + print "%s: %s" % (attribute, value) |
2740 | + |
2741 | |
2742 | + |
2743 | + |
2744 | +def main(): |
2745 | + stream = os.popen(COMMAND) |
2746 | + udev = DmidecodeParser(stream) |
2747 | + |
2748 | + result = DmiResult() |
2749 | + udev.run(result) |
2750 | + |
2751 | + return 0 |
2752 | + |
2753 | + |
2754 | +if __name__ == "__main__": |
2755 | + sys.exit(main()) |
2756 | |
2757 | === modified file 'scripts/hal_resource' |
2758 | --- scripts/hal_resource 2010-04-22 21:12:51 +0000 |
2759 | +++ scripts/hal_resource 2011-10-12 15:56:08 +0000 |
2760 | @@ -23,7 +23,6 @@ |
2761 | import string |
2762 | import posixpath |
2763 | |
2764 | -from checkbox.lib.dmi import DmiNotAvailable |
2765 | from checkbox.lib.pci import Pci |
2766 | from checkbox.lib.usb import Usb |
2767 | |
2768 | @@ -332,10 +331,6 @@ |
2769 | |
2770 | @property |
2771 | def vendor(self): |
2772 | - return self._get_vendor() |
2773 | - |
2774 | - @DmiNotAvailable |
2775 | - def _get_vendor(self): |
2776 | for subproperty in "vendor", "manufacturer": |
2777 | property = "%s.%s" % (self._property, subproperty) |
2778 | if property in self._properties: |
2779 | |
2780 | === modified file 'scripts/meminfo_resource' |
2781 | --- scripts/meminfo_resource 2010-02-25 20:58:11 +0000 |
2782 | +++ scripts/meminfo_resource 2011-10-12 15:56:08 +0000 |
2783 | @@ -17,39 +17,27 @@ |
2784 | # You should have received a copy of the GNU General Public License |
2785 | # along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
2786 | # |
2787 | -import re |
2788 | import sys |
2789 | |
2790 | +from checkbox.parsers.meminfo import MeminfoParser |
2791 | |
2792 | # Filename where meminfo is stored. |
2793 | -FILENAME = "/proc/meminfo" |
2794 | - |
2795 | - |
2796 | -def get_meminfo(): |
2797 | - key_value_pattern = re.compile(r"(?P<key>.*):\s+(?P<value>.*)") |
2798 | - meminfo_map = { |
2799 | - "MemTotal": "total", |
2800 | - "SwapTotal": "swap"} |
2801 | - |
2802 | - meminfo = {} |
2803 | - file = open(FILENAME, "r") |
2804 | - for line in file.readlines(): |
2805 | - line = line.strip() |
2806 | - match = key_value_pattern.match(line) |
2807 | - if match: |
2808 | - key = match.group("key") |
2809 | - if key in meminfo_map: |
2810 | - key = meminfo_map[key] |
2811 | - value = match.group("value") |
2812 | - (integer, factor) = value.split() |
2813 | - meminfo[key] = int(integer) * 1024 |
2814 | - |
2815 | - return meminfo |
2816 | +MEMINFO_FILENAME = "/proc/meminfo" |
2817 | + |
2818 | + |
2819 | +class MeminfoResult: |
2820 | + |
2821 | + def setMemory(self, memory): |
2822 | + for key, value in memory.iteritems(): |
2823 | + print "%s: %s" % (key, value) |
2824 | + |
2825 | |
2826 | def main(): |
2827 | - meminfo = get_meminfo() |
2828 | - for key, value in meminfo.iteritems(): |
2829 | - print "%s: %s" % (key, value) |
2830 | + stream = open(MEMINFO_FILENAME) |
2831 | + parser = MeminfoParser(stream) |
2832 | + |
2833 | + result = MeminfoResult() |
2834 | + parser.run(result) |
2835 | |
2836 | return 0 |
2837 | |
2838 | |
2839 | === modified file 'scripts/udev_resource' |
2840 | --- scripts/udev_resource 2011-05-30 20:42:48 +0000 |
2841 | +++ scripts/udev_resource 2011-10-12 15:56:08 +0000 |
2842 | @@ -20,7 +20,7 @@ |
2843 | import os |
2844 | import sys |
2845 | |
2846 | -from checkbox.parsers.udev import UdevLocalParser |
2847 | +from checkbox.parsers.udevadm import UdevadmParser |
2848 | |
2849 | |
2850 | # Command to retrieve udev information. |
2851 | @@ -43,7 +43,7 @@ |
2852 | |
2853 | def main(): |
2854 | stream = os.popen(COMMAND) |
2855 | - udev = UdevLocalParser(stream) |
2856 | + udev = UdevadmParser(stream) |
2857 | |
2858 | result = UdevResult() |
2859 | udev.run(result) |
2860 | |
2861 | === modified file 'setup.py' |
2862 | --- setup.py 2011-08-26 10:29:35 +0000 |
2863 | +++ setup.py 2011-10-12 15:56:08 +0000 |
2864 | @@ -233,6 +233,8 @@ |
2865 | scripts = ["bin/checkbox-cli", "bin/checkbox-gtk", "bin/checkbox-urwid"], |
2866 | packages = ["checkbox", "checkbox.contrib", "checkbox.lib", "checkbox.parsers", |
2867 | "checkbox.reports", "checkbox_cli", "checkbox_gtk", "checkbox_urwid"], |
2868 | + package_data = { |
2869 | + "": ["cputable"]}, |
2870 | cmdclass = { |
2871 | "build": checkbox_build, |
2872 | "build_i18n": build_i18n, |
Looks OK and works as advertised. Merging, thanks!