Merge lp:~cr3/checkbox/pre-task-525.5 into lp:checkbox/0.9
- pre-task-525.5
- Merge into 0.9
Proposed by
Marc Tardif
Status: | Merged |
---|---|
Merged at revision: | 809 |
Proposed branch: | lp:~cr3/checkbox/pre-task-525.5 |
Merge into: | lp:checkbox/0.9 |
Diff against target: |
6242 lines (+3652/-975) 72 files modified
apport/source_checkbox.py (+3/-0) backend (+5/-0) checkbox/application.py (+9/-6) checkbox/contrib/persist.py (+20/-13) checkbox/dispatcher.py (+201/-0) checkbox/job.py (+12/-3) checkbox/lib/bit.py (+8/-4) checkbox/lib/conversion.py (+131/-42) checkbox/lib/dmi.py (+169/-18) checkbox/lib/enum.py (+72/-0) checkbox/lib/fifo.py (+29/-4) checkbox/lib/log.py (+12/-3) checkbox/lib/process.py (+7/-5) checkbox/lib/safe.py (+5/-0) checkbox/lib/selector.py (+207/-0) checkbox/lib/template.py (+4/-1) checkbox/lib/template_i18n.py (+2/-1) checkbox/lib/transport.py (+85/-2) checkbox/lib/usb.py (+2/-0) checkbox/message.py (+21/-4) checkbox/parsers/cpuinfo.py (+180/-0) checkbox/parsers/cputable (+40/-0) checkbox/parsers/cputable.py (+42/-0) checkbox/parsers/deferred.py (+27/-0) checkbox/parsers/description.py (+74/-0) checkbox/parsers/device.py (+24/-0) checkbox/parsers/dmidecode.py (+123/-0) checkbox/parsers/meminfo.py (+46/-0) checkbox/parsers/submission.py (+540/-0) checkbox/parsers/tests/cputable.py (+74/-0) checkbox/parsers/tests/description.py (+146/-0) checkbox/parsers/tests/dmi.py (+80/-0) checkbox/parsers/tests/dmidecode.py (+60/-0) checkbox/parsers/tests/udevadm.py (+68/-0) checkbox/parsers/udevadm.py (+470/-0) checkbox/parsers/utils.py (+36/-0) checkbox/properties.py (+6/-1) checkbox/reactor.py (+16/-11) checkbox/report.py (+39/-12) checkbox/resource.py (+32/-13) checkbox/tests/report.py (+1/-1) checkbox/variables.py (+15/-1) checkbox_cli/cli_interface.py (+16/-9) debian/control (+1/-1) examples/checkbox.ini (+1/-1) jobs/resource.txt.in (+5/-0) plugins/apport_prompt.py (+20/-5) plugins/backend_info.py (+66/-15) plugins/jobs_info.py (+40/-8) plugins/jobs_prompt.py (+42/-4) plugins/lock_prompt.py (+14/-0) plugins/manual_test.py (+3/-1) plugins/message_info.py (+30/-1) plugins/persist_info.py (+7/-4) plugins/remote_suite.py (+1/-0) plugins/report_prompt.py (+7/-0) plugins/resource_info.py (+16/-2) plugins/scripts_info.py (+3/-1) plugins/shell_test.py (+3/-2) plugins/suites_prompt.py (+35/-20) plugins/system_info.py (+12/-4) plugins/user_interface.py (+6/-0) po/checkbox.pot (+67/-45) scripts/cdimage_resource (+2/-1) scripts/cpuinfo_resource (+18/-158) scripts/dmi_resource (+55/-0) scripts/gconf_resource (+1/-1) scripts/hal_resource (+0/-5) scripts/meminfo_resource (+15/-27) scripts/package_resource (+6/-0) scripts/udev_resource (+15/-514) setup.py (+2/-1) |
To merge this branch: | bzr merge lp:~cr3/checkbox/pre-task-525.5 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Brendan Donegan (community) | Approve | ||
Marc Tardif (community) | Needs Resubmitting | ||
Review via email: mp+93881@code.launchpad.net |
Commit message
Description of the change
This has been tested to build successfully and submit results to Launchpad. No errors encountered in the log when running.
To post a comment you must log in.
lp:~cr3/checkbox/pre-task-525.5
updated
- 810. By Marc Tardif
-
Added python-dateutil to checkbox recommends.
Revision history for this message
Brendan Donegan (brendan-donegan) wrote : | # |
Considering that this has been checked by both of us now and I've had a thorough read of the diff, it's time to take the plunge and merge it.
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'apport/source_checkbox.py' |
2 | --- apport/source_checkbox.py 2010-02-25 19:09:59 +0000 |
3 | +++ apport/source_checkbox.py 2012-02-21 14:59:19 +0000 |
4 | @@ -15,3 +15,6 @@ |
5 | |
6 | SUBMISSION = os.path.join(CACHE_HOME, 'checkbox', 'submission.xml') |
7 | attach_file_if_exists(report, SUBMISSION) |
8 | + |
9 | + CHECKBOX_LOG = os.path.join(CACHE_HOME, 'checkbox', 'checkbox.log') |
10 | + attach_file_if_exists(report, CHECKBOX_LOG) |
11 | |
12 | === modified file 'backend' |
13 | --- backend 2010-04-20 16:23:16 +0000 |
14 | +++ backend 2012-02-21 14:59:19 +0000 |
15 | @@ -28,6 +28,11 @@ |
16 | while True: |
17 | try: |
18 | message = reader.read_object() |
19 | + if message == "stop": |
20 | + break |
21 | + if message == "ping": |
22 | + writer.write_object("pong") |
23 | + continue |
24 | if isinstance(message, dict) and "command" in message: |
25 | job = Job(message["command"], message.get("environ"), |
26 | message.get("timeout")) |
27 | |
28 | === modified file 'checkbox/application.py' |
29 | --- checkbox/application.py 2010-02-25 17:37:40 +0000 |
30 | +++ checkbox/application.py 2012-02-21 14:59:19 +0000 |
31 | @@ -16,6 +16,7 @@ |
32 | # You should have received a copy of the GNU General Public License |
33 | # along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
34 | # |
35 | +import os |
36 | import sys |
37 | import logging |
38 | import posixpath |
39 | @@ -58,7 +59,8 @@ |
40 | |
41 | application_factory = Application |
42 | |
43 | - default_log_level = "critical" |
44 | + default_log = os.path.join(get_variable("CHECKBOX_DATA", "."), "checkbox.log") |
45 | + default_log_level = "debug" |
46 | |
47 | def parse_options(self, args): |
48 | usage = _("Usage: checkbox [OPTIONS]") |
49 | @@ -68,6 +70,7 @@ |
50 | help=_("Print version information and exit.")) |
51 | parser.add_option("-l", "--log", |
52 | metavar="FILE", |
53 | + default=self.default_log, |
54 | help=_("The file to write the log to.")) |
55 | parser.add_option("--log-level", |
56 | default=self.default_log_level, |
57 | @@ -78,13 +81,13 @@ |
58 | default=[], |
59 | help=_("Configuration override parameters.")) |
60 | parser.add_option("-b", "--blacklist", |
61 | - help=_("Shorthand for --config=checkbox/plugins/jobs_info/blacklist.")) |
62 | + help=_("Shorthand for --config=.*/jobs_info/blacklist.")) |
63 | parser.add_option("-B", "--blacklist-file", |
64 | - help=_("Shorthand for --config=checkbox/plugins/jobs_info/blacklist_file.")) |
65 | + help=_("Shorthand for --config=.*/jobs_info/blacklist_file.")) |
66 | parser.add_option("-w", "--whitelist", |
67 | - help=_("Shorthand for --config=checkbox/plugins/jobs_info/whitelist.")) |
68 | + help=_("Shorthand for --config=.*/jobs_info/whitelist.")) |
69 | parser.add_option("-W", "--whitelist-file", |
70 | - help=_("Shorthand for --config=checkbox/plugins/jobs_info/whitelist_file.")) |
71 | + help=_("Shorthand for --config=.*/jobs_info/whitelist_file.")) |
72 | return parser.parse_args(args) |
73 | |
74 | def create_application(self, args=sys.argv): |
75 | @@ -99,7 +102,7 @@ |
76 | |
77 | # Replace shorthands |
78 | for shorthand in "blacklist", "blacklist_file", "whitelist", "whitelist_file": |
79 | - key = "checkbox/plugins/jobs_info/%s" % shorthand |
80 | + key = ".*/jobs_info/%s" % shorthand |
81 | value = getattr(options, shorthand) |
82 | if value: |
83 | options.config.append("=".join([key, value])) |
84 | |
85 | === modified file 'checkbox/contrib/persist.py' |
86 | --- checkbox/contrib/persist.py 2009-02-15 15:40:07 +0000 |
87 | +++ checkbox/contrib/persist.py 2012-02-21 14:59:19 +0000 |
88 | @@ -23,8 +23,9 @@ |
89 | import re |
90 | import posixpath |
91 | |
92 | +from checkbox.lib.safe import safe_close |
93 | |
94 | -__all__ = ["Persist", "PickleBackend", "BPickleBackend", |
95 | +__all__ = ["Persist", "MemoryBackend", "PickleBackend", "BPickleBackend", |
96 | "path_string_to_tuple", "path_tuple_to_string", "RootedPersist", |
97 | "PersistError", "PersistReadOnlyError"] |
98 | |
99 | @@ -128,7 +129,7 @@ |
100 | """ |
101 | if filepath is None: |
102 | if self.filename is None: |
103 | - raise PersistError("Need a filename!") |
104 | + return |
105 | filepath = self.filename |
106 | filepath = posixpath.expanduser(filepath) |
107 | if posixpath.isfile(filepath): |
108 | @@ -410,7 +411,7 @@ |
109 | class Backend(object): |
110 | |
111 | def new(self): |
112 | - raise NotImplementedError |
113 | + return {} |
114 | |
115 | def load(self, filepath): |
116 | raise NotImplementedError |
117 | @@ -494,28 +495,37 @@ |
118 | return NotImplemented |
119 | |
120 | |
121 | +class MemoryBackend(Backend): |
122 | + |
123 | + def __init__(self): |
124 | + self._store = {} |
125 | + |
126 | + def load(self, filepath): |
127 | + return self._store.get(filepath) |
128 | + |
129 | + def save(self, filepath, map): |
130 | + self._store[filepath] = map |
131 | + |
132 | + |
133 | class PickleBackend(Backend): |
134 | |
135 | def __init__(self): |
136 | import cPickle |
137 | self._pickle = cPickle |
138 | |
139 | - def new(self): |
140 | - return {} |
141 | - |
142 | def load(self, filepath): |
143 | file = open(filepath) |
144 | try: |
145 | return self._pickle.load(file) |
146 | finally: |
147 | - file.close() |
148 | + safe_close(file) |
149 | |
150 | def save(self, filepath, map): |
151 | file = open(filepath, "w") |
152 | try: |
153 | self._pickle.dump(map, file, 2) |
154 | finally: |
155 | - file.close() |
156 | + safe_close(file) |
157 | |
158 | |
159 | class BPickleBackend(Backend): |
160 | @@ -524,19 +534,16 @@ |
161 | from checkbox.contrib import bpickle |
162 | self._bpickle = bpickle |
163 | |
164 | - def new(self): |
165 | - return {} |
166 | - |
167 | def load(self, filepath): |
168 | file = open(filepath) |
169 | try: |
170 | return self._bpickle.loads(file.read()) |
171 | finally: |
172 | - file.close() |
173 | + safe_close(file) |
174 | |
175 | def save(self, filepath, map): |
176 | file = open(filepath, "w") |
177 | try: |
178 | file.write(self._bpickle.dumps(map)) |
179 | finally: |
180 | - file.close() |
181 | + safe_close(file) |
182 | |
183 | === added file 'checkbox/dispatcher.py' |
184 | --- checkbox/dispatcher.py 1970-01-01 00:00:00 +0000 |
185 | +++ checkbox/dispatcher.py 2012-02-21 14:59:19 +0000 |
186 | @@ -0,0 +1,201 @@ |
187 | +# Copyright 2010-2011 Canonical Ltd. This software is licensed under the |
188 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
189 | + |
190 | +__metaclass__ = type |
191 | + |
192 | +__all__ = [ |
193 | + "Dispatcher", |
194 | + "DispatcherList", |
195 | + "DispatcherQueue", |
196 | + ] |
197 | + |
198 | +import logging |
199 | + |
200 | +from itertools import product |
201 | + |
202 | + |
203 | +class Event: |
204 | + """Event payload containing the positional and keywoard arguments |
205 | + passed to the handler in the event listener.""" |
206 | + |
207 | + def __init__(self, type, *args, **kwargs): |
208 | + self.type = type |
209 | + self.args = args |
210 | + self.kwargs = kwargs |
211 | + |
212 | + |
213 | +class Listener: |
214 | + """Event listener notified when events are published by the dispatcher.""" |
215 | + |
216 | + def __init__(self, event_type, handler, count): |
217 | + self.event_type = event_type |
218 | + self.handler = handler |
219 | + self.count = count |
220 | + |
221 | + def notify(self, event): |
222 | + """Notify the handler with the payload of the event. |
223 | + |
224 | + :param event: The event containint the payload for the handler. |
225 | + """ |
226 | + if self.count is None or self.count: |
227 | + self.handler(*event.args, **event.kwargs) |
228 | + if self.count: |
229 | + self.count -= 1 |
230 | + |
231 | + |
232 | +class ListenerList(Listener): |
233 | + """Event listener notified for lists of events.""" |
234 | + |
235 | + def __init__(self, *args, **kwargs): |
236 | + super(ListenerList, self).__init__(*args, **kwargs) |
237 | + self.event_types = set(self.event_type) |
238 | + self.kwargs = {} |
239 | + |
240 | + def notify(self, event): |
241 | + """Only notify the handler when all the events for this listener |
242 | + have been published by the dispatcher. When duplicate events |
243 | + occur, the latest event is preserved and the previous one are |
244 | + overwritten until all events have been published. |
245 | + """ |
246 | + if self.count is None or self.count: |
247 | + self.kwargs[event.type] = event.args[0] |
248 | + if self.event_types.issubset(self.kwargs): |
249 | + self.handler(**self.kwargs) |
250 | + if self.count: |
251 | + self.count -= 1 |
252 | + |
253 | + |
254 | +class ListenerQueue(ListenerList): |
255 | + |
256 | + def notify(self, event): |
257 | + """Only notify the handler when all the events for this listener |
258 | + have been published by the dispatcher. Duplicate events are enqueued |
259 | + and dequeued only when all events have been published. |
260 | + """ |
261 | + arg = event.args[0] |
262 | + queue = self.kwargs.setdefault(event.type, []) |
263 | + |
264 | + # Strip duplicates from the queue. |
265 | + if arg not in queue: |
266 | + queue.append(arg) |
267 | + |
268 | + # Once the queue has handler has been called, the queue |
269 | + # then behaves like a list using the latest events. |
270 | + if self.event_types.issubset(self.kwargs): |
271 | + self.notify = notify = super(ListenerQueue, self).notify |
272 | + keys = self.kwargs.keys() |
273 | + for values in product(*self.kwargs.values()): |
274 | + self.kwargs = dict(zip(keys, values)) |
275 | + notify(event) |
276 | + |
277 | + |
278 | +class Dispatcher: |
279 | + """Register handlers and publish events for them identified by strings.""" |
280 | + |
281 | + listener_factory = Listener |
282 | + |
283 | + def __init__(self, listener_factory=None): |
284 | + self._event_listeners = {} |
285 | + |
286 | + if listener_factory is not None: |
287 | + self.listener_factory = listener_factory |
288 | + |
289 | + def registerHandler(self, event_type, handler, count=None): |
290 | + """Register an event handler and return its listener. |
291 | + |
292 | + :param event_type: The name of the event type to handle. |
293 | + :param handler: The function handling the given event type. |
294 | + :param count: Optionally, the number times to call the handler. |
295 | + """ |
296 | + listener = self.listener_factory(event_type, handler, count) |
297 | + |
298 | + listeners = self._event_listeners.setdefault(event_type, []) |
299 | + listeners.append(listener) |
300 | + |
301 | + return listener |
302 | + |
303 | + def unregisterHandler(self, handler): |
304 | + """Unregister a handler. |
305 | + |
306 | + :param handler: The handler to unregister. |
307 | + """ |
308 | + for event_type, listeners in self._event_listeners.items(): |
309 | + listeners = [ |
310 | + listener for listener in listeners |
311 | + if listener.handler == handler] |
312 | + if listeners: |
313 | + self._event_listeners[event_type] = listeners |
314 | + else: |
315 | + del self._event_listeners[event_type] |
316 | + |
317 | + def unregisterListener(self, listener, event_type=None): |
318 | + """Unregister a listener. |
319 | + |
320 | + :param listener: The listener of the handler to unregister. |
321 | + :param event_type: Optionally, the event_type to unregister. |
322 | + """ |
323 | + if event_type is None: |
324 | + event_type = listener.event_type |
325 | + |
326 | + self._event_listeners[event_type].remove(listener) |
327 | + if not self._event_listeners[event_type]: |
328 | + del self._event_listeners[event_type] |
329 | + |
330 | + def publishEvent(self, event_type, *args, **kwargs): |
331 | + """Publish an event of a given type and notify all listeners. |
332 | + |
333 | + :param event_type: The name of the event type to publish. |
334 | + :param args: Positional arguments to pass to the registered handlers. |
335 | + :param kwargs: Keyword arguments to pass to the registered handlers. |
336 | + """ |
337 | + if event_type in self._event_listeners: |
338 | + event = Event(event_type, *args, **kwargs) |
339 | + for listener in list(self._event_listeners[event_type]): |
340 | + try: |
341 | + listener.notify(event) |
342 | + if listener.count is not None and not listener.count: |
343 | + self.unregisterListener(listener) |
344 | + except: |
345 | + logging.exception( |
346 | + "Error running event handler for %r with args %r %r", |
347 | + event_type, args, kwargs) |
348 | + |
349 | + |
350 | +class DispatcherList(Dispatcher): |
351 | + """ |
352 | + Register handlers and publish events for them identified by lists |
353 | + of strings. |
354 | + """ |
355 | + |
356 | + listener_factory = ListenerList |
357 | + |
358 | + def registerHandler(self, event_types, handler, count=None): |
359 | + """See Dispatcher.""" |
360 | + if not isinstance(event_types, (list, tuple,)): |
361 | + event_types = (event_types,) |
362 | + |
363 | + listener = self.listener_factory(event_types, handler, count) |
364 | + for event_type in event_types: |
365 | + listeners = self._event_listeners.setdefault(event_type, []) |
366 | + listeners.append(listener) |
367 | + |
368 | + return listener |
369 | + |
370 | + def unregisterListener(self, listener): |
371 | + """See Dispatcher.""" |
372 | + for event_type in listener.event_types: |
373 | + super(DispatcherList, self).unregisterListener( |
374 | + listener, event_type) |
375 | + |
376 | + def publishEvent(self, event_type, arg): |
377 | + """See Dispatcher.""" |
378 | + super(DispatcherList, self).publishEvent(event_type, arg) |
379 | + |
380 | + |
381 | +class DispatcherQueue(DispatcherList): |
382 | + """ |
383 | + Register handlers and publish events for them identified by lists |
384 | + of strings in queue order. |
385 | + """ |
386 | + |
387 | + listener_factory = ListenerQueue |
388 | |
389 | === modified file 'checkbox/job.py' |
390 | --- checkbox/job.py 2010-04-20 16:23:16 +0000 |
391 | +++ checkbox/job.py 2012-02-21 14:59:19 +0000 |
392 | @@ -37,6 +37,7 @@ |
393 | |
394 | ALL_STATUS = [FAIL, PASS, UNINITIATED, UNRESOLVED, UNSUPPORTED, UNTESTED] |
395 | |
396 | +DEFAULT_JOB_TIMEOUT = 30 # used in case a job specifies invalid timeout |
397 | |
398 | class Job(object): |
399 | |
400 | @@ -47,6 +48,14 @@ |
401 | self.command = command |
402 | self.environ = environ |
403 | self.timeout = timeout |
404 | + if self.timeout is not None: |
405 | + try: |
406 | + self.timeout = float(self.timeout) |
407 | + except: |
408 | + self.timeout = DEFAULT_JOB_TIMEOUT |
409 | + finally: |
410 | + if self.timeout < 0: |
411 | + self.timeout = DEFAULT_JOB_TIMEOUT |
412 | |
413 | def execute(self): |
414 | # Sanitize environment |
415 | @@ -112,8 +121,8 @@ |
416 | # TODO: Apply dependencies |
417 | if "depends" in job: |
418 | for depends in job["depends"]: |
419 | - for filename in self._find_matching_messages(suite=job.get("suite")): |
420 | - message = self._read_message(filename) |
421 | + for filename in self._find_matching_messages(): |
422 | + message = self._read_message(filename, cache=True) |
423 | if job["name"] in message.get("depends", []): |
424 | new_filename = self._get_next_message_filename() |
425 | os.rename(filename, new_filename) |
426 | @@ -123,7 +132,7 @@ |
427 | # TODO: Optimize by only searching backwards until a given condition |
428 | def _find_matching_messages(self, **kwargs): |
429 | for filename in self._walk_messages(): |
430 | - message = self._read_message(filename) |
431 | + message = self._read_message(filename,cache=True) |
432 | for key, value in kwargs.iteritems(): |
433 | if message.get(key) != value: |
434 | break |
435 | |
436 | === modified file 'checkbox/lib/bit.py' |
437 | --- checkbox/lib/bit.py 2009-08-29 18:43:36 +0000 |
438 | +++ checkbox/lib/bit.py 2012-02-21 14:59:19 +0000 |
439 | @@ -16,6 +16,9 @@ |
440 | # You should have received a copy of the GNU General Public License |
441 | # along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
442 | # |
443 | +from struct import calcsize |
444 | + |
445 | + |
446 | def get_bitmask(key): |
447 | bitmask = [] |
448 | for value in reversed(key.split()): |
449 | @@ -33,10 +36,11 @@ |
450 | |
451 | return bitcount |
452 | |
453 | -def test_bit(bit, bitmask): |
454 | - bits_per_long = 8 * 8 |
455 | - offset = bit % bits_per_long |
456 | - long = int(bit / bits_per_long) |
457 | +def test_bit(bit, bitmask, bits=None): |
458 | + if bits is None: |
459 | + bits = calcsize("l") * 8 |
460 | + offset = bit % bits |
461 | + long = int(bit / bits) |
462 | if long >= len(bitmask): |
463 | return 0 |
464 | return (bitmask[long] >> offset) & 1 |
465 | |
466 | === modified file 'checkbox/lib/conversion.py' |
467 | --- checkbox/lib/conversion.py 2009-02-16 15:48:24 +0000 |
468 | +++ checkbox/lib/conversion.py 2012-02-21 14:59:19 +0000 |
469 | @@ -18,48 +18,110 @@ |
470 | # |
471 | import re |
472 | |
473 | - |
474 | -def string_to_type(value): |
475 | - conversion_table = ( |
476 | - ("(yes|true)", lambda v: True), |
477 | - ("(no|false)", lambda v: False), |
478 | - ("\d+", lambda v: int(v.group(0))), |
479 | - ("\d+\.\d+", lambda v: float(v.group(0))), |
480 | - ("(\d+) ?([kmgt]?b?)", lambda v: int(v.group(1))), |
481 | - ("(\d+\.\d+) ?([kmgt]?b?)", lambda v: float(v.group(1))), |
482 | - ("(\d+) ?([kmgt]?hz?)", lambda v: int(v.group(1))), |
483 | - ("(\d+\.\d+) ?([kmgt]?hz?)", lambda v: float(v.group(1)))) |
484 | - |
485 | - multiplier_table = ( |
486 | - ("b", 1), |
487 | - ("kb?", 1024), |
488 | - ("mb?", 1024 * 1024), |
489 | - ("gb?", 1024 * 1024 * 1024), |
490 | - ("tb?", 1024 * 1024 * 1024 * 1024), |
491 | - ("hz", 1), |
492 | - ("khz?", 1024), |
493 | - ("mhz?", 1024 * 1024), |
494 | - ("ghz?", 1024 * 1024 * 1024), |
495 | - ("thz?", 1024 * 1024 * 1024 * 1024)) |
496 | - |
497 | - if isinstance(value, basestring): |
498 | - for regex, conversion in conversion_table: |
499 | - match = re.match("^%s$" % regex, value, re.IGNORECASE) |
500 | - if match: |
501 | - value = conversion(match) |
502 | - if len(match.groups()) < 2: |
503 | - return value |
504 | - |
505 | - unit = match.group(2) |
506 | - for regex, multiplier in multiplier_table: |
507 | - match = re.match("^%s$" % regex, unit, re.IGNORECASE) |
508 | - if match: |
509 | - value *= multiplier |
510 | - return value |
511 | - else: |
512 | - raise Exception, "Unknown multiplier: %s" % unit |
513 | - |
514 | - return value |
515 | +from dateutil import tz |
516 | +from datetime import ( |
517 | + datetime, |
518 | + timedelta, |
519 | + ) |
520 | + |
521 | + |
522 | +DATETIME_RE = re.compile(r""" |
523 | + ^(?P<year>\d\d\d\d)-?(?P<month>\d\d)-?(?P<day>\d\d) |
524 | + T(?P<hour>\d\d):?(?P<minute>\d\d):?(?P<second>\d\d) |
525 | + (?:\.(?P<second_fraction>\d{0,6}))? |
526 | + (?P<tz> |
527 | + (?:(?P<tz_sign>[-+])(?P<tz_hour>\d\d):(?P<tz_minute>\d\d)) |
528 | + | Z)?$ |
529 | + """, re.VERBOSE) |
530 | + |
531 | +TYPE_FORMATS = ( |
532 | + (r"(yes|true)", lambda v: True), |
533 | + (r"(no|false)", lambda v: False), |
534 | + (r"-?\d+", lambda v: int(v.group(0))), |
535 | + (r"-?\d+\.\d+", lambda v: float(v.group(0))), |
536 | + (r"(-?\d+) ?([kmgt]?b?)", lambda v: int(v.group(1))), |
537 | + (r"(-?\d+\.\d+) ?([kmgt]?b?)", lambda v: float(v.group(1))), |
538 | + (r"(-?\d+) ?([kmgt]?hz)", lambda v: int(v.group(1))), |
539 | + (r"(-?\d+\.\d+) ?([kmgt]?hz)", lambda v: float(v.group(1)))) |
540 | +TYPE_FORMATS = tuple( |
541 | + (re.compile(r"^%s$" % pattern, re.IGNORECASE), format) |
542 | + for pattern, format in TYPE_FORMATS) |
543 | + |
544 | +TYPE_MULTIPLIERS = ( |
545 | + (r"b", 1), |
546 | + (r"kb?", 1024), |
547 | + (r"mb?", 1024 * 1024), |
548 | + (r"gb?", 1024 * 1024 * 1024), |
549 | + (r"tb?", 1024 * 1024 * 1024 * 1024), |
550 | + (r"hz", 1), |
551 | + (r"khz?", 1024), |
552 | + (r"mhz?", 1024 * 1024), |
553 | + (r"ghz?", 1024 * 1024 * 1024), |
554 | + (r"thz?", 1024 * 1024 * 1024 * 1024)) |
555 | +TYPE_MULTIPLIERS = tuple( |
556 | + (re.compile(r"^%s$" % pattern, re.IGNORECASE), multiplier) |
557 | + for pattern, multiplier in TYPE_MULTIPLIERS) |
558 | + |
559 | + |
560 | +def datetime_to_string(dt): |
561 | + """Return a consistent string representation for a given datetime. |
562 | + |
563 | + :param dt: The datetime object. |
564 | + """ |
565 | + return dt.isoformat() |
566 | + |
567 | +def string_to_datetime(string): |
568 | + """Return a datetime object from a consistent string representation. |
569 | + |
570 | + :param string: The string representation. |
571 | + """ |
572 | + # we cannot use time.strptime: this function accepts neither fractions |
573 | + # of a second nor a time zone given e.g. as '+02:30'. |
574 | + match = DATETIME_RE.match(string) |
575 | + |
576 | + # The Relax NG schema allows a leading minus sign and year numbers |
577 | + # with more than four digits, which are not "covered" by _time_regex. |
578 | + if not match: |
579 | + raise ValueError("Datetime with unreasonable value: %s" % string) |
580 | + |
581 | + time_parts = match.groupdict() |
582 | + |
583 | + year = int(time_parts['year']) |
584 | + month = int(time_parts['month']) |
585 | + day = int(time_parts['day']) |
586 | + hour = int(time_parts['hour']) |
587 | + minute = int(time_parts['minute']) |
588 | + second = int(time_parts['second']) |
589 | + second_fraction = time_parts['second_fraction'] |
590 | + if second_fraction is not None: |
591 | + milliseconds = second_fraction + '0' * (6 - len(second_fraction)) |
592 | + milliseconds = int(milliseconds) |
593 | + else: |
594 | + milliseconds = 0 |
595 | + |
596 | + # The Relax NG validator accepts leap seconds, but the datetime |
597 | + # constructor rejects them. The time values submitted by the HWDB |
598 | + # client are not necessarily very precise, hence we can round down |
599 | + # to 59.999999 seconds without losing any real precision. |
600 | + if second > 59: |
601 | + second = 59 |
602 | + milliseconds = 999999 |
603 | + |
604 | + dt = datetime( |
605 | + year, month, day, hour, minute, second, milliseconds, |
606 | + tzinfo=tz.tzutc()) |
607 | + |
608 | + tz_sign = time_parts['tz_sign'] |
609 | + tz_hour = time_parts['tz_hour'] |
610 | + tz_minute = time_parts['tz_minute'] |
611 | + if tz_sign in ('-', '+'): |
612 | + delta = timedelta(hours=int(tz_hour), minutes=int(tz_minute)) |
613 | + if tz_sign == '-': |
614 | + dt = dt + delta |
615 | + else: |
616 | + dt = dt - delta |
617 | + |
618 | + return dt |
619 | |
620 | def sizeof_bytes(bytes): |
621 | for x in ["bytes", "KB", "MB", "GB", "TB"]: |
622 | @@ -78,3 +140,30 @@ |
623 | hertz /= 1000.0 |
624 | |
625 | return string |
626 | + |
627 | +def string_to_type(string): |
628 | + """Return a typed representation for the given string. |
629 | + |
630 | + The result might be a bool, int or float. The string might also be |
631 | + supplemented by a multiplier like KB which would return an int or |
632 | + float multiplied by 1024 for example. |
633 | + |
634 | + :param string: The string representation. |
635 | + """ |
636 | + if isinstance(string, basestring): |
637 | + for regex, formatter in TYPE_FORMATS: |
638 | + match = regex.match(string) |
639 | + if match: |
640 | + string = formatter(match) |
641 | + if len(match.groups()) > 1: |
642 | + unit = match.group(2) |
643 | + for regex, multiplier in TYPE_MULTIPLIERS: |
644 | + match = regex.match(unit) |
645 | + if match: |
646 | + string *= multiplier |
647 | + break |
648 | + else: |
649 | + raise ValueError("Unknown multiplier: %s" % unit) |
650 | + break |
651 | + |
652 | + return string |
653 | |
654 | === modified file 'checkbox/lib/dmi.py' |
655 | --- checkbox/lib/dmi.py 2009-09-06 14:19:16 +0000 |
656 | +++ checkbox/lib/dmi.py 2012-02-21 14:59:19 +0000 |
657 | @@ -16,13 +16,15 @@ |
658 | # You should have received a copy of the GNU General Public License |
659 | # along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
660 | # |
661 | +import os |
662 | + |
663 | |
664 | # See also 3.3.4.1 of the "System Management BIOS Reference Specification, |
665 | # Version 2.6.1" (Preliminary Standard) document, available from |
666 | # http://www.dmtf.org/standards/smbios. |
667 | class Dmi: |
668 | chassis = ( |
669 | - ("Undefined", "unknown"), # 0x00 |
670 | + ("Undefined", "unknown"), # 0x00 |
671 | ("Other", "unknown"), |
672 | ("Unknown", "unknown"), |
673 | ("Desktop", "desktop"), |
674 | @@ -53,22 +55,171 @@ |
675 | ("Blade", "server"), |
676 | ("Blade Enclosure", "unknown")) |
677 | |
678 | - chassis_names = [c[0] for c in chassis] |
679 | - chassis_types = [c[1] for c in chassis] |
680 | + chassis_names = tuple(c[0] for c in chassis) |
681 | + chassis_types = tuple(c[1] for c in chassis) |
682 | chassis_name_to_type = dict(chassis) |
683 | |
684 | - |
685 | -class DmiNotAvailable(object): |
686 | - def __init__(self, function): |
687 | - self._function = function |
688 | - |
689 | - def __get__(self, instance, cls=None): |
690 | - self._instance = instance |
691 | - return self |
692 | - |
693 | - def __call__(self, *args, **kwargs): |
694 | - name = self._function(self._instance, *args, **kwargs) |
695 | - if name == "Not Available": |
696 | - name = None |
697 | - |
698 | - return name |
699 | + type_names = ( |
700 | + "BIOS", # 0x00 |
701 | + "System", |
702 | + "Base Board", |
703 | + "Chassis", |
704 | + "Processor", |
705 | + "Memory Controller", |
706 | + "Memory Module", |
707 | + "Cache", |
708 | + "Port Connector", |
709 | + "System Slots", |
710 | + "On Board Devices", |
711 | + "OEM Strings", |
712 | + "System Configuration Options", |
713 | + "BIOS Language", |
714 | + "Group Associations", |
715 | + "System Event Log", |
716 | + "Physical Memory Array", |
717 | + "Memory Device", |
718 | + "32-bit Memory Error", |
719 | + "Memory Array Mapped Address", |
720 | + "Memory Device Mapped Address", |
721 | + "Built-in Pointing Device", |
722 | + "Portable Battery", |
723 | + "System Reset", |
724 | + "Hardware Security", |
725 | + "System Power Controls", |
726 | + "Voltage Probe", |
727 | + "Cooling Device", |
728 | + "Temperature Probe", |
729 | + "Electrical Current Probe", |
730 | + "Out-of-band Remote Access", |
731 | + "Boot Integrity Services", |
732 | + "System Boot", |
733 | + "64-bit Memory Error", |
734 | + "Management Device", |
735 | + "Management Device Component", |
736 | + "Management Device Threshold Data", |
737 | + "Memory Channel", |
738 | + "IPMI Device", |
739 | + "Power Supply", |
740 | + ) |
741 | + |
742 | + |
743 | +class DmiDevice: |
744 | + |
745 | + bus = "dmi" |
746 | + driver = None |
747 | + product_id = None |
748 | + vendor_id = None |
749 | + |
750 | + _product_blacklist = ( |
751 | + "<BAD INDEX>", |
752 | + "N/A", |
753 | + "Not Available", |
754 | + "INVALID", |
755 | + "OEM", |
756 | + "Product Name", |
757 | + "System Product Name", |
758 | + "To be filled by O.E.M.", |
759 | + "To Be Filled By O.E.M.", |
760 | + "To Be Filled By O.E.M. by More String", |
761 | + "Unknown", |
762 | + "Uknown", |
763 | + "Unknow", |
764 | + "xxxxxxxxxxxxxx", |
765 | + ) |
766 | + _vendor_blacklist = ( |
767 | + "<BAD INDEX>", |
768 | + "Not Available", |
769 | + "OEM", |
770 | + "OEM Manufacturer", |
771 | + "System manufacturer", |
772 | + "System Manufacturer", |
773 | + "System Name", |
774 | + "To be filled by O.E.M.", |
775 | + "To Be Filled By O.E.M.", |
776 | + "To Be Filled By O.E.M. by More String", |
777 | + "Unknow", # XXX This is correct mispelling |
778 | + "Unknown", |
779 | + ) |
780 | + _serial_blacklist = ( |
781 | + "0", |
782 | + "00000000", |
783 | + "00 00 00 00 00 00 00 00", |
784 | + "0123456789", |
785 | + "Base Board Serial Number", |
786 | + "Chassis Serial Number", |
787 | + "N/A", |
788 | + "None", |
789 | + "Not Applicable", |
790 | + "Not Available", |
791 | + "Not Specified", |
792 | + "OEM", |
793 | + "System Serial Number", |
794 | + ) |
795 | + _version_blacklist = ( |
796 | + "-1", |
797 | + "<BAD INDEX>", |
798 | + "N/A", |
799 | + "None", |
800 | + "Not Applicable", |
801 | + "Not Available", |
802 | + "Not Specified", |
803 | + "OEM", |
804 | + "System Version", |
805 | + "Unknown", |
806 | + "x.x", |
807 | + ) |
808 | + |
809 | + def __init__(self, attributes, category): |
810 | + self._attributes = attributes |
811 | + self.category = category |
812 | + |
813 | + @property |
814 | + def path(self): |
815 | + path = "/devices/virtual/dmi/id" |
816 | + return os.path.join(path, self.category.lower()) |
817 | + |
818 | + @property |
819 | + def product(self): |
820 | + if self.category == "CHASSIS": |
821 | + type_string = self._attributes.get("chassis_type", "0") |
822 | + try: |
823 | + type_index = int(type_string) |
824 | + return Dmi.chassis_names[type_index] |
825 | + except ValueError: |
826 | + return type_string |
827 | + |
828 | + for name in "name", "version": |
829 | + attribute = "%s_%s" % (self.category.lower(), name) |
830 | + product = self._attributes.get(attribute) |
831 | + if product and product not in self._product_blacklist: |
832 | + return product |
833 | + |
834 | + return None |
835 | + |
836 | + @property |
837 | + def vendor(self): |
838 | + for name in "manufacturer", "vendor": |
839 | + attribute = "%s_%s" % (self.category.lower(), name) |
840 | + vendor = self._attributes.get(attribute) |
841 | + if vendor and vendor not in self._vendor_blacklist: |
842 | + return vendor |
843 | + |
844 | + return None |
845 | + |
846 | + @property |
847 | + def serial(self): |
848 | + attribute = "%s_serial" % self.category.lower() |
849 | + serial = self._attributes.get(attribute) |
850 | + if serial and serial not in self._serial_blacklist: |
851 | + return serial |
852 | + |
853 | + return None |
854 | + |
855 | + @property |
856 | + def version(self): |
857 | + attribute = "%s_version" % self.category.lower() |
858 | + version = self._attributes.get(attribute) |
859 | + if version and version not in self._version_blacklist: |
860 | + return version |
861 | + |
862 | + return None |
863 | |
864 | === added file 'checkbox/lib/enum.py' |
865 | --- checkbox/lib/enum.py 1970-01-01 00:00:00 +0000 |
866 | +++ checkbox/lib/enum.py 2012-02-21 14:59:19 +0000 |
867 | @@ -0,0 +1,72 @@ |
868 | +# |
869 | +# This file is part of Checkbox. |
870 | +# |
871 | +# Copyright 2011 Canonical Ltd. |
872 | +# |
873 | +# Checkbox is free software: you can redistribute it and/or modify |
874 | +# it under the terms of the GNU General Public License as published by |
875 | +# the Free Software Foundation, either version 3 of the License, or |
876 | +# (at your option) any later version. |
877 | +# |
878 | +# Checkbox is distributed in the hope that it will be useful, |
879 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
880 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
881 | +# GNU General Public License for more details. |
882 | +# |
883 | +# You should have received a copy of the GNU General Public License |
884 | +# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
885 | +# |
886 | +__all__ = [ |
887 | + "EnumException", "Enum", "enum_name_to_value", "enum_value_to_name"] |
888 | + |
889 | + |
890 | +class EnumException(Exception): |
891 | + |
892 | + pass |
893 | + |
894 | + |
895 | +class Enum(object): |
896 | + |
897 | + def __init__(self, *names): |
898 | + value = 0 |
899 | + unique_names = set() |
900 | + for name in names: |
901 | + if isinstance(name, (list, tuple)): |
902 | + name, value = name |
903 | + |
904 | + if not isinstance(name, str): |
905 | + raise EnumException( |
906 | + "enum name is not a string: %s" % name) |
907 | + |
908 | + if isinstance(value, str): |
909 | + if value not in unique_names: |
910 | + raise EnumException( |
911 | + "enum value does not define: %s" % value) |
912 | + value = getattr(self, value) |
913 | + |
914 | + if not isinstance(value, (int, long)): |
915 | + raise EnumException( |
916 | + "enum value is not an integer: %s" % value) |
917 | + |
918 | + if name in unique_names: |
919 | + raise EnumException( |
920 | + "enum name is not unique: %s" % name) |
921 | + |
922 | + unique_names.add(name) |
923 | + setattr(self, name, value) |
924 | + value += 1 |
925 | + |
926 | + |
927 | +def enum_name_to_value(enum, name): |
928 | + if not hasattr(enum, name): |
929 | + raise EnumException("enum name unknown: %s" % name) |
930 | + |
931 | + return getattr(enum, name) |
932 | + |
933 | + |
934 | +def enum_value_to_name(enum, value): |
935 | + for k, v in enum.__dict__.items(): |
936 | + if v == value: |
937 | + return k |
938 | + |
939 | + raise EnumException("enum value unknown: %s" % value) |
940 | |
941 | === modified file 'checkbox/lib/fifo.py' |
942 | --- checkbox/lib/fifo.py 2010-03-28 03:00:14 +0000 |
943 | +++ checkbox/lib/fifo.py 2012-02-21 14:59:19 +0000 |
944 | @@ -20,15 +20,16 @@ |
945 | import struct |
946 | |
947 | from checkbox.contrib.bpickle import dumps, loads |
948 | - |
949 | +from checkbox.lib.selector import Selector, SelectorIO |
950 | |
951 | class FifoBase(object): |
952 | |
953 | mode = None |
954 | |
955 | - def __init__(self, path): |
956 | + def __init__(self, path, timeout=None): |
957 | self.path = path |
958 | self.file = open(path, self.mode) |
959 | + self._timeout = timeout |
960 | |
961 | def __del__(self): |
962 | self.close() |
963 | @@ -36,12 +37,29 @@ |
964 | def close(self): |
965 | self.file.close() |
966 | |
967 | + def wait_for(self, operation): |
968 | + if self._timeout is not None: |
969 | + selector = Selector() |
970 | + selector.set_timeout(self._timeout) |
971 | + selector.add_fd(self.file.fileno(), operation) |
972 | + |
973 | + selector.execute() |
974 | + |
975 | + if not selector.has_ready(): |
976 | + return False |
977 | + return True |
978 | |
979 | class FifoReader(FifoBase): |
980 | |
981 | - mode = "r" |
982 | + #on Linux, opening a FIFO in read-write mode is non-blocking and |
983 | + #succeeds even if other end is not open as per FIFO(7) |
984 | + mode = "w+" |
985 | |
986 | def read_string(self): |
987 | + # Check if a connection arrived within the timeout |
988 | + if not self.wait_for(SelectorIO.READ): |
989 | + return None |
990 | + |
991 | size = struct.calcsize("i") |
992 | length_string = self.file.read(size) |
993 | if not length_string: |
994 | @@ -60,9 +78,16 @@ |
995 | |
996 | class FifoWriter(FifoBase): |
997 | |
998 | - mode = "w" |
999 | + #on Linux, opening a FIFO in read-write mode is non-blocking and |
1000 | + #succeeds even if other end is not open as per FIFO(7) |
1001 | + mode = "w+" |
1002 | |
1003 | def write_string(self, string): |
1004 | + |
1005 | + # Wait until I can write |
1006 | + if not self.wait_for(SelectorIO.WRITE): |
1007 | + return None |
1008 | + |
1009 | length = len(string) |
1010 | length_string = struct.pack(">i", length) |
1011 | self.file.write(length_string) |
1012 | |
1013 | === modified file 'checkbox/lib/log.py' |
1014 | --- checkbox/lib/log.py 2009-12-06 18:07:40 +0000 |
1015 | +++ checkbox/lib/log.py 2012-02-21 14:59:19 +0000 |
1016 | @@ -25,21 +25,30 @@ |
1017 | def format_class(cls): |
1018 | return "%s.%s" % (cls.__module__, cls.__name__) |
1019 | |
1020 | -def format_object(object): |
1021 | +def format_object(object, *args, **kwargs): |
1022 | """ |
1023 | Returns a fully-qualified name for the specified object, such as |
1024 | 'checkbox.log.format_object()'. |
1025 | """ |
1026 | + args_string = "" |
1027 | + if args: |
1028 | + args_string += ", ".join(str(a) for a in args) |
1029 | + if kwargs: |
1030 | + args_string += ", " |
1031 | + if kwargs: |
1032 | + args_string += ", ".join(["%s=%s" % (k, v) for k, v in kwargs.iteritems()]) |
1033 | + |
1034 | + module = object.__module__ if object.__module__ else inspect.getfile(object) |
1035 | if inspect.ismethod(object): |
1036 | # FIXME If the method is implemented on a base class of |
1037 | # object's class, the module name and function name will be |
1038 | # from the base class and the method's class name will be from |
1039 | # object's class. |
1040 | name = repr(object).split(" ")[2] |
1041 | - return "%s.%s()" % (object.__module__, name) |
1042 | + return "%s %s(%s)" % (module, name, args_string) |
1043 | elif inspect.isfunction(object): |
1044 | name = repr(object).split(" ")[1] |
1045 | - return "%s.%s()" % (object.__module__, name) |
1046 | + return "%s %s(%s)" % (module, name, args_string) |
1047 | return format_class(object.__class__) |
1048 | |
1049 | def format_delta(seconds): |
1050 | |
1051 | === modified file 'checkbox/lib/process.py' |
1052 | --- checkbox/lib/process.py 2009-12-07 16:55:14 +0000 |
1053 | +++ checkbox/lib/process.py 2012-02-21 14:59:19 +0000 |
1054 | @@ -16,16 +16,21 @@ |
1055 | # You should have received a copy of the GNU General Public License |
1056 | # along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
1057 | # |
1058 | +from __future__ import absolute_import |
1059 | + |
1060 | import os |
1061 | import time |
1062 | import fcntl |
1063 | import select |
1064 | +import signal |
1065 | |
1066 | |
1067 | STDIN_FILENO = 0 |
1068 | STDOUT_FILENO = 1 |
1069 | STDERR_FILENO = 2 |
1070 | |
1071 | +DEFAULT_OPEN_MAX = 1024 |
1072 | + |
1073 | |
1074 | class Process: |
1075 | """Class representing a child process which is non blocking. This makes |
1076 | @@ -56,14 +61,11 @@ |
1077 | self._outeof = self._erreof = False |
1078 | |
1079 | def _child(self, cmd, env): |
1080 | - # Note sh below doesn't setup a seperate group (job control) |
1081 | - # for non interactive shells (hmm maybe -m option does?) |
1082 | - os.setpgrp() #seperate group so we can kill it |
1083 | # Force FD_CLOEXEC on all inherited descriptors |
1084 | try: |
1085 | open_max = os.sysconf('SC_OPEN_MAX') |
1086 | except (AttributeError, ValueError): |
1087 | - open_max = 1024 |
1088 | + open_max = DEFAULT_OPEN_MAX |
1089 | for fileno in range(STDERR_FILENO+1, open_max): |
1090 | try: |
1091 | flags = fcntl.fcntl(fileno, fcntl.F_GETFD) |
1092 | @@ -120,7 +122,7 @@ |
1093 | return has_finished |
1094 | |
1095 | def kill(self): |
1096 | - os.kill(-self.pid, 15) # SIGTERM whole group |
1097 | + os.kill(self.pid, signal.SIGTERM) |
1098 | |
1099 | def cleanup(self): |
1100 | """Wait for and return the exit status of the child process.""" |
1101 | |
1102 | === modified file 'checkbox/lib/safe.py' |
1103 | --- checkbox/lib/safe.py 2009-03-09 14:43:55 +0000 |
1104 | +++ checkbox/lib/safe.py 2012-02-21 14:59:19 +0000 |
1105 | @@ -94,3 +94,8 @@ |
1106 | md5sum = digest.hexdigest() |
1107 | |
1108 | return md5sum |
1109 | + |
1110 | +def safe_close(file): |
1111 | + file.flush() |
1112 | + os.fsync(file.fileno()) |
1113 | + file.close() |
1114 | |
1115 | === added file 'checkbox/lib/selector.py' |
1116 | --- checkbox/lib/selector.py 1970-01-01 00:00:00 +0000 |
1117 | +++ checkbox/lib/selector.py 2012-02-21 14:59:19 +0000 |
1118 | @@ -0,0 +1,207 @@ |
1119 | +# |
1120 | +# This file is part of Checkbox. |
1121 | +# |
1122 | +# Copyright 2011 Canonical Ltd. |
1123 | +# |
1124 | +# Checkbox is free software: you can redistribute it and/or modify |
1125 | +# it under the terms of the GNU General Public License as published by |
1126 | +# the Free Software Foundation, either version 3 of the License, or |
1127 | +# (at your option) any later version. |
1128 | +# |
1129 | +# Checkbox is distributed in the hope that it will be useful, |
1130 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1131 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1132 | +# GNU General Public License for more details. |
1133 | +# |
1134 | +# You should have received a copy of the GNU General Public License |
1135 | +# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
1136 | +# |
1137 | +import os |
1138 | +import errno |
1139 | +import select |
1140 | + |
1141 | +from checkbox.lib.enum import Enum |
1142 | + |
1143 | + |
1144 | +__all__ = ["SelectorIO", "SelectorState", "Selector"] |
1145 | + |
1146 | + |
1147 | +SelectorIO = Enum( |
1148 | + "READ", |
1149 | + "WRITE", |
1150 | + "EXCEPT") |
1151 | + |
1152 | +SelectorState = Enum( |
1153 | + "VIRGIN", |
1154 | + "READY", |
1155 | + "TIMED_OUT", |
1156 | + "SIGNALED", |
1157 | + "FAILED") |
1158 | + |
1159 | + |
1160 | +class Selector(object): |
1161 | + __slots__ = ("_read_fds", "_write_fds", "_except_fds", |
1162 | + "_save_read_fds", "_save_write_fds", "_save_except_fds", |
1163 | + "_fd_set_size", "_timeout", "_state", "_errno") |
1164 | + |
1165 | + _fd_select_size = -1 |
1166 | + _cached_read_fds = None |
1167 | + _cached_save_read_fds = None |
1168 | + _cached_write_fds = None |
1169 | + _cached_save_write_fds = None |
1170 | + _cached_except_fds = None |
1171 | + _cached_save_except_fds = None |
1172 | + |
1173 | + def __init__(self): |
1174 | + if Selector._cached_read_fds: |
1175 | + self._read_fds = Selector._cached_read_fds |
1176 | + self._write_fds = Selector._cached_write_fds |
1177 | + self._except_fds = Selector._cached_except_fds |
1178 | + |
1179 | + self._save_read_fds = Selector._cached_save_read_fds |
1180 | + self._save_write_fds = Selector._cached_save_write_fds |
1181 | + self._save_except_fds = Selector._cached_save_except_fds |
1182 | + |
1183 | + Selector._cached_read_fds = None |
1184 | + Selector._cached_write_fds = None |
1185 | + Selector._cached_except_fds = None |
1186 | + |
1187 | + Selector._cached_save_read_fds = None |
1188 | + Selector._cached_save_write_fds = None |
1189 | + Selector._cached_save_except_fds = None |
1190 | + |
1191 | + else: |
1192 | + self._read_fds = [] |
1193 | + self._write_fds = [] |
1194 | + self._except_fds = [] |
1195 | + |
1196 | + self._save_read_fds = [] |
1197 | + self._save_write_fds = [] |
1198 | + self._save_except_fds = [] |
1199 | + |
1200 | + self.reset() |
1201 | + |
1202 | + def __del__(self): |
1203 | + if Selector._cached_read_fds is None: |
1204 | + Selector._cached_read_fds = self._read_fds |
1205 | + Selector._cached_write_fds = self._write_fds |
1206 | + Selector._cached_except_fds = self._except_fds |
1207 | + |
1208 | + Selector._cached_save_read_fds = self._save_read_fds |
1209 | + Selector._cached_save_write_fds = self._save_write_fds |
1210 | + Selector._cached_save_except_fds = self._save_except_fds |
1211 | + |
1212 | + def reset(self): |
1213 | + self._errno = 0 |
1214 | + self._state = SelectorState.VIRGIN |
1215 | + self._timeout = None |
1216 | + self._save_read_fds = [] |
1217 | + self._save_write_fds = [] |
1218 | + self._save_except_fds = [] |
1219 | + |
1220 | + @staticmethod |
1221 | + def get_fd_select_size(): |
1222 | + if Selector._fd_select_size == -1: |
1223 | + try: |
1224 | + Selector._fd_select_size = os.sysconf("SC_OPEN_MAX") |
1225 | + except (AttributeError, ValueError): |
1226 | + Selector._fd_select_size = 1024 |
1227 | + |
1228 | + return Selector._fd_select_size |
1229 | + |
1230 | + def get_errno(self): |
1231 | + return self._errno |
1232 | + |
1233 | + def get_errstr(self): |
1234 | + return os.strerror(self._errno) |
1235 | + |
1236 | + def set_timeout(self, timeout): |
1237 | + self._timeout = timeout |
1238 | + |
1239 | + def unset_timeout(self): |
1240 | + self._timeout = None |
1241 | + |
1242 | + def has_timed_out(self): |
1243 | + return self._state == SelectorState.TIMED_OUT |
1244 | + |
1245 | + def has_signaled(self): |
1246 | + return self._state == SelectorState.SIGNALED |
1247 | + |
1248 | + def has_failed(self): |
1249 | + return self._state == SelectorState.FAILED |
1250 | + |
1251 | + def has_ready(self): |
1252 | + return self._state == SelectorState.READY |
1253 | + |
1254 | + def add_fd(self, fd, interest): |
1255 | + if fd < 0 or fd >= Selector.get_fd_select_size(): |
1256 | + raise Exception("File descriptor %d outside of range 0-%d" |
1257 | + % (fd, Selector._fd_select_size)) |
1258 | + |
1259 | + if interest == SelectorIO.READ: |
1260 | + self._save_read_fds.append(fd) |
1261 | + |
1262 | + elif interest == SelectorIO.WRITE: |
1263 | + self._save_write_fds.append(fd) |
1264 | + |
1265 | + elif interest == SelectorIO.EXCEPT: |
1266 | + self._save_except_fds.append(fd) |
1267 | + |
1268 | + def remove_fd(self, fd, interest): |
1269 | + if fd < 0 or fd >= Selector.get_fd_select_size(): |
1270 | + raise Exception("File descriptor %d outside of range 0-%d" |
1271 | + % (fd, Selector._fd_select_size)) |
1272 | + |
1273 | + if interest == SelectorIO.READ: |
1274 | + self._save_read_fds.remove(fd) |
1275 | + |
1276 | + elif interest == SelectorIO.WRITE: |
1277 | + self._save_write_fds.remove(fd) |
1278 | + |
1279 | + elif interest == SelectorIO.EXCEPT: |
1280 | + self._save_except_fds.remove(fd) |
1281 | + |
1282 | + def execute(self): |
1283 | + try: |
1284 | + self._read_fds, self._write_fds, self._except_fds = select.select( |
1285 | + self._save_read_fds, self._save_write_fds, |
1286 | + self._save_except_fds, self._timeout) |
1287 | + except select.error, e: |
1288 | + self._errno = e.errno |
1289 | + if e.errno == errno.EINTR: |
1290 | + self._state = SelectorState.SIGNALED |
1291 | + |
1292 | + else: |
1293 | + self._state = SelectorState.FAILED |
1294 | + |
1295 | + return |
1296 | + |
1297 | + # Just in case |
1298 | + self._errno = 0 |
1299 | + if not self._read_fds \ |
1300 | + and not self._write_fds \ |
1301 | + and not self._except_fds: |
1302 | + self._state = SelectorState.TIMED_OUT |
1303 | + |
1304 | + else: |
1305 | + self._state = SelectorState.READY |
1306 | + |
1307 | + def is_fd_ready(self, fd, interest): |
1308 | + if self._state != SelectorState.READY \ |
1309 | + and self._state != SelectorState.TIMED_OUT: |
1310 | + raise Exception( |
1311 | + "Selector requested descriptor not in ready state") |
1312 | + |
1313 | + if fd < 0 or fd >= Selector.get_fd_select_size(): |
1314 | + return False |
1315 | + |
1316 | + if interest == SelectorIO.READ: |
1317 | + return fd in self._read_fds |
1318 | + |
1319 | + elif interest == SelectorIO.WRITE: |
1320 | + return fd in self._write_fds |
1321 | + |
1322 | + elif interest == SelectorIO.EXCEPT: |
1323 | + return fd in self._except_fds |
1324 | + |
1325 | + return False |
1326 | |
1327 | === modified file 'checkbox/lib/template.py' |
1328 | --- checkbox/lib/template.py 2009-09-11 16:41:43 +0000 |
1329 | +++ checkbox/lib/template.py 2012-02-21 14:59:19 +0000 |
1330 | @@ -92,7 +92,10 @@ |
1331 | field = value = extended = "" |
1332 | for line in string.split("\n"): |
1333 | line.strip() |
1334 | - match = re.search(r"^([-_.A-Za-z0-9]*):\s?(.*)", line) |
1335 | + if line.startswith("#"): |
1336 | + continue |
1337 | + |
1338 | + match = re.search(r"^([-_.A-Za-z0-9@]*):\s?(.*)", line) |
1339 | if match: |
1340 | _save(field, value, extended) |
1341 | field = match.groups()[0].lower() |
1342 | |
1343 | === modified file 'checkbox/lib/template_i18n.py' |
1344 | --- checkbox/lib/template_i18n.py 2010-02-16 21:09:28 +0000 |
1345 | +++ checkbox/lib/template_i18n.py 2012-02-21 14:59:19 +0000 |
1346 | @@ -77,7 +77,8 @@ |
1347 | languages = [] |
1348 | if environ.has_key("LANGUAGE") and environ["LANGUAGE"]: |
1349 | for language in environ["LANGUAGE"].split(":"): |
1350 | - languages.extend(self._get_language_list(language)) |
1351 | + if language: |
1352 | + languages.extend(self._get_language_list(language)) |
1353 | |
1354 | language = locale.setlocale(locale.LC_MESSAGES) |
1355 | languages.extend(self._get_language_list(language)) |
1356 | |
1357 | === modified file 'checkbox/lib/transport.py' |
1358 | --- checkbox/lib/transport.py 2009-03-09 14:30:47 +0000 |
1359 | +++ checkbox/lib/transport.py 2012-02-21 14:59:19 +0000 |
1360 | @@ -19,6 +19,7 @@ |
1361 | import logging |
1362 | |
1363 | import os |
1364 | +import re |
1365 | import stat |
1366 | import sys |
1367 | import posixpath |
1368 | @@ -42,6 +43,29 @@ |
1369 | ssl_sock = socket.ssl(sock, key_file, cert_file) |
1370 | return httplib.FakeSocket(sock, ssl_sock) |
1371 | |
1372 | +try: |
1373 | + # Python 2.6 introduced create_connection convenience function |
1374 | + create_connection = socket.create_connection |
1375 | +except AttributeError: |
1376 | + def create_connection(address, timeout=None): |
1377 | + msg = "getaddrinfo returns an empty list" |
1378 | + host, port = address |
1379 | + for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM): |
1380 | + af, socktype, proto, canonname, sa = res |
1381 | + sock = None |
1382 | + try: |
1383 | + sock = socket.socket(af, socktype, proto) |
1384 | + if timeout is not None: |
1385 | + sock.settimeout(timeout) |
1386 | + sock.connect(sa) |
1387 | + return sock |
1388 | + |
1389 | + except socket.error, msg: |
1390 | + if sock is not None: |
1391 | + sock.close() |
1392 | + |
1393 | + raise socket.error, msg |
1394 | + |
1395 | |
1396 | class ProxyHTTPConnection(httplib.HTTPConnection): |
1397 | |
1398 | @@ -63,6 +87,9 @@ |
1399 | port = self._ports[scheme] |
1400 | except KeyError: |
1401 | raise ValueError, "unknown protocol for: %s" % url |
1402 | + else: |
1403 | + port = int(port) |
1404 | + |
1405 | self._real_host = host |
1406 | self._real_port = port |
1407 | httplib.HTTPConnection.request(self, method, url, body, headers) |
1408 | @@ -101,6 +128,59 @@ |
1409 | self.sock = _ssl_wrap_socket(self.sock, self.key_file, self.cert_file) |
1410 | |
1411 | |
1412 | +class VerifiedHTTPSConnection(httplib.HTTPSConnection): |
1413 | + |
1414 | + # Compatibility layer with Python 2.5 |
1415 | + timeout = None |
1416 | + _tunnel_host = None |
1417 | + |
1418 | + def match_name(self, name): |
1419 | + parts = [] |
1420 | + for fragment in name.split(r"."): |
1421 | + if fragment == "*": |
1422 | + parts.append(".+") |
1423 | + else: |
1424 | + fragment = re.escape(fragment) |
1425 | + parts.append(fragment.replace(r"\*", ".*")) |
1426 | + return re.match(r"\A" + r"\.".join(parts) + r"\Z", self.host, re.IGNORECASE) |
1427 | + |
1428 | + def verify_cert(self, cert): |
1429 | + # verify that the hostname matches that of the certificate |
1430 | + if cert: |
1431 | + san = cert.get("subjectAltName", ()) |
1432 | + for key, value in san: |
1433 | + if key == "DNS" and self.match_name(value): |
1434 | + return True |
1435 | + |
1436 | + if not san: |
1437 | + for subject in cert.get("subject", ()): |
1438 | + for key, value in subject: |
1439 | + if key == "commonName" and self.match_name(value): |
1440 | + return True |
1441 | + |
1442 | + return False |
1443 | + |
1444 | + def connect(self): |
1445 | + # overrides the version in httplib so that we do |
1446 | + # certificate verification |
1447 | + sock = create_connection((self.host, self.port), self.timeout) |
1448 | + if self._tunnel_host: |
1449 | + self.sock = sock |
1450 | + self._tunnel() |
1451 | + |
1452 | + # wrap the socket using verification with the root |
1453 | + # certs in trusted_root_certs |
1454 | + self.sock = _ssl_wrap_socket(sock, |
1455 | + self.key_file, |
1456 | + self.cert_file, |
1457 | + cert_reqs=ssl.CERT_REQUIRED, |
1458 | + ca_certs="/etc/ssl/certs/ca-certificates.crt") |
1459 | + |
1460 | + if not self.verify_cert(self.sock.getpeercert()): |
1461 | + raise ValueError( |
1462 | + "Failed to verify cert for hostname: %s" % self.host) |
1463 | + |
1464 | + |
1465 | class HTTPTransport(object): |
1466 | """Transport makes a request to exchange message data over HTTP.""" |
1467 | |
1468 | @@ -115,6 +195,9 @@ |
1469 | scheme, rest = urllib.splittype(string) |
1470 | host, rest = urllib.splithost(rest) |
1471 | host, port = urllib.splitport(host) |
1472 | + if port is not None: |
1473 | + port = int(port) |
1474 | + |
1475 | return (host, port) |
1476 | |
1477 | def _get_connection(self, timeout=0): |
1478 | @@ -135,7 +218,7 @@ |
1479 | connection = ProxyHTTPSConnection(host, port) |
1480 | else: |
1481 | host, port = self._unpack_host_and_port(self.url) |
1482 | - connection = httplib.HTTPSConnection(host, port) |
1483 | + connection = VerifiedHTTPSConnection(host, port) |
1484 | else: |
1485 | raise Exception, "Unknown URL scheme: %s" % scheme |
1486 | |
1487 | @@ -247,7 +330,7 @@ |
1488 | else: |
1489 | if response.status == httplib.FOUND: |
1490 | # TODO prevent infinite redirect loop |
1491 | - self.url = self._get_location_header(response) |
1492 | + self.url = response.getheader('location') |
1493 | response = self.exchange(body, headers, timeout) |
1494 | |
1495 | return response |
1496 | |
1497 | === modified file 'checkbox/lib/usb.py' |
1498 | --- checkbox/lib/usb.py 2009-09-14 18:41:46 +0000 |
1499 | +++ checkbox/lib/usb.py 2012-02-21 14:59:19 +0000 |
1500 | @@ -55,3 +55,5 @@ |
1501 | BASE_CLASS_WIRELESS = 224 |
1502 | CLASS_WIRELESS_RADIO_FREQUENCY = 1 |
1503 | CLASS_WIRELESS_USB_ADAPTER = 2 |
1504 | + |
1505 | + PROTOCOL_BLUETOOTH = 1 |
1506 | |
1507 | === modified file 'checkbox/message.py' |
1508 | --- checkbox/message.py 2010-02-16 15:18:39 +0000 |
1509 | +++ checkbox/message.py 2012-02-21 14:59:19 +0000 |
1510 | @@ -20,9 +20,10 @@ |
1511 | import logging |
1512 | import itertools |
1513 | import posixpath |
1514 | +import sys |
1515 | |
1516 | from checkbox.contrib import bpickle |
1517 | - |
1518 | +from checkbox.lib.safe import safe_close |
1519 | |
1520 | HELD = "h" |
1521 | BROKEN = "b" |
1522 | @@ -40,6 +41,9 @@ |
1523 | class MessageStore(object): |
1524 | """A message store which stores its messages in a file system hierarchy.""" |
1525 | |
1526 | + #This caches everything but a message's data, making it manageable to keep in memory. |
1527 | + _message_cache = {} |
1528 | + |
1529 | def __init__(self, persist, directory, directory_size=1000): |
1530 | self._directory = directory |
1531 | self._directory_size = directory_size |
1532 | @@ -215,7 +219,7 @@ |
1533 | try: |
1534 | return file.read() |
1535 | finally: |
1536 | - file.close() |
1537 | + safe_close(file) |
1538 | |
1539 | def _get_flags(self, path): |
1540 | basename = posixpath.basename(path) |
1541 | @@ -240,7 +244,11 @@ |
1542 | def _dump_message(self, message): |
1543 | return bpickle.dumps(message) |
1544 | |
1545 | - def _read_message(self, filename): |
1546 | + def _read_message(self, filename, cache=False): |
1547 | + #cache basically indicates whether the caller cares about having "data" |
1548 | + if cache and filename in self._message_cache: |
1549 | + return Message(self._message_cache[filename],filename) |
1550 | + |
1551 | data = self._get_content(filename) |
1552 | message = self._load_message(data) |
1553 | return Message(message, filename) |
1554 | @@ -253,9 +261,18 @@ |
1555 | |
1556 | file = open(filename + ".tmp", "w") |
1557 | file.write(message_data) |
1558 | - file.close() |
1559 | + safe_close(file) |
1560 | + |
1561 | os.rename(filename + ".tmp", filename) |
1562 | |
1563 | + #Strip the big data element and shove it in the cache |
1564 | + |
1565 | + temp_message=dict(message) |
1566 | + if "data" in temp_message: |
1567 | + temp_message["data"] = None |
1568 | + |
1569 | + self._message_cache[filename] = temp_message |
1570 | + |
1571 | # For now we use the inode as the message id, as it will work |
1572 | # correctly even faced with holding/unholding. It will break |
1573 | # if the store is copied over for some reason, but this shouldn't |
1574 | |
1575 | === added directory 'checkbox/parsers' |
1576 | === added file 'checkbox/parsers/__init__.py' |
1577 | === added file 'checkbox/parsers/cpuinfo.py' |
1578 | --- checkbox/parsers/cpuinfo.py 1970-01-01 00:00:00 +0000 |
1579 | +++ checkbox/parsers/cpuinfo.py 2012-02-21 14:59:19 +0000 |
1580 | @@ -0,0 +1,180 @@ |
1581 | +# |
1582 | +# This file is part of Checkbox. |
1583 | +# |
1584 | +# Copyright 2011 Canonical Ltd. |
1585 | +# |
1586 | +# Checkbox is free software: you can redistribute it and/or modify |
1587 | +# it under the terms of the GNU General Public License as published by |
1588 | +# the Free Software Foundation, either version 3 of the License, or |
1589 | +# (at your option) any later version. |
1590 | +# |
1591 | +# Checkbox is distributed in the hope that it will be useful, |
1592 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1593 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1594 | +# GNU General Public License for more details. |
1595 | +# |
1596 | +# You should have received a copy of the GNU General Public License |
1597 | +# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
1598 | +# |
1599 | +import re |
1600 | + |
1601 | +from os import uname |
1602 | + |
1603 | +from checkbox.lib.conversion import string_to_type |
1604 | + |
1605 | + |
1606 | +class CpuinfoParser: |
1607 | + """Parser for the /proc/cpuinfo file.""" |
1608 | + |
1609 | + def __init__(self, stream, machine=None): |
1610 | + self.stream = stream |
1611 | + self.machine = machine or uname()[4].lower() |
1612 | + |
1613 | + def getAttributes(self): |
1614 | + count = 0 |
1615 | + attributes = {} |
1616 | + cpuinfo = self.stream.read() |
1617 | + for block in re.split(r"\n{2,}", cpuinfo): |
1618 | + block = block.strip() |
1619 | + if not block: |
1620 | + continue |
1621 | + |
1622 | + count += 1 |
1623 | + if count > 1: |
1624 | + continue |
1625 | + |
1626 | + for line in block.split("\n"): |
1627 | + if not line: |
1628 | + continue |
1629 | + |
1630 | + key, value = line.split(":") |
1631 | + key, value = key.strip(), value.strip() |
1632 | + |
1633 | + # Handle bogomips on sparc |
1634 | + if key.endswith("Bogo"): |
1635 | + key = "bogomips" |
1636 | + |
1637 | + attributes[key.lower()] = value |
1638 | + |
1639 | + if attributes: |
1640 | + attributes["count"] = count |
1641 | + |
1642 | + return attributes |
1643 | + |
1644 | + def run(self, result): |
1645 | + attributes = self.getAttributes() |
1646 | + if not attributes: |
1647 | + return |
1648 | + |
1649 | + # Default values |
1650 | + machine = self.machine |
1651 | + processor = { |
1652 | + "platform": machine, |
1653 | + "count": 1, |
1654 | + "type": machine, |
1655 | + "model": machine, |
1656 | + "model_number": "", |
1657 | + "model_version": "", |
1658 | + "model_revision": "", |
1659 | + "cache": 0, |
1660 | + "bogomips": 0, |
1661 | + "speed": -1, |
1662 | + "other": ""} |
1663 | + |
1664 | + # Conversion table |
1665 | + platform_to_conversion = { |
1666 | + ("i386", "i486", "i586", "i686", "x86_64",): { |
1667 | + "type": "vendor_id", |
1668 | + "model": "model name", |
1669 | + "model_number": "cpu family", |
1670 | + "model_version": "model", |
1671 | + "model_revision": "stepping", |
1672 | + "cache": "cache size", |
1673 | + "other": "flags", |
1674 | + "speed": "cpu mhz"}, |
1675 | + ("alpha", "alphaev6",): { |
1676 | + "count": "cpus detected", |
1677 | + "type": "cpu", |
1678 | + "model": "cpu model", |
1679 | + "model_number": "cpu variation", |
1680 | + "model_version": ("system type", "system variation",), |
1681 | + "model_revision": "cpu revision", |
1682 | + "other": "platform string", |
1683 | + "speed": "cycle frequency [Hz]"}, |
1684 | + ("armv7l",): { |
1685 | + "type": "hardware", |
1686 | + "model": "processor", |
1687 | + "model_number": "cpu variant", |
1688 | + "model_version": "cpu architecture", |
1689 | + "model_revision": "cpu revision", |
1690 | + "other": "features"}, |
1691 | + ("ia64",): { |
1692 | + "type": "vendor", |
1693 | + "model": "family", |
1694 | + "model_version": "archrev", |
1695 | + "model_revision": "revision", |
1696 | + "other": "features", |
1697 | + "speed": "cpu mhz"}, |
1698 | + ("ppc64", "ppc",): { |
1699 | + "type": "platform", |
1700 | + "model": "cpu", |
1701 | + "model_version": "revision", |
1702 | + "speed": "clock"}, |
1703 | + ("sparc64", "sparc",): { |
1704 | + "count": "ncpus probed", |
1705 | + "type": "type", |
1706 | + "model": "cpu", |
1707 | + "model_version": "type", |
1708 | + "speed": "bogomips"}} |
1709 | + |
1710 | + processor["count"] = attributes.get("count", 1) |
1711 | + bogompips_string = attributes.get("bogomips", "0.0") |
1712 | + processor["bogomips"] = int(round(float(bogompips_string))) |
1713 | + for platform, conversion in platform_to_conversion.iteritems(): |
1714 | + if machine in platform: |
1715 | + for pkey, ckey in conversion.iteritems(): |
1716 | + if isinstance(ckey, (list, tuple)): |
1717 | + processor[pkey] = "/".join([attributes[k] |
1718 | + for k in ckey]) |
1719 | + elif ckey in attributes: |
1720 | + processor[pkey] = attributes[ckey] |
1721 | + |
1722 | + # Adjust platform |
1723 | + if machine[0] == "i" and machine[-2:] == "86": |
1724 | + processor["platform"] = u"i386" |
1725 | + elif machine[:5] == "alpha": |
1726 | + processor["platform"] = u"alpha" |
1727 | + |
1728 | + # Adjust cache |
1729 | + if processor["cache"]: |
1730 | + processor["cache"] = string_to_type(processor["cache"]) |
1731 | + |
1732 | + # Adjust speed |
1733 | + try: |
1734 | + if machine[:5] == "alpha": |
1735 | + speed = processor["speed"].split()[0] |
1736 | + processor["speed"] = int(round(float(speed))) / 1000000 |
1737 | + elif machine[:5] == "sparc": |
1738 | + speed = processor["speed"] |
1739 | + processor["speed"] = int(round(float(speed))) / 2 |
1740 | + else: |
1741 | + if machine[:3] == "ppc": |
1742 | + # String is appended with "mhz" |
1743 | + speed = processor["speed"][:-3] |
1744 | + else: |
1745 | + speed = processor["speed"] |
1746 | + processor["speed"] = int(round(float(speed)) - 1) |
1747 | + except ValueError: |
1748 | + processor["speed"] = -1 |
1749 | + |
1750 | + # Adjust count |
1751 | + try: |
1752 | + processor["count"] = int(processor["count"]) |
1753 | + except ValueError: |
1754 | + processor["count"] = 1 |
1755 | + else: |
1756 | + # There is at least one processor |
1757 | + if processor["count"] == 0: |
1758 | + processor["count"] = 1 |
1759 | + |
1760 | + result.setProcessor(processor) |
1761 | |
1762 | === added file 'checkbox/parsers/cputable' |
1763 | --- checkbox/parsers/cputable 1970-01-01 00:00:00 +0000 |
1764 | +++ checkbox/parsers/cputable 2012-02-21 14:59:19 +0000 |
1765 | @@ -0,0 +1,40 @@ |
1766 | +# This file contains the table of known CPU names. |
1767 | +# |
1768 | +# Architecture names are formed as a combination of the system name |
1769 | +# (from ostable) and CPU name (from this table) after mapping from |
1770 | +# the Debian triplet (from triplettable). A list of architecture |
1771 | +# names in the Debian ‘sid’ distribution can be found in the archtable |
1772 | +# file. |
1773 | +# |
1774 | +# Column 1 is the Debian name for the CPU, used to form the cpu part in |
1775 | +# the Debian triplet. |
1776 | +# Column 2 is the GNU name for the CPU, used to output build and host |
1777 | +# targets in ‘dpkg-architecture’. |
1778 | +# Column 3 is an extended regular expression used to match against the |
1779 | +# CPU part of the output of the GNU config.guess script. |
1780 | +# Column 4 is the size (in bits) of the integers/pointers |
1781 | +# Column 5 is the endianness (byte ordering in numbers) |
1782 | +# |
1783 | +# <Debian name> <GNU name> <config.guess regex> <Bits> <Endianness> |
1784 | +i386 i686 (i[3456]86|pentium) 32 little |
1785 | +ia64 ia64 ia64 64 little |
1786 | +alpha alpha alpha.* 64 little |
1787 | +amd64 x86_64 x86_64 64 little |
1788 | +armeb armeb arm.*b 32 big |
1789 | +arm arm arm.* 32 little |
1790 | +avr32 avr32 avr32 32 big |
1791 | +hppa hppa hppa.* 32 big |
1792 | +m32r m32r m32r 32 big |
1793 | +m68k m68k m68k 32 big |
1794 | +mips mips mips(eb)? 32 big |
1795 | +mipsel mipsel mipsel 32 little |
1796 | +powerpc powerpc (powerpc|ppc) 32 big |
1797 | +ppc64 powerpc64 (powerpc|ppc)64 64 big |
1798 | +s390 s390 s390 32 big |
1799 | +s390x s390x s390x 64 big |
1800 | +sh3 sh3 sh3 32 little |
1801 | +sh3eb sh3eb sh3eb 32 big |
1802 | +sh4 sh4 sh4 32 little |
1803 | +sh4eb sh4eb sh4eb 32 big |
1804 | +sparc sparc sparc 32 big |
1805 | +sparc64 sparc64 sparc64 64 big |
1806 | |
1807 | === added file 'checkbox/parsers/cputable.py' |
1808 | --- checkbox/parsers/cputable.py 1970-01-01 00:00:00 +0000 |
1809 | +++ checkbox/parsers/cputable.py 2012-02-21 14:59:19 +0000 |
1810 | @@ -0,0 +1,42 @@ |
1811 | +# |
1812 | +# This file is part of Checkbox. |
1813 | +# |
1814 | +# Copyright 2011 Canonical Ltd. |
1815 | +# |
1816 | +# Checkbox is free software: you can redistribute it and/or modify |
1817 | +# it under the terms of the GNU General Public License as published by |
1818 | +# the Free Software Foundation, either version 3 of the License, or |
1819 | +# (at your option) any later version. |
1820 | +# |
1821 | +# Checkbox is distributed in the hope that it will be useful, |
1822 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1823 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1824 | +# GNU General Public License for more details. |
1825 | +# |
1826 | +# You should have received a copy of the GNU General Public License |
1827 | +# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
1828 | +# |
1829 | +import re |
1830 | + |
1831 | + |
1832 | +CPUTABLE_RE = re.compile( |
1833 | + r"^(?!\#)(?P<debian_name>\S+)" |
1834 | + r"\s+(?P<gnu_name>\S+)" |
1835 | + r"\s+(?P<regex>\S+)" |
1836 | + r"\s+(?P<bits>\d+)" |
1837 | + r"\s+(?P<endianness>big|little)") |
1838 | + |
1839 | + |
1840 | +class CputableParser: |
1841 | + """Parser for the /usr/share/dpkg/cputable file.""" |
1842 | + |
1843 | + def __init__(self, stream): |
1844 | + self.stream = stream |
1845 | + |
1846 | + def run(self, result): |
1847 | + for line in self.stream.readlines(): |
1848 | + match = CPUTABLE_RE.match(line) |
1849 | + if match: |
1850 | + cpu = match.groupdict() |
1851 | + cpu["bits"] = int(cpu["bits"]) |
1852 | + result.addCpu(cpu) |
1853 | |
1854 | === added file 'checkbox/parsers/deferred.py' |
1855 | --- checkbox/parsers/deferred.py 1970-01-01 00:00:00 +0000 |
1856 | +++ checkbox/parsers/deferred.py 2012-02-21 14:59:19 +0000 |
1857 | @@ -0,0 +1,27 @@ |
1858 | +# |
1859 | +# This file is part of Checkbox. |
1860 | +# |
1861 | +# Copyright 2011 Canonical Ltd. |
1862 | +# |
1863 | +# Checkbox is free software: you can redistribute it and/or modify |
1864 | +# it under the terms of the GNU General Public License as published by |
1865 | +# the Free Software Foundation, either version 3 of the License, or |
1866 | +# (at your option) any later version. |
1867 | +# |
1868 | +# Checkbox is distributed in the hope that it will be useful, |
1869 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1870 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1871 | +# GNU General Public License for more details. |
1872 | +# |
1873 | +# You should have received a copy of the GNU General Public License |
1874 | +# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
1875 | +# |
1876 | +class DeferredParser: |
1877 | + """Parser for deferred dispatching of events.""" |
1878 | + |
1879 | + def __init__(self, dispatcher, event_type="result"): |
1880 | + self.dispatcher = dispatcher |
1881 | + self.event_type = event_type |
1882 | + |
1883 | + def run(self, result): |
1884 | + self.dispatcher.publishEvent(self.event_type, result) |
1885 | |
1886 | === added file 'checkbox/parsers/description.py' |
1887 | --- checkbox/parsers/description.py 1970-01-01 00:00:00 +0000 |
1888 | +++ checkbox/parsers/description.py 2012-02-21 14:59:19 +0000 |
1889 | @@ -0,0 +1,74 @@ |
1890 | +# |
1891 | +# This file is part of Checkbox. |
1892 | +# |
1893 | +# Copyright 2011 Canonical Ltd. |
1894 | +# |
1895 | +# Checkbox is free software: you can redistribute it and/or modify |
1896 | +# it under the terms of the GNU General Public License as published by |
1897 | +# the Free Software Foundation, either version 3 of the License, or |
1898 | +# (at your option) any later version. |
1899 | +# |
1900 | +# Checkbox is distributed in the hope that it will be useful, |
1901 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1902 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1903 | +# GNU General Public License for more details. |
1904 | +# |
1905 | +# You should have received a copy of the GNU General Public License |
1906 | +# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
1907 | +# |
1908 | +from checkbox.lib.enum import Enum |
1909 | + |
1910 | + |
1911 | +DescriptionState = Enum( |
1912 | + "INIT", |
1913 | + "PURPOSE", |
1914 | + "STEPS", |
1915 | + "INFO", |
1916 | + "VERIFICATION", |
1917 | + "OTHER") |
1918 | + |
1919 | +TransitionStates = { |
1920 | + DescriptionState.INIT: DescriptionState.PURPOSE, |
1921 | + DescriptionState.PURPOSE: DescriptionState.STEPS, |
1922 | + DescriptionState.STEPS: DescriptionState.VERIFICATION, |
1923 | + DescriptionState.INFO: DescriptionState.VERIFICATION, |
1924 | + DescriptionState.VERIFICATION: DescriptionState.OTHER, |
1925 | + DescriptionState.OTHER: DescriptionState.OTHER, |
1926 | + } |
1927 | + |
1928 | + |
1929 | +class DescriptionParser: |
1930 | + """Parser for the description field in jobs.""" |
1931 | + |
1932 | + def __init__(self, stream): |
1933 | + self.stream = stream |
1934 | + |
1935 | + def run(self, result): |
1936 | + parts = {} |
1937 | + state = DescriptionState.INIT |
1938 | + |
1939 | + for line in self.stream.readlines(): |
1940 | + # Check for upper case characters without leading spaces. |
1941 | + if not line[0].isspace() \ |
1942 | + and line.isupper(): |
1943 | + state = TransitionStates[state] |
1944 | + |
1945 | + # Append to description parts between INIT and OTHER states. |
1946 | + elif state > DescriptionState.INIT \ |
1947 | + and state < DescriptionState.OTHER: |
1948 | + # Handle optional INFO state and translations of $output. |
1949 | + if state == DescriptionState.VERIFICATION \ |
1950 | + and "$" in line: |
1951 | + state = DescriptionState.INFO |
1952 | + line = "$output\n" |
1953 | + |
1954 | + parts.setdefault(state, "") |
1955 | + parts[state] += line.lstrip() |
1956 | + |
1957 | + # Only set the description if the last state is still VERIFICATION. |
1958 | + if state == DescriptionState.VERIFICATION: |
1959 | + result.setDescription( |
1960 | + parts[DescriptionState.PURPOSE], |
1961 | + parts[DescriptionState.STEPS], |
1962 | + parts[DescriptionState.VERIFICATION], |
1963 | + parts.get(DescriptionState.INFO)) |
1964 | |
1965 | === added file 'checkbox/parsers/device.py' |
1966 | --- checkbox/parsers/device.py 1970-01-01 00:00:00 +0000 |
1967 | +++ checkbox/parsers/device.py 2012-02-21 14:59:19 +0000 |
1968 | @@ -0,0 +1,24 @@ |
1969 | +# |
1970 | +# This file is part of Checkbox. |
1971 | +# |
1972 | +# Copyright 2011 Canonical Ltd. |
1973 | +# |
1974 | +# Checkbox is free software: you can redistribute it and/or modify |
1975 | +# it under the terms of the GNU General Public License as published by |
1976 | +# the Free Software Foundation, either version 3 of the License, or |
1977 | +# (at your option) any later version. |
1978 | +# |
1979 | +# Checkbox is distributed in the hope that it will be useful, |
1980 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1981 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1982 | +# GNU General Public License for more details. |
1983 | +# |
1984 | +# You should have received a copy of the GNU General Public License |
1985 | +# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
1986 | +# |
1987 | + |
1988 | + |
1989 | +class DeviceResult(object): |
1990 | + |
1991 | + def addDevice(self, device): |
1992 | + pass |
1993 | |
1994 | === added file 'checkbox/parsers/dmidecode.py' |
1995 | --- checkbox/parsers/dmidecode.py 1970-01-01 00:00:00 +0000 |
1996 | +++ checkbox/parsers/dmidecode.py 2012-02-21 14:59:19 +0000 |
1997 | @@ -0,0 +1,123 @@ |
1998 | +# |
1999 | +# This file is part of Checkbox. |
2000 | +# |
2001 | +# Copyright 2011 Canonical Ltd. |
2002 | +# |
2003 | +# Checkbox is free software: you can redistribute it and/or modify |
2004 | +# it under the terms of the GNU General Public License as published by |
2005 | +# the Free Software Foundation, either version 3 of the License, or |
2006 | +# (at your option) any later version. |
2007 | +# |
2008 | +# Checkbox is distributed in the hope that it will be useful, |
2009 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2010 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2011 | +# GNU General Public License for more details. |
2012 | +# |
2013 | +# You should have received a copy of the GNU General Public License |
2014 | +# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
2015 | +# |
2016 | +import re |
2017 | + |
2018 | +from string import ( |
2019 | + hexdigits, |
2020 | + uppercase, |
2021 | + ) |
2022 | + |
2023 | +from checkbox.lib.dmi import ( |
2024 | + Dmi, |
2025 | + DmiDevice, |
2026 | + ) |
2027 | + |
2028 | + |
2029 | +HANDLE_RE = re.compile( |
2030 | + r"^Handle (?P<handle>0x[%s]{4}), " |
2031 | + r"DMI type (?P<type>\d+), " |
2032 | + r"(?P<size>\d+) bytes$" |
2033 | + % hexdigits) |
2034 | +KEY_VALUE_RE = re.compile( |
2035 | + r"^\t(?P<key>[%s].+):( (?P<value>.+))?$" |
2036 | + % uppercase) |
2037 | + |
2038 | + |
2039 | +class DmidecodeParser: |
2040 | + """Parser for the dmidecode command.""" |
2041 | + |
2042 | + _key_map = { |
2043 | + "ID": "serial", |
2044 | + "Manufacturer": "vendor", |
2045 | + "Product Name": "name", |
2046 | + "Serial Number": "serial", |
2047 | + "Type": "type", |
2048 | + "Vendor": "vendor", |
2049 | + "Version": "version", |
2050 | + } |
2051 | + |
2052 | + def __init__(self, stream): |
2053 | + self.stream = stream |
2054 | + |
2055 | + def _parseKey(self, key): |
2056 | + return self._key_map.get(key) |
2057 | + |
2058 | + def _parseValue(self, value): |
2059 | + if value is not None: |
2060 | + value = value.strip() |
2061 | + if not value: |
2062 | + value = None |
2063 | + |
2064 | + return value |
2065 | + |
2066 | + def run(self, result): |
2067 | + output = self.stream.read() |
2068 | + for record in re.split(r"\n{2,}", output): |
2069 | + record = record.strip() |
2070 | + # Skip empty records |
2071 | + if not record: |
2072 | + continue |
2073 | + |
2074 | + # Skip header record |
2075 | + lines = record.split("\n") |
2076 | + line = lines.pop(0) |
2077 | + if line.startswith("#"): |
2078 | + continue |
2079 | + |
2080 | + # Skip records with an unsupported handle |
2081 | + match = HANDLE_RE.match(line) |
2082 | + if not match: |
2083 | + continue |
2084 | + |
2085 | + # Skip records that are empty or inactive |
2086 | + if not lines or lines.pop(0) == "Inactive": |
2087 | + continue |
2088 | + |
2089 | + # Skip disabled entries and end-of-table marker |
2090 | + type_index = int(match.group("type")) |
2091 | + if type_index >= len(Dmi.type_names): |
2092 | + continue |
2093 | + |
2094 | + category = Dmi.type_names[type_index] |
2095 | + category = category.upper().split(" ")[-1] |
2096 | + if category not in ( |
2097 | + "BOARD", "BIOS", "CHASSIS", "PROCESSOR", "SYSTEM"): |
2098 | + continue |
2099 | + |
2100 | + # Parse attributes |
2101 | + attributes = {} |
2102 | + for line in lines: |
2103 | + # Skip lines with an unsupported key/value pair |
2104 | + match = KEY_VALUE_RE.match(line) |
2105 | + if not match: |
2106 | + continue |
2107 | + |
2108 | + # Skip lines with an unsupported key |
2109 | + key = self._parseKey(match.group("key")) |
2110 | + if not key: |
2111 | + continue |
2112 | + |
2113 | + key = "%s_%s" % (category.lower(), key) |
2114 | + value = self._parseValue(match.group("value")) |
2115 | + attributes[key] = value |
2116 | + |
2117 | + device = DmiDevice(attributes, category) |
2118 | + result.addDmiDevice(device) |
2119 | + |
2120 | + return result |
2121 | |
2122 | === added file 'checkbox/parsers/meminfo.py' |
2123 | --- checkbox/parsers/meminfo.py 1970-01-01 00:00:00 +0000 |
2124 | +++ checkbox/parsers/meminfo.py 2012-02-21 14:59:19 +0000 |
2125 | @@ -0,0 +1,46 @@ |
2126 | +# |
2127 | +# This file is part of Checkbox. |
2128 | +# |
2129 | +# Copyright 2011 Canonical Ltd. |
2130 | +# |
2131 | +# Checkbox is free software: you can redistribute it and/or modify |
2132 | +# it under the terms of the GNU General Public License as published by |
2133 | +# the Free Software Foundation, either version 3 of the License, or |
2134 | +# (at your option) any later version. |
2135 | +# |
2136 | +# Checkbox is distributed in the hope that it will be useful, |
2137 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2138 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2139 | +# GNU General Public License for more details. |
2140 | +# |
2141 | +# You should have received a copy of the GNU General Public License |
2142 | +# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
2143 | +# |
2144 | +import re |
2145 | + |
2146 | + |
2147 | +class MeminfoParser: |
2148 | + """Parser for the /proc/meminfo file.""" |
2149 | + |
2150 | + def __init__(self, stream): |
2151 | + self.stream = stream |
2152 | + |
2153 | + def run(self, result): |
2154 | + key_value_pattern = re.compile(r"(?P<key>.*):\s+(?P<value>.*)") |
2155 | + meminfo_map = { |
2156 | + "MemTotal": "total", |
2157 | + "SwapTotal": "swap"} |
2158 | + |
2159 | + meminfo = {} |
2160 | + for line in self.stream.readlines(): |
2161 | + line = line.strip() |
2162 | + match = key_value_pattern.match(line) |
2163 | + if match: |
2164 | + key = match.group("key") |
2165 | + if key in meminfo_map: |
2166 | + key = meminfo_map[key] |
2167 | + value = match.group("value") |
2168 | + (integer, factor) = value.split() |
2169 | + meminfo[key] = int(integer) * 1024 |
2170 | + |
2171 | + result.setMemory(meminfo) |
2172 | |
2173 | === added file 'checkbox/parsers/submission.py' |
2174 | --- checkbox/parsers/submission.py 1970-01-01 00:00:00 +0000 |
2175 | +++ checkbox/parsers/submission.py 2012-02-21 14:59:19 +0000 |
2176 | @@ -0,0 +1,540 @@ |
2177 | +# |
2178 | +# This file is part of Checkbox. |
2179 | +# |
2180 | +# Copyright 2011 Canonical Ltd. |
2181 | +# |
2182 | +# Checkbox is free software: you can redistribute it and/or modify |
2183 | +# it under the terms of the GNU General Public License as published by |
2184 | +# the Free Software Foundation, either version 3 of the License, or |
2185 | +# (at your option) any later version. |
2186 | +# |
2187 | +# Checkbox is distributed in the hope that it will be useful, |
2188 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2189 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2190 | +# GNU General Public License for more details. |
2191 | +# |
2192 | +# You should have received a copy of the GNU General Public License |
2193 | +# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
2194 | +# |
2195 | +try: |
2196 | + import xml.etree.cElementTree as etree |
2197 | +except ImportError: |
2198 | + import cElementTree as etree |
2199 | + |
2200 | +from StringIO import StringIO |
2201 | +from logging import getLogger |
2202 | +from pkg_resources import resource_string |
2203 | + |
2204 | +from checkbox.lib.conversion import string_to_datetime |
2205 | + |
2206 | +from checkbox import parsers |
2207 | +from checkbox.dispatcher import DispatcherQueue |
2208 | +from checkbox.parsers.cpuinfo import CpuinfoParser |
2209 | +from checkbox.parsers.cputable import CputableParser |
2210 | +from checkbox.parsers.deferred import DeferredParser |
2211 | +from checkbox.parsers.dmidecode import DmidecodeParser |
2212 | +from checkbox.parsers.meminfo import MeminfoParser |
2213 | +from checkbox.parsers.udevadm import UdevadmParser |
2214 | +from checkbox.job import (FAIL, PASS, UNINITIATED, UNRESOLVED, |
2215 | + UNSUPPORTED, UNTESTED) |
2216 | + |
2217 | + |
2218 | +class SubmissionResult: |
2219 | + |
2220 | + def __init__(self, test_run_factory, **kwargs): |
2221 | + self.test_run_factory = test_run_factory |
2222 | + self.test_run_kwargs = kwargs |
2223 | + self.dispatcher = DispatcherQueue() |
2224 | + |
2225 | + # Register handlers to incrementally add information |
2226 | + register = self.dispatcher.registerHandler |
2227 | + register(("cpu", "architecture",), self.addCpuArchitecture) |
2228 | + register(("identifier",), self.addIdentifier) |
2229 | + register(("test_run", "attachment",), self.addAttachment) |
2230 | + register(("test_run", "device",), self.addDeviceState) |
2231 | + register(("test_run", "distribution",), self.setDistribution) |
2232 | + register(("test_run", "package_version",), self.addPackageVersion) |
2233 | + register(("test_run", "test_result",), self.addTestResult) |
2234 | + |
2235 | + # Register handlers to set information once |
2236 | + register(("architecture",), self.setArchitecture, count=1) |
2237 | + register( |
2238 | + ("cpuinfo", "machine", "cpuinfo_result",), |
2239 | + self.setCpuinfo, count=1) |
2240 | + register( |
2241 | + ("meminfo", "meminfo_result",), |
2242 | + self.setMeminfo, count=1) |
2243 | + register( |
2244 | + ("project", "series",), |
2245 | + self.setTestRun, count=1) |
2246 | + register( |
2247 | + ("test_run", "architecture",), |
2248 | + self.setArchitectureState, count=1) |
2249 | + register( |
2250 | + ("test_run", "memory",), |
2251 | + self.setMemoryState, count=1) |
2252 | + register( |
2253 | + ("test_run", "processor",), |
2254 | + self.setProcessorState, count=1) |
2255 | + register( |
2256 | + ("udevadm", "bits", "udevadm_result",), |
2257 | + self.setUdevadm, count=1) |
2258 | + |
2259 | + # Publish events passed as keyword arguments |
2260 | + if "project" in kwargs: |
2261 | + self.dispatcher.publishEvent("project", kwargs.pop("project")) |
2262 | + self.dispatcher.publishEvent("series", kwargs.pop("series", None)) |
2263 | + |
2264 | + def addAttachment(self, test_run, attachment): |
2265 | + test_run.addAttachment(**attachment) |
2266 | + |
2267 | + def addContext(self, text, command=None): |
2268 | + if text.strip() == "Command not found.": |
2269 | + return |
2270 | + |
2271 | + self.dispatcher.publishEvent( |
2272 | + "attachment", {"name": command, "content": text }) |
2273 | + |
2274 | + parsers = { |
2275 | + "cat /proc/cpuinfo": self.parseCpuinfo, |
2276 | + "cat /proc/meminfo": self.parseMeminfo, |
2277 | + "dmidecode": DmidecodeParser, |
2278 | + "udevadm info --export-db": self.parseUdevadm, |
2279 | + } |
2280 | + parser = parsers.get(command) |
2281 | + if parser: |
2282 | + if not isinstance(text, unicode): |
2283 | + text = text.decode("utf-8") |
2284 | + stream = StringIO(text) |
2285 | + p = parser(stream) |
2286 | + p.run(self) |
2287 | + |
2288 | + def addCpu(self, cpu): |
2289 | + self.dispatcher.publishEvent("cpu", cpu) |
2290 | + |
2291 | + def addCpuArchitecture(self, cpu, architecture): |
2292 | + if cpu["debian_name"] == architecture: |
2293 | + self.dispatcher.publishEvent("machine", cpu["gnu_name"]) |
2294 | + self.dispatcher.publishEvent("bits", cpu["bits"]) |
2295 | + |
2296 | + def addDevice(self, device): |
2297 | + self.dispatcher.publishEvent("device", device) |
2298 | + |
2299 | + def addDeviceState(self, test_run, device): |
2300 | + test_run.addDeviceState( |
2301 | + bus_name=device.bus, category_name=device.category, |
2302 | + product_name=device.product, vendor_name=device.vendor, |
2303 | + product_id=device.product_id, vendor_id=device.vendor_id, |
2304 | + subproduct_id=device.subproduct_id, |
2305 | + subvendor_id=device.subvendor_id, |
2306 | + driver_name=device.driver, path=device.path) |
2307 | + |
2308 | + def addDmiDevice(self, device): |
2309 | + if device.serial: |
2310 | + self.dispatcher.publishEvent("identifier", device.serial) |
2311 | + |
2312 | + if device.category in ("BOARD", "SYSTEM") \ |
2313 | + and device.vendor != device.product \ |
2314 | + and device.product is not None: |
2315 | + self.dispatcher.publishEvent("model", device.product) |
2316 | + self.dispatcher.publishEvent("make", device.vendor) |
2317 | + self.dispatcher.publishEvent("version", device.version) |
2318 | + |
2319 | + def addIdentifier(self, identifier): |
2320 | + try: |
2321 | + self.identifiers.append(identifier) |
2322 | + except AttributeError: |
2323 | + self.identifiers = [identifier] |
2324 | + self.dispatcher.publishEvent("identifiers", self.identifiers) |
2325 | + |
2326 | + def addPackage(self, package): |
2327 | + package_version = { |
2328 | + "name": package["name"], |
2329 | + "version": package["properties"]["version"], |
2330 | + } |
2331 | + self.dispatcher.publishEvent("package_version", package_version) |
2332 | + |
2333 | + def addPackageVersion(self, test_run, package_version): |
2334 | + test_run.addPackageVersion(**package_version) |
2335 | + |
2336 | + def addQuestion(self, question): |
2337 | + answer_to_status = { |
2338 | + "fail": FAIL, |
2339 | + "no": FAIL, |
2340 | + "pass": PASS, |
2341 | + "skip": UNTESTED, |
2342 | + "uninitiated": UNINITIATED, |
2343 | + "unresolved": UNRESOLVED, |
2344 | + "unsupported": UNSUPPORTED, |
2345 | + "untested": UNTESTED, |
2346 | + "yes": PASS, |
2347 | + } |
2348 | + |
2349 | + test_result = dict( |
2350 | + name=question["name"], |
2351 | + output=question["comment"], |
2352 | + status=answer_to_status[question["answer"]["value"]], |
2353 | + ) |
2354 | + test_result.update(self.test_run_kwargs) |
2355 | + self.dispatcher.publishEvent("test_result", test_result) |
2356 | + |
2357 | + def addTestResult(self, test_run, test_result): |
2358 | + test_run.addTestResult(**test_result) |
2359 | + |
2360 | + def addSummary(self, name, value): |
2361 | + if name == "architecture": |
2362 | + self.dispatcher.publishEvent("architecture", value) |
2363 | + elif name == "distribution": |
2364 | + self.dispatcher.publishEvent("project", value) |
2365 | + elif name == "distroseries": |
2366 | + self.dispatcher.publishEvent("series", value) |
2367 | + |
2368 | + def parseCpuinfo(self, cpuinfo): |
2369 | + self.dispatcher.publishEvent("cpuinfo", cpuinfo) |
2370 | + return DeferredParser(self.dispatcher, "cpuinfo_result") |
2371 | + |
2372 | + def parseMeminfo(self, meminfo): |
2373 | + self.dispatcher.publishEvent("meminfo", meminfo) |
2374 | + return DeferredParser(self.dispatcher, "meminfo_result") |
2375 | + |
2376 | + def parseUdevadm(self, udevadm): |
2377 | + self.dispatcher.publishEvent("udevadm", udevadm) |
2378 | + return DeferredParser(self.dispatcher, "udevadm_result") |
2379 | + |
2380 | + def setArchitecture(self, architecture): |
2381 | + string = resource_string(parsers.__name__, "cputable") |
2382 | + stream = StringIO(string.decode("utf-8")) |
2383 | + parser = CputableParser(stream) |
2384 | + parser.run(self) |
2385 | + |
2386 | + def setArchitectureState(self, test_run, architecture): |
2387 | + test_run.setArchitectureState(architecture) |
2388 | + |
2389 | + def setCpuinfo(self, cpuinfo, machine, cpuinfo_result): |
2390 | + parser = CpuinfoParser(cpuinfo, machine) |
2391 | + parser.run(cpuinfo_result) |
2392 | + |
2393 | + def setMeminfo(self, meminfo, meminfo_result): |
2394 | + parser = MeminfoParser(meminfo) |
2395 | + parser.run(meminfo_result) |
2396 | + |
2397 | + def setDistribution(self, test_run, distribution): |
2398 | + test_run.setDistribution(**distribution) |
2399 | + |
2400 | + def setLSBRelease(self, lsb_release): |
2401 | + self.dispatcher.publishEvent("distribution", lsb_release) |
2402 | + |
2403 | + def setMemory(self, memory): |
2404 | + self.dispatcher.publishEvent("memory", memory) |
2405 | + |
2406 | + def setMemoryState(self, test_run, memory): |
2407 | + test_run.setMemoryState(**memory) |
2408 | + |
2409 | + def setProcessor(self, processor): |
2410 | + self.dispatcher.publishEvent("processor", processor) |
2411 | + |
2412 | + def setProcessorState(self, test_run, processor): |
2413 | + test_run.setProcessorState( |
2414 | + platform_name=processor["platform"], |
2415 | + make=processor["type"], model=processor["model"], |
2416 | + model_number=processor["model_number"], |
2417 | + model_version=processor["model_version"], |
2418 | + model_revision=processor["model_revision"], |
2419 | + cache=processor["cache"], other=processor["other"], |
2420 | + bogomips=processor["bogomips"], speed=processor["speed"], |
2421 | + count=processor["count"]) |
2422 | + |
2423 | + def setTestRun(self, project, series): |
2424 | + test_run = self.test_run_factory( |
2425 | + **self.test_run_kwargs) |
2426 | + self.dispatcher.publishEvent("test_run", test_run) |
2427 | + |
2428 | + def setUdevadm(self, udevadm, bits, udevadm_result): |
2429 | + parser = UdevadmParser(udevadm, bits) |
2430 | + parser.run(udevadm_result) |
2431 | + |
2432 | + |
2433 | +class SubmissionParser: |
2434 | + |
2435 | + def __init__(self, file): |
2436 | + self.file = file |
2437 | + self.logger = getLogger() |
2438 | + |
2439 | + def _getClient(self, node): |
2440 | + """Return a dictionary with the name and version of the client.""" |
2441 | + return { |
2442 | + "name": node.get("name"), |
2443 | + "version": node.get("version"), |
2444 | + } |
2445 | + |
2446 | + def _getProperty(self, node): |
2447 | + """Return the (name, value) of a property.""" |
2448 | + return (node.get("name"), self._getValueAsType(node)) |
2449 | + |
2450 | + def _getProperties(self, node): |
2451 | + """Return a dictionary of properties.""" |
2452 | + properties = {} |
2453 | + for child in node.getchildren(): |
2454 | + assert child.tag == "property", \ |
2455 | + "Unexpected tag <%s>, expected <property>" % child.tag |
2456 | + name, value = self._getProperty(child) |
2457 | + properties[name] = value |
2458 | + |
2459 | + return properties |
2460 | + |
2461 | + def _getValueAsType(self, node): |
2462 | + """Return value of a node as the type attribute.""" |
2463 | + type_ = node.get("type") |
2464 | + if type_ in ("bool",): |
2465 | + value = node.text.strip() |
2466 | + assert value in ("True", "False",), \ |
2467 | + "Unexpected boolean value '%s' in <%s>" % (value, node.tag) |
2468 | + return value == "True" |
2469 | + elif type_ in ("str",): |
2470 | + return unicode(node.text.strip()) |
2471 | + elif type_ in ("int", "long",): |
2472 | + return int(node.text.strip()) |
2473 | + elif type_ in ("float",): |
2474 | + return float(node.text.strip()) |
2475 | + elif type_ in ("list",): |
2476 | + return list(self._getValueAsType(child) |
2477 | + for child in node.getchildren()) |
2478 | + elif type_ in ("dict",): |
2479 | + return dict((child.get("name"), self._getValueAsType(child)) |
2480 | + for child in node.getchildren()) |
2481 | + else: |
2482 | + raise AssertionError( |
2483 | + "Unexpected type '%s' in <%s>" % (type_, node.tag)) |
2484 | + |
2485 | + def _getValueAsBoolean(self, node): |
2486 | + """Return the value of the attribute "value" as a boolean.""" |
2487 | + value = node.attrib["value"] |
2488 | + assert value in ("True", "False",), \ |
2489 | + "Unexpected boolean value '%s' in tag <%s>" % (value, node.tag) |
2490 | + return value == "True" |
2491 | + |
2492 | + def _getValueAsDatetime(self, node): |
2493 | + """Return the value of the attribute "value" as a datetime.""" |
2494 | + string = node.attrib["value"] |
2495 | + return string_to_datetime(string) |
2496 | + |
2497 | + def _getValueAsString(self, node): |
2498 | + """Return the value of the attribute "value".""" |
2499 | + return unicode(node.attrib["value"]) |
2500 | + |
2501 | + def parseContext(self, result, node): |
2502 | + """Parse the <context> part of a submission.""" |
2503 | + duplicates = set() |
2504 | + for child in node.getchildren(): |
2505 | + assert child.tag == "info", \ |
2506 | + "Unexpected tag <%s>, expected <info>" % child.tag |
2507 | + command = child.get("command") |
2508 | + if command not in duplicates: |
2509 | + duplicates.add(command) |
2510 | + text = child.text |
2511 | + if text is None: |
2512 | + text = "" |
2513 | + result.addContext(text, command) |
2514 | + else: |
2515 | + self.logger.debug( |
2516 | + "Duplicate command found in tag <info>: %s" % command) |
2517 | + |
2518 | + def parseHardware(self, result, node): |
2519 | + """Parse the <hardware> section of a submission.""" |
2520 | + parsers = { |
2521 | + "dmi": DmidecodeParser, |
2522 | + "processors": self.parseProcessors, |
2523 | + "udev": result.parseUdevadm, |
2524 | + } |
2525 | + |
2526 | + for child in node.getchildren(): |
2527 | + parser = parsers.get(child.tag) |
2528 | + if parser: |
2529 | + if child.getchildren(): |
2530 | + parser(result, child) |
2531 | + else: |
2532 | + text = child.text |
2533 | + if not isinstance(text, unicode): |
2534 | + text = text.decode("utf-8") |
2535 | + stream = StringIO(text) |
2536 | + p = parser(stream) |
2537 | + p.run(result) |
2538 | + else: |
2539 | + self.logger.debug( |
2540 | + "Unsupported tag <%s> in <hardware>" % child.tag) |
2541 | + |
2542 | + def parseLSBRelease(self, result, node): |
2543 | + """Parse the <lsbrelease> part of a submission.""" |
2544 | + properties = self._getProperties(node) |
2545 | + result.setLSBRelease(properties) |
2546 | + |
2547 | + def parsePackages(self, result, node): |
2548 | + """Parse the <packages> part of a submission.""" |
2549 | + for child in node.getchildren(): |
2550 | + assert child.tag == "package", \ |
2551 | + "Unexpected tag <%s>, expected <package>" % child.tag |
2552 | + |
2553 | + package = { |
2554 | + "name": child.get("name"), |
2555 | + "properties": self._getProperties(child), |
2556 | + } |
2557 | + result.addPackage(package) |
2558 | + |
2559 | + def parseProcessors(self, result, node): |
2560 | + """Parse the <processors> part of a submission.""" |
2561 | + processors = [] |
2562 | + for child in node.getchildren(): |
2563 | + assert child.tag == "processor", \ |
2564 | + "Unexpected tag <%s>, expected <processor>" % child.tag |
2565 | + |
2566 | + # Convert lists to space separated strings. |
2567 | + properties = self._getProperties(child) |
2568 | + for key, value in properties.iteritems(): |
2569 | + if key in ("bogomips", "cache", "count", "speed",): |
2570 | + properties[key] = int(value) |
2571 | + elif isinstance(value, list): |
2572 | + properties[key] = u" ".join(value) |
2573 | + processors.append(properties) |
2574 | + |
2575 | + # Check if /proc/cpuinfo was parsed already. |
2576 | + if any("platform" in processor for processor in processors): |
2577 | + result.setProcessor(processors[0]) |
2578 | + else: |
2579 | + lines = [] |
2580 | + for processor in processors: |
2581 | + # Convert some keys with underscores to spaces instead. |
2582 | + for key, value in processor.iteritems(): |
2583 | + if "_" in key and key != "vendor_id": |
2584 | + key = key.replace("_", " ") |
2585 | + |
2586 | + lines.append(u"%s: %s" % (key, value)) |
2587 | + |
2588 | + lines.append(u"") |
2589 | + |
2590 | + stream = StringIO(u"\n".join(lines)) |
2591 | + parser = result.parseCpuinfo(stream) |
2592 | + parser.run(result) |
2593 | + |
2594 | + def parseQuestions(self, result, node): |
2595 | + """Parse the <questions> part of a submission.""" |
2596 | + for child in node.getchildren(): |
2597 | + assert child.tag == "question", \ |
2598 | + "Unexpected tag <%s>, expected <question>" % child.tag |
2599 | + question = { |
2600 | + "name": child.get("name"), |
2601 | + "targets": [], |
2602 | + } |
2603 | + plugin = child.get("plugin", None) |
2604 | + if plugin is not None: |
2605 | + question["plugin"] = plugin |
2606 | + |
2607 | + answer_choices = [] |
2608 | + for sub_node in child.getchildren(): |
2609 | + sub_tag = sub_node.tag |
2610 | + if sub_tag == "answer": |
2611 | + question["answer"] = answer = {} |
2612 | + answer["type"] = sub_node.get("type") |
2613 | + if answer["type"] == "multiple_choice": |
2614 | + question["answer_choices"] = answer_choices |
2615 | + unit = sub_node.get("unit", None) |
2616 | + if unit is not None: |
2617 | + answer["unit"] = unit |
2618 | + answer["value"] = sub_node.text.strip() |
2619 | + |
2620 | + elif sub_tag == "answer_choices": |
2621 | + for value_node in sub_node.getchildren(): |
2622 | + answer_choices.append( |
2623 | + self._getValueAsType(value_node)) |
2624 | + |
2625 | + elif sub_tag == "target": |
2626 | + # The Relax NG schema ensures that the attribute |
2627 | + # id exists and that it is an integer |
2628 | + target = {"id": int(sub_node.get("id"))} |
2629 | + target["drivers"] = drivers = [] |
2630 | + for driver_node in sub_node.getchildren(): |
2631 | + drivers.append(driver_node.text.strip()) |
2632 | + question["targets"].append(target) |
2633 | + |
2634 | + elif sub_tag in ("comment", "command",): |
2635 | + text = sub_node.text |
2636 | + if text is None: |
2637 | + text = "" |
2638 | + question[sub_tag] = text.strip() |
2639 | + |
2640 | + else: |
2641 | + raise AssertionError( |
2642 | + "Unexpected tag <%s> in <question>" % sub_tag) |
2643 | + |
2644 | + result.addQuestion(question) |
2645 | + |
2646 | + def parseSoftware(self, result, node): |
2647 | + """Parse the <software> section of a submission.""" |
2648 | + parsers = { |
2649 | + "lsbrelease": self.parseLSBRelease, |
2650 | + "packages": self.parsePackages, |
2651 | + } |
2652 | + |
2653 | + for child in node.getchildren(): |
2654 | + parser = parsers.get(child.tag) |
2655 | + if parser: |
2656 | + parser(result, child) |
2657 | + else: |
2658 | + self.logger.debug( |
2659 | + "Unsupported tag <%s> in <software>" % child.tag) |
2660 | + |
2661 | + def parseSummary(self, result, node): |
2662 | + """Parse the <summary> section of a submission.""" |
2663 | + parsers = { |
2664 | + "architecture": self._getValueAsString, |
2665 | + "client": self._getClient, |
2666 | + "contactable": self._getValueAsBoolean, |
2667 | + "date_created": self._getValueAsDatetime, |
2668 | + "distribution": self._getValueAsString, |
2669 | + "distroseries": self._getValueAsString, |
2670 | + "kernel-release": self._getValueAsString, |
2671 | + "live_cd": self._getValueAsBoolean, |
2672 | + "private": self._getValueAsBoolean, |
2673 | + "system_id": self._getValueAsString, |
2674 | + } |
2675 | + |
2676 | + for child in node.getchildren(): |
2677 | + parser = parsers.get(child.tag) |
2678 | + if parser: |
2679 | + value = parser(child) |
2680 | + result.addSummary(child.tag, value) |
2681 | + else: |
2682 | + self.logger.debug( |
2683 | + "Unsupported tag <%s> in <summary>" % child.tag) |
2684 | + |
2685 | + def parseRoot(self, result, node): |
2686 | + """Parse the <system> root of a submission.""" |
2687 | + parsers = { |
2688 | + "context": self.parseContext, |
2689 | + "hardware": self.parseHardware, |
2690 | + "questions": self.parseQuestions, |
2691 | + "software": self.parseSoftware, |
2692 | + "summary": self.parseSummary, |
2693 | + } |
2694 | + |
2695 | + # Iterate over the root children, "summary" first |
2696 | + for child in node.getchildren(): |
2697 | + parser = parsers.get(child.tag) |
2698 | + if parser: |
2699 | + parser(result, child) |
2700 | + else: |
2701 | + self.logger.debug( |
2702 | + "Unsupported tag <%s> in <system>" % child.tag) |
2703 | + |
2704 | + def run(self, test_run_factory, **kwargs): |
2705 | + parser = etree.XMLParser() |
2706 | + |
2707 | + tree = etree.parse(self.file, parser=parser) |
2708 | + root = tree.getroot() |
2709 | + if root.tag != "system": |
2710 | + raise AssertionError( |
2711 | + "Unexpected tag <%s> at root, expected <system>" % root.tag) |
2712 | + |
2713 | + result = SubmissionResult(test_run_factory, **kwargs) |
2714 | + self.parseRoot(result, root) |
2715 | + |
2716 | + return result |
2717 | |
2718 | === added directory 'checkbox/parsers/tests' |
2719 | === added file 'checkbox/parsers/tests/__init__.py' |
2720 | === added file 'checkbox/parsers/tests/cputable.py' |
2721 | --- checkbox/parsers/tests/cputable.py 1970-01-01 00:00:00 +0000 |
2722 | +++ checkbox/parsers/tests/cputable.py 2012-02-21 14:59:19 +0000 |
2723 | @@ -0,0 +1,74 @@ |
2724 | +# |
2725 | +# This file is part of Checkbox. |
2726 | +# |
2727 | +# Copyright 2012 Canonical Ltd. |
2728 | +# |
2729 | +# Checkbox is free software: you can redistribute it and/or modify |
2730 | +# it under the terms of the GNU General Public License as published by |
2731 | +# the Free Software Foundation, either version 3 of the License, or |
2732 | +# (at your option) any later version. |
2733 | +# |
2734 | +# Checkbox is distributed in the hope that it will be useful, |
2735 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2736 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2737 | +# GNU General Public License for more details. |
2738 | +# |
2739 | +# You should have received a copy of the GNU General Public License |
2740 | +# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
2741 | +# |
2742 | +from StringIO import StringIO |
2743 | + |
2744 | +from unittest import TestCase |
2745 | + |
2746 | +from checkbox.parsers.cputable import CputableParser |
2747 | + |
2748 | + |
2749 | +class CputableResult: |
2750 | + |
2751 | + def __init__(self): |
2752 | + self.cpus = [] |
2753 | + |
2754 | + def addCpu(self, cpu): |
2755 | + self.cpus.append(cpu) |
2756 | + |
2757 | + def getByDebianName(self, name): |
2758 | + for cpu in self.cpus: |
2759 | + if cpu["debian_name"] == name: |
2760 | + return cpu |
2761 | + |
2762 | + return None |
2763 | + |
2764 | + def getByGnuName(self, name): |
2765 | + for cpu in self.cpus: |
2766 | + if cpu["gnu_name"] == name: |
2767 | + return cpu |
2768 | + |
2769 | + return None |
2770 | + |
2771 | + |
2772 | +class TestCputableParser(TestCase): |
2773 | + |
2774 | + def getParser(self, string): |
2775 | + stream = StringIO(string) |
2776 | + return CputableParser(stream) |
2777 | + |
2778 | + def getResult(self, string): |
2779 | + parser = self.getParser(string) |
2780 | + result = CputableResult() |
2781 | + parser.run(result) |
2782 | + return result |
2783 | + |
2784 | + def test_empty(self): |
2785 | + result = self.getResult("") |
2786 | + self.assertEquals(result.cpus, []) |
2787 | + |
2788 | + def test_i386(self): |
2789 | + result = self.getResult(""" |
2790 | +# <Debian name> <GNU name> <config.guess regex> <Bits> <Endianness> |
2791 | +i386 i686 (i[3456]86|pentium) 32 little |
2792 | +""") |
2793 | + debian_cpu = result.getByDebianName("i386") |
2794 | + self.assertNotEquals(debian_cpu, None) |
2795 | + gnu_cpu = result.getByGnuName("i686") |
2796 | + self.assertNotEquals(gnu_cpu, None) |
2797 | + self.assertEquals(debian_cpu, gnu_cpu) |
2798 | |
2799 | === added file 'checkbox/parsers/tests/description.py' |
2800 | --- checkbox/parsers/tests/description.py 1970-01-01 00:00:00 +0000 |
2801 | +++ checkbox/parsers/tests/description.py 2012-02-21 14:59:19 +0000 |
2802 | @@ -0,0 +1,146 @@ |
2803 | +# -*- coding: utf-8 -*- |
2804 | +# |
2805 | +# This file is part of Checkbox. |
2806 | +# |
2807 | +# Copyright 2012 Canonical Ltd. |
2808 | +# |
2809 | +# Checkbox is free software: you can redistribute it and/or modify |
2810 | +# it under the terms of the GNU General Public License as published by |
2811 | +# the Free Software Foundation, either version 3 of the License, or |
2812 | +# (at your option) any later version. |
2813 | +# |
2814 | +# Checkbox is distributed in the hope that it will be useful, |
2815 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2816 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2817 | +# GNU General Public License for more details. |
2818 | +# |
2819 | +# You should have received a copy of the GNU General Public License |
2820 | +# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
2821 | +# |
2822 | +from StringIO import StringIO |
2823 | + |
2824 | +from unittest import TestCase |
2825 | + |
2826 | +from checkbox.parsers.description import DescriptionParser |
2827 | + |
2828 | + |
2829 | +class DescriptionResult: |
2830 | + |
2831 | + purpose = None |
2832 | + steps = None |
2833 | + verification = None |
2834 | + info = None |
2835 | + |
2836 | + def setDescription(self, purpose, steps, verification, info): |
2837 | + self.purpose = purpose |
2838 | + self.steps = steps |
2839 | + self.verification = verification |
2840 | + self.info = info |
2841 | + |
2842 | + |
2843 | +class TestDescriptionParser(TestCase): |
2844 | + |
2845 | + def getParser(self, string): |
2846 | + stream = StringIO(string) |
2847 | + return DescriptionParser(stream) |
2848 | + |
2849 | + def getResult(self, string): |
2850 | + parser = self.getParser(string) |
2851 | + result = DescriptionResult() |
2852 | + parser.run(result) |
2853 | + return result |
2854 | + |
2855 | + def assertResult( |
2856 | + self, result, purpose=None, steps=None, verification=None, info=None): |
2857 | + self.assertEquals(result.purpose, purpose) |
2858 | + self.assertEquals(result.steps, steps) |
2859 | + self.assertEquals(result.verification, verification) |
2860 | + self.assertEquals(result.info, info) |
2861 | + |
2862 | + def test_empty(self): |
2863 | + result = self.getResult("") |
2864 | + self.assertEquals(result.purpose, None) |
2865 | + |
2866 | + def test_purpose(self): |
2867 | + result = self.getResult(""" |
2868 | +PURPOSE: |
2869 | + foo |
2870 | +""") |
2871 | + self.assertResult(result) |
2872 | + |
2873 | + def test_purpose_steps(self): |
2874 | + result = self.getResult(""" |
2875 | +PURPOSE: |
2876 | + foo |
2877 | +STEPS: |
2878 | + bar |
2879 | +""") |
2880 | + self.assertResult(result) |
2881 | + |
2882 | + def test_purpose_steps_verification(self): |
2883 | + result = self.getResult(""" |
2884 | +PURPOSE: |
2885 | + foo |
2886 | +STEPS: |
2887 | + bar |
2888 | +VERIFICATION: |
2889 | + baz |
2890 | +""") |
2891 | + self.assertResult(result, "foo\n", "bar\n", "baz\n") |
2892 | + |
2893 | + def test_purpose_steps_info_verification(self): |
2894 | + result = self.getResult(""" |
2895 | +PURPOSE: |
2896 | + foo |
2897 | +STEPS: |
2898 | + bar |
2899 | +INFO: |
2900 | + $output |
2901 | +VERIFICATION: |
2902 | + baz |
2903 | +""") |
2904 | + self.assertResult(result, "foo\n", "bar\n", "baz\n", "$output\n") |
2905 | + |
2906 | + def test_purpose_steps_verification_other(self): |
2907 | + result = self.getResult(""" |
2908 | +PURPOSE: |
2909 | + foo |
2910 | +STEPS: |
2911 | + bar |
2912 | +VERIFICATION: |
2913 | + baz |
2914 | +OTHER: |
2915 | + blah |
2916 | +""") |
2917 | + self.assertResult(result) |
2918 | + |
2919 | + def test_es(self): |
2920 | + result = self.getResult(u""" |
2921 | +PROPÓSITO: |
2922 | + Esta prueba verifica los diferentes modos de vídeo detectados |
2923 | +PASOS: |
2924 | + 1. Se han detectado las siguientes pantallas y modos de vídeo |
2925 | +INFORMACIÓN: |
2926 | + $ salida |
2927 | +VERIFICACIÓN: |
2928 | + ¿Son las pantallas y los modos de vídeo correctos? |
2929 | +""") |
2930 | + self.assertNotEquals(result.purpose, None) |
2931 | + self.assertNotEquals(result.steps, None) |
2932 | + self.assertNotEquals(result.verification, None) |
2933 | + self.assertEquals(result.info, "$output\n") |
2934 | + |
2935 | + def test_ru(self): |
2936 | + result = self.getResult(u""" |
2937 | +ЦЕЛЬ: |
2938 | + Эта проверка позволит убедиться в работоспособности штекера наушников |
2939 | +ДЕЙСТВИЯ: |
2940 | + 1. Подсоедините наушники к вашему звуковому устройству |
2941 | + 2. Щёлкните кнопку Проверить для воспроизведения звукового сигнала через звуковое устройство |
2942 | +ПРОВЕРКА: |
2943 | + Был ли слышен звук в наушниках и был ли он воспроизведён в ваших наушниках без искажений, щелчков или других искажённых звуков?" |
2944 | +""") |
2945 | + self.assertNotEquals(result.purpose, None) |
2946 | + self.assertNotEquals(result.steps, None) |
2947 | + self.assertNotEquals(result.verification, None) |
2948 | + self.assertEquals(result.info, None) |
2949 | |
2950 | === added file 'checkbox/parsers/tests/dmi.py' |
2951 | --- checkbox/parsers/tests/dmi.py 1970-01-01 00:00:00 +0000 |
2952 | +++ checkbox/parsers/tests/dmi.py 2012-02-21 14:59:19 +0000 |
2953 | @@ -0,0 +1,80 @@ |
2954 | +# |
2955 | +# This file is part of Checkbox. |
2956 | +# |
2957 | +# Copyright 2012 Canonical Ltd. |
2958 | +# |
2959 | +# Checkbox is free software: you can redistribute it and/or modify |
2960 | +# it under the terms of the GNU General Public License as published by |
2961 | +# the Free Software Foundation, either version 3 of the License, or |
2962 | +# (at your option) any later version. |
2963 | +# |
2964 | +# Checkbox is distributed in the hope that it will be useful, |
2965 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2966 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2967 | +# GNU General Public License for more details. |
2968 | +# |
2969 | +# You should have received a copy of the GNU General Public License |
2970 | +# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
2971 | +# |
2972 | +class DmiResult: |
2973 | + |
2974 | + def __init__(self): |
2975 | + self.devices = [] |
2976 | + |
2977 | + def addDmiDevice(self, device): |
2978 | + self.devices.append(device) |
2979 | + |
2980 | + def getDevice(self, category): |
2981 | + for device in self.devices: |
2982 | + if device.category == category: |
2983 | + return device |
2984 | + |
2985 | + return None |
2986 | + |
2987 | + |
2988 | +class TestDmiMixin: |
2989 | + |
2990 | + def getParser(self): |
2991 | + raise NotImplementedError() |
2992 | + |
2993 | + def getResult(self): |
2994 | + parser = self.getParser() |
2995 | + result = DmiResult() |
2996 | + parser.run(result) |
2997 | + return result |
2998 | + |
2999 | + def test_devices(self): |
3000 | + result = self.getResult() |
3001 | + self.assertEquals(len(result.devices), 4) |
3002 | + |
3003 | + def test_bios(self): |
3004 | + result = self.getResult() |
3005 | + device = result.getDevice("BIOS") |
3006 | + self.assertTrue(device) |
3007 | + self.assertEquals(device.product, "BIOS PRODUCT") |
3008 | + self.assertEquals(device.vendor, "BIOS VENDOR") |
3009 | + self.assertEquals(device.serial, None) |
3010 | + |
3011 | + def test_board(self): |
3012 | + result = self.getResult() |
3013 | + device = result.getDevice("BOARD") |
3014 | + self.assertTrue(device) |
3015 | + self.assertEquals(device.product, None) |
3016 | + self.assertEquals(device.vendor, None) |
3017 | + self.assertEquals(device.serial, None) |
3018 | + |
3019 | + def test_chassis(self): |
3020 | + result = self.getResult() |
3021 | + device = result.getDevice("CHASSIS") |
3022 | + self.assertTrue(device) |
3023 | + self.assertEquals(device.product, "Notebook") |
3024 | + self.assertEquals(device.vendor, "CHASSIS VENDOR") |
3025 | + self.assertEquals(device.serial, None) |
3026 | + |
3027 | + def test_system(self): |
3028 | + result = self.getResult() |
3029 | + device = result.getDevice("SYSTEM") |
3030 | + self.assertTrue(device) |
3031 | + self.assertEquals(device.product, "SYSTEM PRODUCT") |
3032 | + self.assertEquals(device.vendor, "SYSTEM VENDOR") |
3033 | + self.assertEquals(device.serial, "SYSTEM SERIAL") |
3034 | |
3035 | === added file 'checkbox/parsers/tests/dmidecode.py' |
3036 | --- checkbox/parsers/tests/dmidecode.py 1970-01-01 00:00:00 +0000 |
3037 | +++ checkbox/parsers/tests/dmidecode.py 2012-02-21 14:59:19 +0000 |
3038 | @@ -0,0 +1,60 @@ |
3039 | +# |
3040 | +# This file is part of Checkbox. |
3041 | +# |
3042 | +# Copyright 2012 Canonical Ltd. |
3043 | +# |
3044 | +# Checkbox is free software: you can redistribute it and/or modify |
3045 | +# it under the terms of the GNU General Public License as published by |
3046 | +# the Free Software Foundation, either version 3 of the License, or |
3047 | +# (at your option) any later version. |
3048 | +# |
3049 | +# Checkbox is distributed in the hope that it will be useful, |
3050 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3051 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3052 | +# GNU General Public License for more details. |
3053 | +# |
3054 | +# You should have received a copy of the GNU General Public License |
3055 | +# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
3056 | +# |
3057 | +from cStringIO import StringIO |
3058 | + |
3059 | +from unittest import TestCase |
3060 | + |
3061 | +from checkbox.parsers.dmidecode import DmidecodeParser |
3062 | +from checkbox.parsers.tests.dmi import TestDmiMixin |
3063 | + |
3064 | + |
3065 | +FAKE_DMIDECODE = """\ |
3066 | +# dmidecode 2.9 |
3067 | +SMBIOS 2.4 present. |
3068 | + |
3069 | +Handle 0x0000, DMI type 0, 24 bytes |
3070 | +BIOS Information |
3071 | +\tVendor: BIOS VENDOR |
3072 | +\tVersion: BIOS PRODUCT |
3073 | + |
3074 | +Handle 0x0001, DMI type 1, 27 bytes |
3075 | +System Information |
3076 | +\tManufacturer: SYSTEM VENDOR |
3077 | +\tProduct Name: SYSTEM PRODUCT |
3078 | +\tSerial Number: SYSTEM SERIAL |
3079 | + |
3080 | +Handle 0x0002, DMI type 2, 8 bytes |
3081 | +Base Board Information |
3082 | +\tManufacturer: Not Available |
3083 | +\tProduct Name: Not Available |
3084 | +\tSerial Number: Not Available |
3085 | + |
3086 | +Handle 0x0003, DMI type 3, 13 bytes |
3087 | +Chassis Information |
3088 | +\tManufacturer: CHASSIS VENDOR |
3089 | +\tType: Notebook |
3090 | +\tSerial Number: Not Available |
3091 | +""" |
3092 | + |
3093 | + |
3094 | +class TestDmidecodeParser(TestCase, TestDmiMixin): |
3095 | + |
3096 | + def getParser(self): |
3097 | + stream = StringIO(FAKE_DMIDECODE) |
3098 | + return DmidecodeParser(stream) |
3099 | |
3100 | === added file 'checkbox/parsers/tests/udevadm.py' |
3101 | --- checkbox/parsers/tests/udevadm.py 1970-01-01 00:00:00 +0000 |
3102 | +++ checkbox/parsers/tests/udevadm.py 2012-02-21 14:59:19 +0000 |
3103 | @@ -0,0 +1,68 @@ |
3104 | +# |
3105 | +# This file is part of Checkbox. |
3106 | +# |
3107 | +# Copyright 2012 Canonical Ltd. |
3108 | +# |
3109 | +# Checkbox is free software: you can redistribute it and/or modify |
3110 | +# it under the terms of the GNU General Public License as published by |
3111 | +# the Free Software Foundation, either version 3 of the License, or |
3112 | +# (at your option) any later version. |
3113 | +# |
3114 | +# Checkbox is distributed in the hope that it will be useful, |
3115 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3116 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3117 | +# GNU General Public License for more details. |
3118 | +# |
3119 | +# You should have received a copy of the GNU General Public License |
3120 | +# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
3121 | +# |
3122 | +from cStringIO import StringIO |
3123 | + |
3124 | +from unittest import TestCase |
3125 | + |
3126 | +from checkbox.parsers.udevadm import UdevadmParser |
3127 | + |
3128 | + |
3129 | +class DeviceResult: |
3130 | + |
3131 | + def __init__(self): |
3132 | + self.devices = [] |
3133 | + |
3134 | + def addDevice(self, device): |
3135 | + self.devices.append(device) |
3136 | + |
3137 | + def getDevice(self, category): |
3138 | + for device in self.devices: |
3139 | + if device.category == category: |
3140 | + return device |
3141 | + |
3142 | + return None |
3143 | + |
3144 | + |
3145 | +class TestUdevadmParser(TestCase): |
3146 | + |
3147 | + def getParser(self, string): |
3148 | + stream = StringIO(string) |
3149 | + return UdevadmParser(stream) |
3150 | + |
3151 | + def getResult(self, string): |
3152 | + parser = self.getParser(string) |
3153 | + result = DeviceResult() |
3154 | + parser.run(result) |
3155 | + return result |
3156 | + |
3157 | + def test_usb_capture(self): |
3158 | + result = self.getResult(""" |
3159 | +P: /devices/pci0000:00/0000:00:1a.7/usb1/1-6/1-6:1.0 |
3160 | +E: UDEV_LOG=3 |
3161 | +E: DEVPATH=/devices/pci0000:00/0000:00:1a.7/usb1/1-6/1-6:1.0 |
3162 | +E: DEVTYPE=usb_interface |
3163 | +E: DRIVER=uvcvideo |
3164 | +E: PRODUCT=17ef/480c/3134 |
3165 | +E: TYPE=239/2/1 |
3166 | +E: INTERFACE=14/1/0 |
3167 | +E: MODALIAS=usb:v17EFp480Cd3134dcEFdsc02dp01ic0Eisc01ip00 |
3168 | +E: SUBSYSTEM=usb |
3169 | +""") |
3170 | + device = result.getDevice("CAPTURE") |
3171 | + self.assertTrue(device) |
3172 | |
3173 | === added file 'checkbox/parsers/udevadm.py' |
3174 | --- checkbox/parsers/udevadm.py 1970-01-01 00:00:00 +0000 |
3175 | +++ checkbox/parsers/udevadm.py 2012-02-21 14:59:19 +0000 |
3176 | @@ -0,0 +1,470 @@ |
3177 | +# |
3178 | +# This file is part of Checkbox. |
3179 | +# |
3180 | +# Copyright 2011 Canonical Ltd. |
3181 | +# |
3182 | +# Checkbox is free software: you can redistribute it and/or modify |
3183 | +# it under the terms of the GNU General Public License as published by |
3184 | +# the Free Software Foundation, either version 3 of the License, or |
3185 | +# (at your option) any later version. |
3186 | +# |
3187 | +# Checkbox is distributed in the hope that it will be useful, |
3188 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3189 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3190 | +# GNU General Public License for more details. |
3191 | +# |
3192 | +# You should have received a copy of the GNU General Public License |
3193 | +# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
3194 | +# |
3195 | +import re |
3196 | +import string |
3197 | + |
3198 | +from checkbox.lib.bit import ( |
3199 | + get_bitmask, |
3200 | + test_bit, |
3201 | + ) |
3202 | +from checkbox.lib.input import Input |
3203 | +from checkbox.lib.pci import Pci |
3204 | +from checkbox.lib.usb import Usb |
3205 | + |
3206 | + |
3207 | +PCI_RE = re.compile( |
3208 | + r"^pci:" |
3209 | + r"v(?P<vendor_id>[%(hex)s]{8})" |
3210 | + r"d(?P<product_id>[%(hex)s]{8})" |
3211 | + r"sv(?P<subvendor_id>[%(hex)s]{8})" |
3212 | + r"sd(?P<subproduct_id>[%(hex)s]{8})" |
3213 | + r"bc(?P<class>[%(hex)s]{2})" |
3214 | + r"sc(?P<subclass>[%(hex)s]{2})" |
3215 | + r"i(?P<interface>[%(hex)s]{2})" |
3216 | + % {"hex": string.hexdigits}) |
3217 | +PNP_RE = re.compile( |
3218 | + r"^acpi:" |
3219 | + r"(?P<vendor_name>[%(upper)s]{3})" |
3220 | + r"(?P<product_id>[%(hex)s]{4}):" |
3221 | + % {"upper": string.uppercase, "hex": string.hexdigits}) |
3222 | +USB_RE = re.compile( |
3223 | + r"^usb:" |
3224 | + r"v(?P<vendor_id>[%(hex)s]{4})" |
3225 | + r"p(?P<product_id>[%(hex)s]{4})" |
3226 | + r"d(?P<revision>[%(hex)s]{4})" |
3227 | + r"dc(?P<class>[%(hex)s]{2})" |
3228 | + r"dsc(?P<subclass>[%(hex)s]{2})" |
3229 | + r"dp(?P<protocol>[%(hex)s]{2})" |
3230 | + r"ic(?P<interface_class>[%(hex)s]{2})" |
3231 | + r"isc(?P<interface_subclass>[%(hex)s]{2})" |
3232 | + r"ip(?P<interface_protocol>[%(hex)s]{2})" |
3233 | + % {"hex": string.hexdigits}) |
3234 | +SCSI_RE = re.compile( |
3235 | + r"^scsi:" |
3236 | + r"t-0x(?P<type>[%(hex)s]{2})" |
3237 | + % {"hex": string.hexdigits}) |
3238 | + |
3239 | + |
3240 | +class UdevadmDevice: |
3241 | + __slots__ = ("_environment", "_bits", "_stack",) |
3242 | + |
3243 | + def __init__(self, environment, bits=None, stack=[]): |
3244 | + self._environment = environment |
3245 | + self._bits = bits |
3246 | + self._stack = stack |
3247 | + |
3248 | + @property |
3249 | + def bus(self): |
3250 | + # Change the bus from 'acpi' to 'pnp' for some devices |
3251 | + if PNP_RE.match(self._environment.get("MODALIAS", "")) \ |
3252 | + and self.path.endswith(":00"): |
3253 | + return "pnp" |
3254 | + |
3255 | + # Change the bus from 'block' to parent |
3256 | + if self._environment.get("DEVTYPE") == "disk" and self._stack: |
3257 | + return self._stack[-1]._environment.get("SUBSYSTEM") |
3258 | + |
3259 | + bus = self._environment.get("SUBSYSTEM") |
3260 | + if bus == "pnp": |
3261 | + return None |
3262 | + |
3263 | + return bus |
3264 | + |
3265 | + @property |
3266 | + def category(self): |
3267 | + if "IFINDEX" in self._environment: |
3268 | + return "NETWORK" |
3269 | + |
3270 | + if "PCI_CLASS" in self._environment: |
3271 | + pci_class_string = self._environment["PCI_CLASS"] |
3272 | + pci_class = int(pci_class_string, 16) |
3273 | + |
3274 | + # Strip prog_if if defined |
3275 | + if pci_class > 0xFFFF: |
3276 | + pci_class >>= 8 |
3277 | + |
3278 | + subclass_id = pci_class & 0xFF |
3279 | + class_id = (pci_class >> 8) & 0xFF |
3280 | + |
3281 | + if class_id == Pci.BASE_CLASS_NETWORK: |
3282 | + if subclass_id == Pci.CLASS_NETWORK_WIRELESS: |
3283 | + return "WIRELESS" |
3284 | + else: |
3285 | + return "NETWORK" |
3286 | + |
3287 | + if class_id == Pci.BASE_CLASS_DISPLAY: |
3288 | + if subclass_id == Pci.CLASS_DISPLAY_VGA: |
3289 | + return "VIDEO" |
3290 | + else: |
3291 | + return "OTHER" |
3292 | + |
3293 | + if class_id == Pci.BASE_CLASS_SERIAL \ |
3294 | + and subclass_id == Pci.CLASS_SERIAL_USB: |
3295 | + return "USB" |
3296 | + |
3297 | + if class_id == Pci.BASE_CLASS_STORAGE: |
3298 | + if subclass_id == Pci.CLASS_STORAGE_SCSI: |
3299 | + return "SCSI" |
3300 | + |
3301 | + if subclass_id == Pci.CLASS_STORAGE_IDE: |
3302 | + return "IDE" |
3303 | + |
3304 | + if subclass_id == Pci.CLASS_STORAGE_FLOPPY: |
3305 | + return "FLOPPY" |
3306 | + |
3307 | + if subclass_id == Pci.CLASS_STORAGE_RAID: |
3308 | + return "RAID" |
3309 | + |
3310 | + if class_id == Pci.BASE_CLASS_COMMUNICATION \ |
3311 | + and subclass_id == Pci.CLASS_COMMUNICATION_MODEM: |
3312 | + return "MODEM" |
3313 | + |
3314 | + if class_id == Pci.BASE_CLASS_INPUT \ |
3315 | + and subclass_id == Pci.CLASS_INPUT_SCANNER: |
3316 | + return "SCANNER" |
3317 | + |
3318 | + if class_id == Pci.BASE_CLASS_MULTIMEDIA: |
3319 | + if subclass_id == Pci.CLASS_MULTIMEDIA_VIDEO: |
3320 | + return "CAPTURE" |
3321 | + |
3322 | + if subclass_id == Pci.CLASS_MULTIMEDIA_AUDIO \ |
3323 | + or subclass_id == Pci.CLASS_MULTIMEDIA_AUDIO_DEVICE: |
3324 | + return "AUDIO" |
3325 | + |
3326 | + if class_id == Pci.BASE_CLASS_SERIAL \ |
3327 | + and subclass_id == Pci.CLASS_SERIAL_FIREWIRE: |
3328 | + return "FIREWIRE" |
3329 | + |
3330 | + if class_id == Pci.BASE_CLASS_BRIDGE \ |
3331 | + and (subclass_id == Pci.CLASS_BRIDGE_PCMCIA \ |
3332 | + or subclass_id == Pci.CLASS_BRIDGE_CARDBUS): |
3333 | + return "SOCKET" |
3334 | + |
3335 | + if "TYPE" in self._environment and "INTERFACE" in self._environment: |
3336 | + interface_class, interface_subclass, interface_protocol = ( |
3337 | + int(i) for i in self._environment["INTERFACE"].split("/")) |
3338 | + |
3339 | + if interface_class == Usb.BASE_CLASS_AUDIO: |
3340 | + return "AUDIO" |
3341 | + |
3342 | + if interface_class == Usb.BASE_CLASS_PRINTER: |
3343 | + return "PRINTER" |
3344 | + |
3345 | + if interface_class == Usb.BASE_CLASS_STORAGE: |
3346 | + if interface_subclass == Usb.CLASS_STORAGE_FLOPPY: |
3347 | + return "FLOPPY" |
3348 | + |
3349 | + if interface_subclass == Usb.CLASS_STORAGE_SCSI: |
3350 | + return "SCSI" |
3351 | + |
3352 | + if interface_class == Usb.BASE_CLASS_VIDEO: |
3353 | + return "CAPTURE" |
3354 | + |
3355 | + if interface_class == Usb.BASE_CLASS_WIRELESS: |
3356 | + if interface_protocol == Usb.PROTOCOL_BLUETOOTH: |
3357 | + return "BLUETOOTH" |
3358 | + else: |
3359 | + return "WIRELESS" |
3360 | + |
3361 | + if "KEY" in self._environment: |
3362 | + key = self._environment["KEY"].strip("=") |
3363 | + bitmask = get_bitmask(key) |
3364 | + |
3365 | + for i in range(Input.KEY_Q, Input.KEY_P + 1): |
3366 | + if not test_bit(i, bitmask, self._bits): |
3367 | + break |
3368 | + else: |
3369 | + return "KEYBOARD" |
3370 | + |
3371 | + if test_bit(Input.KEY_CAMERA, bitmask, self._bits): |
3372 | + return "CAPTURE" |
3373 | + |
3374 | + if test_bit(Input.BTN_TOUCH, bitmask, self._bits): |
3375 | + return "TOUCH" |
3376 | + |
3377 | + if test_bit(Input.BTN_MOUSE, bitmask, self._bits): |
3378 | + return "MOUSE" |
3379 | + |
3380 | + if "ID_TYPE" in self._environment: |
3381 | + id_type = self._environment["ID_TYPE"] |
3382 | + |
3383 | + if id_type == "cd": |
3384 | + return "CDROM" |
3385 | + |
3386 | + if id_type == "disk": |
3387 | + return "DISK" |
3388 | + |
3389 | + if id_type == "video": |
3390 | + return "VIDEO" |
3391 | + |
3392 | + if "DEVTYPE" in self._environment: |
3393 | + devtype = self._environment["DEVTYPE"] |
3394 | + if devtype == "disk": |
3395 | + if "ID_CDROM" in self._environment: |
3396 | + return "CDROM" |
3397 | + |
3398 | + if "ID_DRIVE_FLOPPY" in self._environment: |
3399 | + return "FLOPPY" |
3400 | + |
3401 | + if devtype == "scsi_device": |
3402 | + match = SCSI_RE.match(self._environment.get("MODALIAS", "")) |
3403 | + type = int(match.group("type"), 16) if match else -1 |
3404 | + |
3405 | + # Check FLASH drives, see /lib/udev/rules.d/80-udisks.rules |
3406 | + if type in (0, 7, 14) \ |
3407 | + and not any(d.driver == "rts_pstor" for d in self._stack): |
3408 | + return "DISK" |
3409 | + |
3410 | + if type == 1: |
3411 | + return "TAPE" |
3412 | + |
3413 | + if type == 2: |
3414 | + return "PRINTER" |
3415 | + |
3416 | + if type in (4, 5): |
3417 | + return "CDROM" |
3418 | + |
3419 | + if type == 6: |
3420 | + return "SCANNER" |
3421 | + |
3422 | + if type == 12: |
3423 | + return "RAID" |
3424 | + |
3425 | + if "DRIVER" in self._environment: |
3426 | + if self._environment["DRIVER"] == "floppy": |
3427 | + return "FLOPPY" |
3428 | + |
3429 | + if self.product: |
3430 | + return "OTHER" |
3431 | + |
3432 | + return None |
3433 | + |
3434 | + @property |
3435 | + def driver(self): |
3436 | + if "DRIVER" in self._environment: |
3437 | + return self._environment["DRIVER"] |
3438 | + |
3439 | + # Check parent device for driver |
3440 | + if self._stack: |
3441 | + parent = self._stack[-1] |
3442 | + if "DRIVER" in parent._environment: |
3443 | + return parent._environment["DRIVER"] |
3444 | + |
3445 | + return None |
3446 | + |
3447 | + @property |
3448 | + def path(self): |
3449 | + devpath = self._environment.get("DEVPATH") |
3450 | + if self._environment.get("DEVTYPE") == "disk" and self._stack: |
3451 | + devpath = re.sub(r"/[^/]+/[^/]+$", "", devpath) |
3452 | + |
3453 | + return devpath |
3454 | + |
3455 | + @property |
3456 | + def product_id(self): |
3457 | + # pci |
3458 | + match = PCI_RE.match(self._environment.get("MODALIAS", "")) |
3459 | + if match: |
3460 | + return int(match.group("product_id"), 16) |
3461 | + |
3462 | + # usb |
3463 | + match = USB_RE.match(self._environment.get("MODALIAS", "")) |
3464 | + if match: |
3465 | + return int(match.group("product_id"), 16) |
3466 | + |
3467 | + # pnp |
3468 | + match = PNP_RE.match(self._environment.get("MODALIAS", "")) |
3469 | + if match: |
3470 | + product_id = int(match.group("product_id"), 16) |
3471 | + # Ignore interrupt controllers |
3472 | + if product_id > 0x0100: |
3473 | + return product_id |
3474 | + |
3475 | + return None |
3476 | + |
3477 | + @property |
3478 | + def vendor_id(self): |
3479 | + # pci |
3480 | + match = PCI_RE.match(self._environment.get("MODALIAS", "")) |
3481 | + if match: |
3482 | + return int(match.group("vendor_id"), 16) |
3483 | + |
3484 | + # usb |
3485 | + match = USB_RE.match(self._environment.get("MODALIAS", "")) |
3486 | + if match: |
3487 | + return int(match.group("vendor_id"), 16) |
3488 | + |
3489 | + return None |
3490 | + |
3491 | + @property |
3492 | + def subproduct_id(self): |
3493 | + if "PCI_SUBSYS_ID" in self._environment: |
3494 | + pci_subsys_id = self._environment["PCI_SUBSYS_ID"] |
3495 | + subvendor_id, subproduct_id = pci_subsys_id.split(":") |
3496 | + return int(subproduct_id, 16) |
3497 | + |
3498 | + return None |
3499 | + |
3500 | + @property |
3501 | + def subvendor_id(self): |
3502 | + if "PCI_SUBSYS_ID" in self._environment: |
3503 | + pci_subsys_id = self._environment["PCI_SUBSYS_ID"] |
3504 | + subvendor_id, subproduct_id = pci_subsys_id.split(":") |
3505 | + return int(subvendor_id, 16) |
3506 | + |
3507 | + return None |
3508 | + |
3509 | + @property |
3510 | + def product(self): |
3511 | + for element in ("NAME", |
3512 | + "RFKILL_NAME", |
3513 | + "POWER_SUPPLY_MODEL_NAME"): |
3514 | + if element in self._environment: |
3515 | + return self._environment[element].strip('"') |
3516 | + |
3517 | + # disk |
3518 | + if self._environment.get("DEVTYPE") == "scsi_device": |
3519 | + for device in reversed(self._stack): |
3520 | + if device._environment.get("ID_BUS") == "usb": |
3521 | + return decode_id(device._environment["ID_MODEL_ENC"]) |
3522 | + |
3523 | + if self._environment.get("DEVTYPE") == "disk" \ |
3524 | + and self._environment.get("ID_BUS") == "ata": |
3525 | + return decode_id(self._environment["ID_MODEL_ENC"]) |
3526 | + |
3527 | + # floppy |
3528 | + if self.driver == "floppy": |
3529 | + return u"Platform Device" |
3530 | + |
3531 | + return None |
3532 | + |
3533 | + @property |
3534 | + def vendor(self): |
3535 | + if "RFKILL_NAME" in self._environment: |
3536 | + return None |
3537 | + |
3538 | + if "POWER_SUPPLY_MANUFACTURER" in self._environment: |
3539 | + return self._environment["POWER_SUPPLY_MANUFACTURER"] |
3540 | + |
3541 | + # pnp |
3542 | + match = PNP_RE.match(self._environment.get("MODALIAS", "")) |
3543 | + if match: |
3544 | + return match.group("vendor_name") |
3545 | + |
3546 | + # disk |
3547 | + if self._environment.get("DEVTYPE") == "scsi_device": |
3548 | + for device in reversed(self._stack): |
3549 | + if device._environment.get("ID_BUS") == "usb": |
3550 | + return decode_id(device._environment["ID_VENDOR_ENC"]) |
3551 | + |
3552 | + return None |
3553 | + |
3554 | + |
3555 | +class UdevadmParser: |
3556 | + """Parser for the udevadm command.""" |
3557 | + |
3558 | + device_factory = UdevadmDevice |
3559 | + |
3560 | + def __init__(self, stream, bits=None): |
3561 | + self.stream = stream |
3562 | + self.bits = bits |
3563 | + |
3564 | + def _ignoreDevice(self, device): |
3565 | + # Ignore devices without bus information |
3566 | + if not device.bus: |
3567 | + return True |
3568 | + |
3569 | + # Ignore devices without product information |
3570 | + if not device.product and device.product_id is None: |
3571 | + return True |
3572 | + |
3573 | + # Ignore invalid subsystem information |
3574 | + if ((device.subproduct_id is None |
3575 | + and device.subvendor_id is not None) |
3576 | + or (device.subproduct_id is not None |
3577 | + and device.subvendor_id is None)): |
3578 | + return True |
3579 | + |
3580 | + # Ignore ACPI devices |
3581 | + if device.bus == "acpi": |
3582 | + return True |
3583 | + |
3584 | + return False |
3585 | + |
3586 | + def getAttributes(self, path): |
3587 | + return {} |
3588 | + |
3589 | + def run(self, result): |
3590 | + # Some attribute lines have a space character after the |
3591 | + # ':', others don't have it (see udevadm-info.c). |
3592 | + line_pattern = re.compile(r"(?P<key>[A-Z]):\s*(?P<value>.*)") |
3593 | + multi_pattern = re.compile(r"(?P<key>[^=]+)=(?P<value>.*)") |
3594 | + |
3595 | + stack = [] |
3596 | + output = self.stream.read() |
3597 | + for record in re.split("\n{2,}", output): |
3598 | + record = record.strip() |
3599 | + if not record: |
3600 | + continue |
3601 | + |
3602 | + # Determine path and environment |
3603 | + path = None |
3604 | + element = None |
3605 | + environment = {} |
3606 | + for line in record.split("\n"): |
3607 | + line_match = line_pattern.match(line) |
3608 | + if not line_match: |
3609 | + if environment: |
3610 | + # Append to last environment element |
3611 | + environment[element] += line |
3612 | + continue |
3613 | + |
3614 | + key = line_match.group("key") |
3615 | + value = line_match.group("value") |
3616 | + |
3617 | + if key == "P": |
3618 | + path = value |
3619 | + elif key == "E": |
3620 | + key_match = multi_pattern.match(value) |
3621 | + if not key_match: |
3622 | + raise Exception( |
3623 | + "Device property not supported: %s" % value) |
3624 | + element = key_match.group("key") |
3625 | + environment[element] = key_match.group("value") |
3626 | + |
3627 | + # Update stack |
3628 | + while stack: |
3629 | + if stack[-1].path + "/" in path: |
3630 | + break |
3631 | + stack.pop() |
3632 | + |
3633 | + # Set default DEVPATH |
3634 | + environment.setdefault("DEVPATH", path) |
3635 | + |
3636 | + device = self.device_factory(environment, self.bits, list(stack)) |
3637 | + if not self._ignoreDevice(device): |
3638 | + result.addDevice(device) |
3639 | + |
3640 | + stack.append(device) |
3641 | + |
3642 | + |
3643 | +def decode_id(id): |
3644 | + encoded_id = id.encode("utf-8") |
3645 | + decoded_id = encoded_id.decode("string-escape").decode("utf-8") |
3646 | + return decoded_id.strip() |
3647 | |
3648 | === added file 'checkbox/parsers/utils.py' |
3649 | --- checkbox/parsers/utils.py 1970-01-01 00:00:00 +0000 |
3650 | +++ checkbox/parsers/utils.py 2012-02-21 14:59:19 +0000 |
3651 | @@ -0,0 +1,36 @@ |
3652 | +# |
3653 | +# This file is part of Checkbox. |
3654 | +# |
3655 | +# Copyright 2011 Canonical Ltd. |
3656 | +# |
3657 | +# Checkbox is free software: you can redistribute it and/or modify |
3658 | +# it under the terms of the GNU General Public License as published by |
3659 | +# the Free Software Foundation, either version 3 of the License, or |
3660 | +# (at your option) any later version. |
3661 | +# |
3662 | +# Checkbox is distributed in the hope that it will be useful, |
3663 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3664 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3665 | +# GNU General Public License for more details. |
3666 | +# |
3667 | +# You should have received a copy of the GNU General Public License |
3668 | +# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
3669 | +# |
3670 | + |
3671 | + |
3672 | +def implement_from_dict(class_name, methods=[], superclass=object): |
3673 | + """Return a class that implements empty methods. |
3674 | + |
3675 | + :param superclass: The superclass of the class to be generated. |
3676 | + :param methods: A list of method names to implement as empty. |
3677 | + """ |
3678 | + def empty(*args, **kwargs): |
3679 | + pass |
3680 | + |
3681 | + class_dict = {} |
3682 | + for method in methods: |
3683 | + class_dict[method] = empty |
3684 | + |
3685 | + if not isinstance(superclass, tuple): |
3686 | + superclass = (superclass,) |
3687 | + return type(class_name, superclass, class_dict) |
3688 | |
3689 | === modified file 'checkbox/properties.py' |
3690 | --- checkbox/properties.py 2010-02-16 21:09:28 +0000 |
3691 | +++ checkbox/properties.py 2012-02-21 14:59:19 +0000 |
3692 | @@ -20,7 +20,7 @@ |
3693 | from checkbox.variables import (ConstantVariable, BoolVariable, StringVariable, |
3694 | PathVariable, UnicodeVariable, IntVariable, FloatVariable, TimeVariable, |
3695 | ListVariable, TupleVariable, AnyVariable, DictVariable, MapVariable, |
3696 | - VariableFactory, Variable, get_variable) |
3697 | + FileVariable, VariableFactory, Variable, get_variable) |
3698 | |
3699 | |
3700 | class Property(object): |
3701 | @@ -196,6 +196,11 @@ |
3702 | super(Map, self).__init__(**kwargs) |
3703 | |
3704 | |
3705 | +class File(PropertyType): |
3706 | + |
3707 | + variable_class = FileVariable |
3708 | + |
3709 | + |
3710 | class Message(Map): |
3711 | |
3712 | def __init__(self, type, schema={}, **kwargs): |
3713 | |
3714 | === modified file 'checkbox/reactor.py' |
3715 | --- checkbox/reactor.py 2010-02-16 15:18:39 +0000 |
3716 | +++ checkbox/reactor.py 2012-02-21 14:59:19 +0000 |
3717 | @@ -44,6 +44,7 @@ |
3718 | def __init__(self): |
3719 | self._event_handlers = {} |
3720 | self._local = threading.local() |
3721 | + self._depth = 0 |
3722 | |
3723 | def call_on(self, event_type, handler, priority=0): |
3724 | pair = (handler, priority) |
3725 | @@ -55,18 +56,21 @@ |
3726 | return EventID(event_type, pair) |
3727 | |
3728 | def fire(self, event_type, *args, **kwargs): |
3729 | - logging.debug("Started firing %s.", event_type) |
3730 | + indent = " " * self._depth |
3731 | + self._depth += 1 |
3732 | + logging.debug("%sStarted firing %s.", indent, event_type) |
3733 | |
3734 | handlers = self._event_handlers.get(event_type, ()) |
3735 | if not handlers: |
3736 | - logging.debug("No handlers found for event type: %s", event_type) |
3737 | + logging.debug("%sNo handlers found for event type: %s", indent, event_type) |
3738 | |
3739 | results = [] |
3740 | handlers = sorted(handlers, key=lambda pair: pair[1]) |
3741 | for handler, priority in handlers: |
3742 | try: |
3743 | - logging.debug("Calling %s for %s with priority %d.", |
3744 | - format_object(handler), event_type, priority) |
3745 | + logging.debug("%sCalling %s for %s with priority %d.", |
3746 | + indent, format_object(handler, *args, **kwargs), |
3747 | + event_type, priority) |
3748 | results.append(handler(*args, **kwargs)) |
3749 | except StopException: |
3750 | break |
3751 | @@ -74,17 +78,18 @@ |
3752 | raise |
3753 | except KeyboardInterrupt: |
3754 | logging.exception("Keyboard interrupt while running event " |
3755 | - "handler %s for event type %r with " |
3756 | - "args %r %r.", format_object(handler), |
3757 | - event_type, args, kwargs) |
3758 | + "handler %s for event type %r", |
3759 | + format_object(handler, *args, **kwargs), |
3760 | + event_type) |
3761 | self.stop_all() |
3762 | except: |
3763 | logging.exception("Error running event handler %s for " |
3764 | - "event type %r with args %r %r.", |
3765 | - format_object(handler), event_type, |
3766 | - args, kwargs) |
3767 | + "event type %r", |
3768 | + format_object(handler, *args, **kwargs), |
3769 | + event_type) |
3770 | |
3771 | - logging.debug("Finished firing %s.", event_type) |
3772 | + logging.debug("%sFinished firing %s.", indent, event_type) |
3773 | + self._depth -= 1 |
3774 | return results |
3775 | |
3776 | def has_call(self, event_type): |
3777 | |
3778 | === modified file 'checkbox/report.py' |
3779 | --- checkbox/report.py 2009-07-22 18:02:57 +0000 |
3780 | +++ checkbox/report.py 2012-02-21 14:59:19 +0000 |
3781 | @@ -16,6 +16,9 @@ |
3782 | # You should have received a copy of the GNU General Public License |
3783 | # along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
3784 | # |
3785 | +import libxml2 |
3786 | +import posixpath |
3787 | + |
3788 | from xml.dom.minidom import Document, Element, parseString |
3789 | |
3790 | |
3791 | @@ -27,13 +30,13 @@ |
3792 | handlers to understand the formats for dumping and loading actions. |
3793 | """ |
3794 | |
3795 | - def __init__(self, name, version=None, stylesheet=None): |
3796 | + def __init__(self, name, version=None, stylesheet=None, schema=None): |
3797 | self.name = name |
3798 | self.version = version |
3799 | self.stylesheet = stylesheet |
3800 | + self.schema = schema |
3801 | self.dumps_table = {} |
3802 | self.loads_table = {} |
3803 | - self.document = None |
3804 | |
3805 | def handle_dumps(self, type, handler): |
3806 | """ |
3807 | @@ -83,17 +86,17 @@ |
3808 | Dump the given object which may be a container of any objects |
3809 | supported by the reports added to the manager. |
3810 | """ |
3811 | - self.document = Document() |
3812 | + document = Document() |
3813 | |
3814 | if self.stylesheet: |
3815 | type = "text/xsl" |
3816 | - href = "file://%s" % self.stylesheet |
3817 | - style = self.document.createProcessingInstruction("xml-stylesheet", |
3818 | + href = "file://%s" % posixpath.abspath(self.stylesheet) |
3819 | + style = document.createProcessingInstruction("xml-stylesheet", |
3820 | "type=\"%s\" href=\"%s\"" % (type, href)) |
3821 | - self.document.appendChild(style) |
3822 | + document.appendChild(style) |
3823 | |
3824 | - node = self.document.createElement(self.name) |
3825 | - self.document.appendChild(node) |
3826 | + node = document.createElement(self.name) |
3827 | + document.appendChild(node) |
3828 | |
3829 | if self.version: |
3830 | node.setAttribute("version", self.version) |
3831 | @@ -103,8 +106,6 @@ |
3832 | except KeyError, e: |
3833 | raise ValueError, "Unsupported type: %s" % e |
3834 | |
3835 | - document = self.document |
3836 | - self.document = None |
3837 | return document |
3838 | |
3839 | def loads(self, str): |
3840 | @@ -123,6 +124,32 @@ |
3841 | |
3842 | return ret |
3843 | |
3844 | + def validate(self, str): |
3845 | + """ |
3846 | + Validate the given string |
3847 | + """ |
3848 | + if not self.schema: |
3849 | + return False |
3850 | + |
3851 | + file = open(self.schema) |
3852 | + try: |
3853 | + schema = file.read() |
3854 | + finally: |
3855 | + file.close() |
3856 | + |
3857 | + rngParser = libxml2.relaxNGNewMemParserCtxt(schema, len(schema)) |
3858 | + rngSchema = rngParser.relaxNGParse() |
3859 | + ctxt = rngSchema.relaxNGNewValidCtxt() |
3860 | + doc = libxml2.parseDoc(str) |
3861 | + is_valid = doc.relaxNGValidateDoc(ctxt) |
3862 | + |
3863 | + # Clean up |
3864 | + doc.freeDoc() |
3865 | + del rngParser, rngSchema, ctxt |
3866 | + libxml2.relaxNGCleanupTypes() |
3867 | + libxml2.cleanupParser() |
3868 | + return is_valid == 0 |
3869 | + |
3870 | |
3871 | class Report(object): |
3872 | """A convenience for writing reports. |
3873 | @@ -133,12 +160,12 @@ |
3874 | """ |
3875 | |
3876 | def _create_element(self, name, parent): |
3877 | - element = self._manager.document.createElement(name) |
3878 | + element = parent.ownerDocument.createElement(name) |
3879 | parent.appendChild(element) |
3880 | return element |
3881 | |
3882 | def _create_text_node(self, text, parent): |
3883 | - text_node = self._manager.document.createTextNode(text) |
3884 | + text_node = parent.ownerDocument.createTextNode(text) |
3885 | parent.appendChild(text_node) |
3886 | return text_node |
3887 | |
3888 | |
3889 | === modified file 'checkbox/resource.py' |
3890 | --- checkbox/resource.py 2010-04-11 20:05:28 +0000 |
3891 | +++ checkbox/resource.py 2012-02-21 14:59:19 +0000 |
3892 | @@ -23,10 +23,10 @@ |
3893 | |
3894 | |
3895 | class ResourceObject(object): |
3896 | - __slots__ = ("_list", "_name", "_convert",) |
3897 | + __slots__ = ("_iterator", "_name", "_convert",) |
3898 | |
3899 | - def __init__(self, list, name, convert=lambda x: x): |
3900 | - self._list = list |
3901 | + def __init__(self, iterator, name, convert=lambda x: x): |
3902 | + self._iterator = iterator |
3903 | self._name = name |
3904 | self._convert = convert |
3905 | |
3906 | @@ -51,25 +51,41 @@ |
3907 | def __ne__(self, other): |
3908 | return self._try(other, lambda a, b: a != b) |
3909 | |
3910 | + def __contains__(self, other): |
3911 | + return self._try(other, lambda a, b: b in a) |
3912 | + |
3913 | def _try(self, other, function, until=True, default=False): |
3914 | found = False |
3915 | - for item in self._list: |
3916 | + for item in self._iterator: |
3917 | if self._name in item: |
3918 | value = self._convert(item[self._name]) |
3919 | if function(value, other) == until: |
3920 | - # Append item to list of results |
3921 | - self._list._map._results.append(item) |
3922 | + # Append item to results |
3923 | + self._iterator._map._results.append(item) |
3924 | found = True |
3925 | |
3926 | return until if found else default |
3927 | |
3928 | |
3929 | -class ResourceList(list): |
3930 | - __slots__ = ("_map",) |
3931 | +class ResourceIterator(object): |
3932 | + __slots__ = ("_map", "_values",) |
3933 | |
3934 | - def __init__(self, map, *args, **kwargs): |
3935 | - super(ResourceList, self).__init__(*args, **kwargs) |
3936 | + def __init__(self, map, values): |
3937 | self._map = map |
3938 | + self._values = values |
3939 | + |
3940 | + def __contains__(self, elt): |
3941 | + found = False |
3942 | + for value in self._values: |
3943 | + if elt in value: |
3944 | + self._map._results.append(value) |
3945 | + found = True |
3946 | + |
3947 | + return found |
3948 | + |
3949 | + def __iter__(self): |
3950 | + for value in self._values: |
3951 | + yield value |
3952 | |
3953 | def __getattr__(self, name): |
3954 | return ResourceObject(self, name) |
3955 | @@ -82,7 +98,7 @@ |
3956 | self._function = function |
3957 | |
3958 | def __call__(self, object): |
3959 | - return ResourceObject(object._list, object._name, self._function) |
3960 | + return ResourceObject(object._iterator, object._name, self._function) |
3961 | |
3962 | |
3963 | class ResourceGlobals(dict): |
3964 | @@ -102,7 +118,10 @@ |
3965 | def __getitem__(self, key): |
3966 | value = super(ResourceMap, self).__getitem__(key) |
3967 | if isinstance(value, (list, tuple)): |
3968 | - return ResourceList(self, value) |
3969 | + return ResourceIterator(self, value) |
3970 | + |
3971 | + elif isinstance(value, dict): |
3972 | + return ResourceIterator(self, [value]) |
3973 | |
3974 | else: |
3975 | return value |
3976 | @@ -118,4 +137,4 @@ |
3977 | except Exception: |
3978 | pass |
3979 | |
3980 | - return [] |
3981 | + return None |
3982 | |
3983 | === modified file 'checkbox/tests/report.py' |
3984 | --- checkbox/tests/report.py 2009-02-16 15:48:24 +0000 |
3985 | +++ checkbox/tests/report.py 2012-02-21 14:59:19 +0000 |
3986 | @@ -33,7 +33,7 @@ |
3987 | self._manager.handle_loads("default", self.loads_default) |
3988 | |
3989 | def dumps_test(self, obj, parent): |
3990 | - text_node = self._manager.document.createTextNode(obj) |
3991 | + text_node = parent.ownerDocument.createTextNode(obj) |
3992 | parent.appendChild(text_node) |
3993 | |
3994 | def loads_test(self, node): |
3995 | |
3996 | === modified file 'checkbox/variables.py' |
3997 | --- checkbox/variables.py 2010-02-25 17:37:40 +0000 |
3998 | +++ checkbox/variables.py 2012-02-21 14:59:19 +0000 |
3999 | @@ -19,6 +19,8 @@ |
4000 | import re |
4001 | import posixpath |
4002 | |
4003 | +from StringIO import StringIO |
4004 | + |
4005 | from checkbox.lib.text import split |
4006 | |
4007 | |
4008 | @@ -137,7 +139,7 @@ |
4009 | |
4010 | def coerce(self, value): |
4011 | if isinstance(value, str): |
4012 | - value = unicode(value) |
4013 | + value = unicode(value, encoding="utf-8") |
4014 | elif not isinstance(value, unicode): |
4015 | raise ValueError("%r is not a unicode" % (value,)) |
4016 | |
4017 | @@ -270,6 +272,18 @@ |
4018 | return value |
4019 | |
4020 | |
4021 | +class FileVariable(Variable): |
4022 | + __slots__ = () |
4023 | + |
4024 | + def coerce(self, value): |
4025 | + if isinstance(value, basestring): |
4026 | + value = StringIO(value) |
4027 | + elif not hasattr(value, "read"): |
4028 | + raise ValueError("%r is not a file" % (value,)) |
4029 | + |
4030 | + return value |
4031 | + |
4032 | + |
4033 | def get_variable(obj, attribute): |
4034 | return get_variables(obj)[attribute] |
4035 | |
4036 | |
4037 | === modified file 'checkbox_cli/cli_interface.py' |
4038 | --- checkbox_cli/cli_interface.py 2010-03-07 19:57:57 +0000 |
4039 | +++ checkbox_cli/cli_interface.py 2012-02-21 14:59:19 +0000 |
4040 | @@ -145,7 +145,17 @@ |
4041 | |
4042 | def add_option(self, option, key=None): |
4043 | if key is None: |
4044 | - key = string.lowercase[len(self.keys)] |
4045 | + keys = option.lower() + \ |
4046 | + string.ascii_letters + \ |
4047 | + string.digits + \ |
4048 | + string.punctuation |
4049 | + |
4050 | + keys = keys.replace(' ','') |
4051 | + keys = keys.replace('+', '') |
4052 | + |
4053 | + for key in keys: |
4054 | + if key not in self.keys: |
4055 | + break |
4056 | self.keys.append(key) |
4057 | self.options.append(option) |
4058 | |
4059 | @@ -205,7 +215,7 @@ |
4060 | elif isinstance(options[key], (list, tuple,)): |
4061 | results[key] = [] |
4062 | else: |
4063 | - raise Exception, "Unknown result type: %s" % type(results) |
4064 | + results[key] = None |
4065 | |
4066 | for k in options[key]: |
4067 | self._toggle_results(k, options[key], results[key]) |
4068 | @@ -216,9 +226,6 @@ |
4069 | elif key in options: |
4070 | results.append(key) |
4071 | |
4072 | - else: |
4073 | - raise Exception, "Unknown result type: %s" % type(results) |
4074 | - |
4075 | def show_progress_start(self, text): |
4076 | self.progress = CLIProgressDialog(text) |
4077 | self.progress.show() |
4078 | @@ -240,7 +247,7 @@ |
4079 | for option in options: |
4080 | dialog.add_option(option) |
4081 | |
4082 | - dialog.add_option("Space when finished", " ") |
4083 | + dialog.add_option(_("Space when finished"), " ") |
4084 | |
4085 | results = default |
4086 | while True: |
4087 | @@ -270,8 +277,8 @@ |
4088 | for option in keys: |
4089 | dialog.add_option(option) |
4090 | |
4091 | - dialog.add_option("Plus to expand options", "+") |
4092 | - dialog.add_option("Space when finished", " ") |
4093 | + dialog.add_option(_("Combine with character above to expand node"), "+") |
4094 | + dialog.add_option(_("Space when finished"), " ") |
4095 | |
4096 | do_expand = False |
4097 | results = default |
4098 | @@ -339,7 +346,7 @@ |
4099 | answer = OPTION_TO_ANSWER[option] |
4100 | if answer == NO_ANSWER: |
4101 | text = _("Further information:") |
4102 | - dialog = CLITextDialog(test.name, text) |
4103 | + dialog = CLITextDialog(text) |
4104 | test["data"] = dialog.run(_("Please type here and press" |
4105 | " Ctrl-D when finished:\n")) |
4106 | else: |
4107 | |
4108 | === modified file 'debian/control' |
4109 | --- debian/control 2010-04-17 16:14:22 +0000 |
4110 | +++ debian/control 2012-02-21 14:59:19 +0000 |
4111 | @@ -14,7 +14,7 @@ |
4112 | Replaces: hwtest (<< 0.1-0ubuntu12) |
4113 | Provides: hwtest |
4114 | Depends: ${misc:Depends}, ${python:Depends}, debconf, udev |
4115 | -Recommends: dpkg, lsb-release, python-apport, python-apt, python-gst0.10 |
4116 | +Recommends: dpkg, lsb-release, python-apport, python-apt, python-dateutil, python-gst0.10 |
4117 | Suggests: checkbox-cli | checkbox-gtk |
4118 | Conflicts: hwtest (<< 0.1-0ubuntu12) |
4119 | XB-Python-Version: ${python:Versions} |
4120 | |
4121 | === modified file 'examples/checkbox.ini' |
4122 | --- examples/checkbox.ini 2010-02-25 17:37:40 +0000 |
4123 | +++ examples/checkbox.ini 2012-02-21 14:59:19 +0000 |
4124 | @@ -22,5 +22,5 @@ |
4125 | [checkbox/plugins/jobs_info] |
4126 | |
4127 | # Blacklist of jobs that are potentially dangerous |
4128 | -blacklist = autotest ltp phoronix qa-regression-testing |
4129 | +blacklist = autotest/full_suite ltp phoronix qa-regression-testing |
4130 | clone06.1 ioctl03.1 lchown02.* swapon03.1 |
4131 | |
4132 | === modified file 'jobs/resource.txt.in' |
4133 | --- jobs/resource.txt.in 2012-01-27 13:28:28 +0000 |
4134 | +++ jobs/resource.txt.in 2012-02-21 14:59:19 +0000 |
4135 | @@ -35,6 +35,11 @@ |
4136 | plugin: resource |
4137 | command: udev_resource |
4138 | |
4139 | +name: dmi |
4140 | +plugin: resource |
4141 | +user: root |
4142 | +command: dmi_resource |
4143 | + |
4144 | name: uname |
4145 | plugin: resource |
4146 | command: uname_resource |
4147 | |
4148 | === modified file 'plugins/apport_prompt.py' |
4149 | --- plugins/apport_prompt.py 2010-03-08 15:29:47 +0000 |
4150 | +++ plugins/apport_prompt.py 2012-02-21 14:59:19 +0000 |
4151 | @@ -39,10 +39,10 @@ |
4152 | |
4153 | |
4154 | CATEGORY_TO_PACKAGE = { |
4155 | - "VIDEO": "xorg", |
4156 | "SOUND": "alsa-base"} |
4157 | |
4158 | CATEGORY_TO_SYMPTOM = { |
4159 | + "VIDEO": "display", |
4160 | "DISK": "storage"} |
4161 | |
4162 | |
4163 | @@ -55,6 +55,8 @@ |
4164 | self.symptom = symptom |
4165 | self.pid = None |
4166 | self.save = False |
4167 | + self.tag = '' #Additional tags to add to reports filed |
4168 | + #through this tool |
4169 | |
4170 | |
4171 | class ApportUserInterface(UserInterface): |
4172 | @@ -135,6 +137,11 @@ |
4173 | |
4174 | return [options.index(r) for r in results] |
4175 | |
4176 | + def ui_question_yesno(self, text): |
4177 | + self.interface.show_progress_stop() |
4178 | + result = self.interface.show_radio(text, ["Yes", "No"]) |
4179 | + return result == "Yes" |
4180 | + |
4181 | def open_url(self, url): |
4182 | self.interface.show_url(url) |
4183 | |
4184 | @@ -192,13 +199,13 @@ |
4185 | |
4186 | # Give lowest priority to required packages |
4187 | for resource in test.get("resources", []): |
4188 | - if resource["suite"] == "package": |
4189 | + if "version" in resource: |
4190 | package = resource["name"] |
4191 | break |
4192 | |
4193 | # Give highest priority to required devices |
4194 | for resource in test.get("resources", []): |
4195 | - if resource["suite"] == "device": |
4196 | + if "bus" in resource: |
4197 | category = resource["category"] |
4198 | if category in CATEGORY_TO_PACKAGE: |
4199 | package = CATEGORY_TO_PACKAGE[category] |
4200 | @@ -216,14 +223,22 @@ |
4201 | if not package and not symptom: |
4202 | return |
4203 | |
4204 | - response = interface.show_info(_("Do you want to report a bug?"), |
4205 | + if test.get("suite"): |
4206 | + failed_test_message = _("Test %(name)s from suite %(suite)s failed.") % { |
4207 | + 'name' : test["name"], |
4208 | + 'suite:' : test["suite"]} |
4209 | + else: |
4210 | + failed_test_message = _("Test %s failed.") % test["name"] |
4211 | + failed_test_message += "\n" + _("Do you want to report a bug?") |
4212 | + |
4213 | + response = interface.show_info(failed_test_message, |
4214 | ["yes", "no"], "no") |
4215 | if response == "no": |
4216 | return |
4217 | |
4218 | # Determine corresponding device |
4219 | for resource in test.get("resources", []): |
4220 | - if resource["suite"] == "device": |
4221 | + if "bus" in resource: |
4222 | device = resource["category"].lower() |
4223 | break |
4224 | |
4225 | |
4226 | === modified file 'plugins/backend_info.py' |
4227 | --- plugins/backend_info.py 2010-04-20 16:23:16 +0000 |
4228 | +++ plugins/backend_info.py 2012-02-21 14:59:19 +0000 |
4229 | @@ -18,6 +18,7 @@ |
4230 | # |
4231 | import os |
4232 | import shutil |
4233 | +import logging |
4234 | |
4235 | from subprocess import call, PIPE |
4236 | from tempfile import mkdtemp |
4237 | @@ -25,11 +26,15 @@ |
4238 | from checkbox.lib.fifo import FifoReader, FifoWriter, create_fifo |
4239 | |
4240 | from checkbox.plugin import Plugin |
4241 | -from checkbox.properties import Path |
4242 | - |
4243 | +from checkbox.properties import Path, Float |
4244 | +from checkbox.job import FAIL |
4245 | |
4246 | class BackendInfo(Plugin): |
4247 | |
4248 | + # how long to wait for I/O from/to the backend before the call returns. |
4249 | + # How we behave if I/O times out is dependent on the situation. |
4250 | + timeout = Float(default=60.0) |
4251 | + |
4252 | command = Path(default="%(checkbox_share)s/backend") |
4253 | |
4254 | def register(self, manager): |
4255 | @@ -57,46 +62,92 @@ |
4256 | stdout=PIPE, stderr=PIPE) == 0 and \ |
4257 | call(["pgrep", "-x", "-u", str(uid), "ksmserver"], |
4258 | stdout=PIPE, stderr=PIPE) == 0: |
4259 | - prefix = ["kdesudo", "--"] |
4260 | + prefix = ["kdesudo", "--desktop", "/usr/share/applications/checkbox-gtk.desktop", "--"] |
4261 | elif os.getenv("DISPLAY") and \ |
4262 | call(["which", "gksu"], |
4263 | stdout=PIPE, stderr=PIPE) == 0 and \ |
4264 | call(["pgrep", "-x", "-u", str(uid), "gnome-panel|gconfd-2"], |
4265 | stdout=PIPE, stderr=PIPE) == 0: |
4266 | - prefix = ["gksu", "--"] |
4267 | + prefix = ["gksu", "--description", "/usr/share/applications/checkbox-gtk.desktop", "--"] |
4268 | else: |
4269 | prefix = ["sudo"] |
4270 | |
4271 | return prefix + self.get_command(*args) |
4272 | |
4273 | + def spawn_backend(self, input_fifo, output_fifo): |
4274 | + self.pid = os.fork() |
4275 | + if self.pid == 0: |
4276 | + root_command = self.get_root_command(input_fifo, output_fifo) |
4277 | + os.execvp(root_command[0], root_command) |
4278 | + # Should never get here |
4279 | + |
4280 | + def ping_backend(self): |
4281 | + if not self.parent_reader or not self.parent_writer: |
4282 | + return False |
4283 | + self.parent_writer.write_object("ping") |
4284 | + result = self.parent_reader.read_object() |
4285 | + return result == "pong" |
4286 | + |
4287 | + |
4288 | def gather(self): |
4289 | self.directory = mkdtemp(prefix="checkbox") |
4290 | child_input = create_fifo(os.path.join(self.directory, "input"), 0600) |
4291 | child_output = create_fifo(os.path.join(self.directory, "output"), 0600) |
4292 | |
4293 | - self.pid = os.fork() |
4294 | - if self.pid > 0: |
4295 | - self.parent_writer = FifoWriter(child_input) |
4296 | - self.parent_reader = FifoReader(child_output) |
4297 | + self.backend_is_alive = False |
4298 | + for attempt in range(1,4): |
4299 | + self.spawn_backend(child_input, child_output) |
4300 | + #Only returns if I'm still the parent, so I can do parent stuff here |
4301 | + self.parent_writer = FifoWriter(child_input, timeout=self.timeout) |
4302 | + self.parent_reader = FifoReader(child_output, timeout=self.timeout) |
4303 | + if self.ping_backend(): |
4304 | + logging.debug("Backend responded, continuing execution.") |
4305 | + self.backend_is_alive = True |
4306 | + break |
4307 | + else: |
4308 | + logging.debug("Backend didn't respond, trying to create again.") |
4309 | |
4310 | - else: |
4311 | - root_command = self.get_root_command(child_input, child_output) |
4312 | - os.execvp(root_command[0], root_command) |
4313 | - # Should never get here |
4314 | + if not self.backend_is_alive: |
4315 | + logging.warning("Privileged backend not responding. " + |
4316 | + "jobs specifying user will not be run") |
4317 | |
4318 | def message_exec(self, message): |
4319 | if "user" in message: |
4320 | - self.parent_writer.write_object(message) |
4321 | - result = self.parent_reader.read_object() |
4322 | + if "environ" in message: |
4323 | + #Prepare variables to be "exported" from my environment |
4324 | + #to the backend's. |
4325 | + backend_environ=["%s=%s" % (key, os.environ[key]) |
4326 | + for key in message["environ"] |
4327 | + if key in os.environ] |
4328 | + message=dict(message) #so as to not wreck the |
4329 | + #original message |
4330 | + message["environ"]=backend_environ |
4331 | + |
4332 | + if (self.backend_is_alive and not self.ping_backend()): |
4333 | + self.backend_is_alive = False |
4334 | + |
4335 | + if self.backend_is_alive: |
4336 | + self.parent_writer.write_object(message) |
4337 | + while True: |
4338 | + result = self.parent_reader.read_object() |
4339 | + if result: |
4340 | + break |
4341 | + else: |
4342 | + logging.info("Waiting for result...") |
4343 | + else: |
4344 | + result = (FAIL, "Unable to test. Privileges are " + |
4345 | + "required for this job.", 0,) |
4346 | if result: |
4347 | self._manager.reactor.fire("message-result", *result) |
4348 | |
4349 | def stop(self): |
4350 | + self.parent_writer.write_object("stop") |
4351 | self.parent_writer.close() |
4352 | self.parent_reader.close() |
4353 | shutil.rmtree(self.directory) |
4354 | |
4355 | - os.waitpid(self.pid, 0) |
4356 | + if self.backend_is_alive: |
4357 | + os.waitpid(self.pid, 0) |
4358 | |
4359 | |
4360 | factory = BackendInfo |
4361 | |
4362 | === modified file 'plugins/jobs_info.py' |
4363 | --- plugins/jobs_info.py 2010-02-23 01:50:41 +0000 |
4364 | +++ plugins/jobs_info.py 2012-02-21 14:59:19 +0000 |
4365 | @@ -16,7 +16,7 @@ |
4366 | # You should have received a copy of the GNU General Public License |
4367 | # along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
4368 | # |
4369 | -import re |
4370 | +import os, sys, re |
4371 | import gettext |
4372 | import logging |
4373 | |
4374 | @@ -32,6 +32,10 @@ |
4375 | "status": String(required=False), |
4376 | "suite": String(required=False), |
4377 | "description": Unicode(required=False), |
4378 | + "purpose": Unicode(required=False), |
4379 | + "steps": Unicode(required=False), |
4380 | + "info": Unicode(required=False), |
4381 | + "verification": Unicode(required=False), |
4382 | "command": String(required=False), |
4383 | "depends": List(String(), required=False), |
4384 | "duration": Float(required=False), |
4385 | @@ -50,16 +54,16 @@ |
4386 | |
4387 | # Space separated list of directories where job files are stored. |
4388 | directories = List(Path(), |
4389 | - default_factory=lambda:"%(checkbox_share)s/jobs") |
4390 | + default_factory=lambda: "%(checkbox_share)s/jobs") |
4391 | |
4392 | # List of jobs to blacklist |
4393 | - blacklist = List(String(), default_factory=lambda:"") |
4394 | + blacklist = List(String(), default_factory=lambda: "") |
4395 | |
4396 | # Path to blacklist file |
4397 | blacklist_file = Path(required=False) |
4398 | |
4399 | # List of jobs to whitelist |
4400 | - whitelist = List(String(), default_factory=lambda:"") |
4401 | + whitelist = List(String(), default_factory=lambda: "") |
4402 | |
4403 | # Path to whitelist file |
4404 | whitelist_file = Path(required=False) |
4405 | @@ -78,17 +82,28 @@ |
4406 | try: |
4407 | file = open(filename) |
4408 | except IOError, e: |
4409 | - logging.info("Failed to open file '%s': %s", |
4410 | - filename, e.strerror) |
4411 | + error_message=(gettext.gettext("Failed to open file '%s': %s") % |
4412 | + (filename, e.strerror)) |
4413 | + logging.critical(error_message) |
4414 | + sys.stderr.write("%s\n" % error_message) |
4415 | + sys.exit(os.EX_NOINPUT) |
4416 | else: |
4417 | strings.extend([l.strip() for l in file.readlines()]) |
4418 | |
4419 | - return [re.compile(r"^%s$" % s) for s in strings if s] |
4420 | + return [re.compile(r"^%s$" % s) for s in strings |
4421 | + if s and not s.startswith("#")] |
4422 | |
4423 | def gather(self): |
4424 | # Register temporary handler for report-message events |
4425 | + messages = [] |
4426 | + |
4427 | def report_message(message): |
4428 | - self._manager.reactor.fire("report-job", message) |
4429 | + if self.whitelist_patterns: |
4430 | + name = message["name"] |
4431 | + if not [name for p in self.whitelist_patterns if p.match(name)]: |
4432 | + return |
4433 | + |
4434 | + messages.append(message) |
4435 | |
4436 | # Set domain and message event handler |
4437 | old_domain = gettext.textdomain() |
4438 | @@ -98,6 +113,23 @@ |
4439 | for directory in self.directories: |
4440 | self._manager.reactor.fire("message-directory", directory) |
4441 | |
4442 | + # Apply whitelist ordering |
4443 | + def cmp_function(a, b): |
4444 | + a_name = a["name"] |
4445 | + b_name = b["name"] |
4446 | + for pattern in self.whitelist_patterns: |
4447 | + if pattern.match(a_name): |
4448 | + a_index = self.whitelist_patterns.index(pattern) |
4449 | + elif pattern.match(b_name): |
4450 | + b_index = self.whitelist_patterns.index(pattern) |
4451 | + |
4452 | + return cmp(a_index, b_index) |
4453 | + |
4454 | + if self.whitelist_patterns: |
4455 | + messages = sorted(messages, cmp=cmp_function) |
4456 | + for message in messages: |
4457 | + self._manager.reactor.fire("report-job", message) |
4458 | + |
4459 | # Unset domain and event handler |
4460 | self._manager.reactor.cancel_call(event_id) |
4461 | gettext.textdomain(old_domain) |
4462 | |
4463 | === modified file 'plugins/jobs_prompt.py' |
4464 | --- plugins/jobs_prompt.py 2010-02-22 19:57:55 +0000 |
4465 | +++ plugins/jobs_prompt.py 2012-02-21 14:59:19 +0000 |
4466 | @@ -16,7 +16,9 @@ |
4467 | # You should have received a copy of the GNU General Public License |
4468 | # along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
4469 | # |
4470 | -from checkbox.job import JobStore, UNINITIATED, UNTESTED |
4471 | +from checkbox.contrib.persist import Persist, MemoryBackend |
4472 | + |
4473 | +from checkbox.job import JobStore, PASS, UNINITIATED, UNTESTED |
4474 | from checkbox.properties import Int, Path |
4475 | from checkbox.plugin import Plugin |
4476 | from checkbox.user_interface import NEXT, PREV |
4477 | @@ -30,13 +32,31 @@ |
4478 | # Maximum number of messages per directory |
4479 | store_directory_size = Int(default=1000) |
4480 | |
4481 | + @property |
4482 | + def persist(self): |
4483 | + if self._persist is None: |
4484 | + self._persist = Persist(backend=MemoryBackend()) |
4485 | + |
4486 | + return self._persist.root_at("jobs_prompt") |
4487 | + |
4488 | + @property |
4489 | + def store(self): |
4490 | + if self._store is None: |
4491 | + self._store = JobStore(self.persist, self.store_directory, |
4492 | + self.store_directory_size) |
4493 | + |
4494 | + return self._store |
4495 | + |
4496 | def register(self, manager): |
4497 | super(JobsPrompt, self).register(manager) |
4498 | |
4499 | self._ignore = [] |
4500 | + self._persist = None |
4501 | + self._store = None |
4502 | |
4503 | for (rt, rh) in [ |
4504 | ("begin-persist", self.begin_persist), |
4505 | + ("begin-recover", self.begin_recover), |
4506 | ("ignore-jobs", self.ignore_jobs), |
4507 | ("prompt-job", self.prompt_job), |
4508 | ("prompt-jobs", self.prompt_jobs), |
4509 | @@ -46,9 +66,11 @@ |
4510 | self._manager.reactor.call_on(rt, rh) |
4511 | |
4512 | def begin_persist(self, persist): |
4513 | - persist = persist.root_at("jobs_prompt") |
4514 | - self.store = JobStore(persist, self.store_directory, |
4515 | - self.store_directory_size) |
4516 | + self._persist = persist |
4517 | + |
4518 | + def begin_recover(self, recover): |
4519 | + if not recover: |
4520 | + self.store.delete_all_messages() |
4521 | |
4522 | def ignore_jobs(self, jobs): |
4523 | self._ignore = jobs |
4524 | @@ -65,6 +87,19 @@ |
4525 | if job[attribute] in self._ignore: |
4526 | job["status"] = UNTESTED |
4527 | else: |
4528 | + if "depends" in job: |
4529 | + offset = self.store.get_pending_offset() |
4530 | + self.store.set_pending_offset(0) |
4531 | + messages = self.store.get_pending_messages() |
4532 | + self.store.set_pending_offset(offset) |
4533 | + |
4534 | + # Skip if any message in the depends doesn't pass |
4535 | + depends = job["depends"] |
4536 | + for message in messages: |
4537 | + if message["name"] in depends \ |
4538 | + and message["status"] != PASS: |
4539 | + return |
4540 | + |
4541 | self._manager.reactor.fire("prompt-%s" % job["plugin"], interface, job) |
4542 | |
4543 | def prompt_jobs(self, interface): |
4544 | @@ -96,6 +131,9 @@ |
4545 | tests = [m for m in messages if m.get("type") in ("test", "metric")] |
4546 | self._manager.reactor.fire("report-tests", tests) |
4547 | |
4548 | + suites = [m for m in messages if m.get("type") == "suite"] |
4549 | + self._manager.reactor.fire("report-suites", suites) |
4550 | + |
4551 | attachments = [m for m in messages if m.get("type") == "attachment" and "data" in m] |
4552 | self._manager.reactor.fire("report-attachments", attachments) |
4553 | |
4554 | |
4555 | === modified file 'plugins/lock_prompt.py' |
4556 | --- plugins/lock_prompt.py 2010-02-22 17:01:03 +0000 |
4557 | +++ plugins/lock_prompt.py 2012-02-21 14:59:19 +0000 |
4558 | @@ -17,7 +17,9 @@ |
4559 | # along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
4560 | # |
4561 | import os |
4562 | +import fcntl |
4563 | import posixpath |
4564 | +import signal |
4565 | |
4566 | from time import time |
4567 | |
4568 | @@ -51,6 +53,7 @@ |
4569 | directory = posixpath.dirname(self.filename) |
4570 | safe_make_directory(directory) |
4571 | |
4572 | + # Try to lock the process |
4573 | self._lock = GlobalLock(self.filename) |
4574 | try: |
4575 | self._lock.acquire() |
4576 | @@ -60,5 +63,16 @@ |
4577 | _("There is another checkbox running. Please close it first.")) |
4578 | self._manager.reactor.stop_all() |
4579 | |
4580 | + # Stop the process if the lock is deleted |
4581 | + def handler(signum, frame): |
4582 | + if not posixpath.exists(self.filename): |
4583 | + self._manager.reactor.stop_all() |
4584 | + |
4585 | + signal.signal(signal.SIGIO, handler) |
4586 | + fd = os.open(directory, os.O_RDONLY) |
4587 | + |
4588 | + fcntl.fcntl(fd, fcntl.F_SETSIG, 0) |
4589 | + fcntl.fcntl(fd, fcntl.F_NOTIFY, fcntl.DN_DELETE|fcntl.DN_MULTISHOT) |
4590 | + |
4591 | |
4592 | factory = LockPrompt |
4593 | |
4594 | === modified file 'plugins/manual_test.py' |
4595 | --- plugins/manual_test.py 2010-03-07 19:57:57 +0000 |
4596 | +++ plugins/manual_test.py 2012-02-21 14:59:19 +0000 |
4597 | @@ -34,7 +34,9 @@ |
4598 | # Avoid modifying the content of test in place |
4599 | temp = dict(test) |
4600 | self._manager.reactor.fire("prompt-shell", interface, temp) |
4601 | - return (temp["status"], temp["data"], temp["duration"]) |
4602 | + return (temp["status"], |
4603 | + temp.get("data", ""), |
4604 | + temp.get("duration", 0)) |
4605 | |
4606 | interface.show_test(test, runner) |
4607 | self._manager.reactor.fire("prompt-test", interface, test) |
4608 | |
4609 | === modified file 'plugins/message_info.py' |
4610 | --- plugins/message_info.py 2010-02-25 17:37:40 +0000 |
4611 | +++ plugins/message_info.py 2012-02-21 14:59:19 +0000 |
4612 | @@ -16,17 +16,34 @@ |
4613 | # You should have received a copy of the GNU General Public License |
4614 | # along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
4615 | # |
4616 | +import os |
4617 | import re |
4618 | import posixpath |
4619 | +import signal |
4620 | + |
4621 | from StringIO import StringIO |
4622 | |
4623 | from checkbox.lib.path import path_expand_recursive |
4624 | from checkbox.lib.template_i18n import TemplateI18n |
4625 | |
4626 | +from checkbox.parsers.description import DescriptionParser |
4627 | + |
4628 | from checkbox.job import Job, PASS |
4629 | from checkbox.plugin import Plugin |
4630 | |
4631 | |
4632 | +class DescriptionResult: |
4633 | + |
4634 | + def __init__(self, message): |
4635 | + self.message = message |
4636 | + |
4637 | + def setDescription(self, purpose, steps, verification, info): |
4638 | + self.message["purpose"] = purpose |
4639 | + self.message["steps"] = steps |
4640 | + self.message["verification"] = verification |
4641 | + self.message["info"] = info |
4642 | + |
4643 | + |
4644 | class MessageInfo(Plugin): |
4645 | |
4646 | def register(self, manager): |
4647 | @@ -49,7 +66,6 @@ |
4648 | whitelist_patterns = [re.compile(r"^%s$" % r) for r in whitelist if r] |
4649 | blacklist_patterns = [re.compile(r"^%s$" % r) for r in blacklist if r] |
4650 | |
4651 | - filenames = [] |
4652 | for filename in path_expand_recursive(directory): |
4653 | name = posixpath.basename(filename) |
4654 | if name.startswith(".") or name.endswith("~"): |
4655 | @@ -66,9 +82,18 @@ |
4656 | |
4657 | def message_exec(self, message): |
4658 | if "user" not in message: |
4659 | + |
4660 | + def stop(): |
4661 | + os.kill(0, signal.SIGTERM) |
4662 | + |
4663 | job = Job(message["command"], message.get("environ"), |
4664 | message.get("timeout")) |
4665 | + |
4666 | + # Kill the job if the stop event is fired during execution |
4667 | + event_id = self._manager.reactor.call_on("stop", stop) |
4668 | result = job.execute() |
4669 | + self._manager.reactor.cancel_call(event_id) |
4670 | + |
4671 | self._manager.reactor.fire("message-result", *result) |
4672 | |
4673 | def message_file(self, file, filename="<stream>"): |
4674 | @@ -80,6 +105,10 @@ |
4675 | if long_key.endswith(long_ext): |
4676 | short_key = long_key.replace(long_ext, "") |
4677 | message[short_key] = message.pop(long_key) |
4678 | + if "description" in message: |
4679 | + parser = DescriptionParser(StringIO(message["description"])) |
4680 | + result = DescriptionResult(message) |
4681 | + parser.run(result) |
4682 | |
4683 | if messages: |
4684 | self._manager.reactor.fire("report-messages", messages) |
4685 | |
4686 | === modified file 'plugins/persist_info.py' |
4687 | --- plugins/persist_info.py 2010-04-09 12:56:55 +0000 |
4688 | +++ plugins/persist_info.py 2012-02-21 14:59:19 +0000 |
4689 | @@ -34,15 +34,18 @@ |
4690 | |
4691 | for (rt, rh) in [ |
4692 | ("begin", self.begin), |
4693 | - ("prompt-job", self.save)]: |
4694 | + ("prompt-begin", self.begin), |
4695 | + ("prompt-job", self.save), |
4696 | + ("report-job", self.save)]: |
4697 | self._manager.reactor.call_on(rt, rh, -100) |
4698 | |
4699 | # Save persist data last |
4700 | self._manager.reactor.call_on("stop", self.save, 1000) |
4701 | |
4702 | - def begin(self): |
4703 | - self.persist = Persist(self.filename) |
4704 | - self._manager.reactor.fire("begin-persist", self.persist) |
4705 | + def begin(self, interface=None): |
4706 | + if self.persist is None: |
4707 | + self.persist = Persist(self.filename) |
4708 | + self._manager.reactor.fire("begin-persist", self.persist) |
4709 | |
4710 | def save(self, *args): |
4711 | # Flush data to disk |
4712 | |
4713 | === modified file 'plugins/remote_suite.py' |
4714 | --- plugins/remote_suite.py 2010-02-22 15:16:05 +0000 |
4715 | +++ plugins/remote_suite.py 2012-02-21 14:59:19 +0000 |
4716 | @@ -35,6 +35,7 @@ |
4717 | # Register temporary handler for report-message events |
4718 | def report_message(message): |
4719 | message["suite"] = suite["name"] |
4720 | + self._manager.reactor.fire("report-job", message) |
4721 | |
4722 | event_id = self._manager.reactor.call_on("report-message", report_message) |
4723 | self._manager.reactor.fire("message-exec", suite) |
4724 | |
4725 | === modified file 'plugins/report_prompt.py' |
4726 | --- plugins/report_prompt.py 2010-02-22 15:16:05 +0000 |
4727 | +++ plugins/report_prompt.py 2012-02-21 14:59:19 +0000 |
4728 | @@ -30,9 +30,16 @@ |
4729 | self._manager.reactor.call_on("prompt-report", self.prompt_report) |
4730 | |
4731 | def prompt_report(self, interface): |
4732 | + def report_error(e): |
4733 | + self._manager.reactor.fire("prompt-error", interface, e) |
4734 | + |
4735 | + event_id = self._manager.reactor.call_on("report-error", report_error) |
4736 | + |
4737 | if interface.direction != PREV: |
4738 | interface.show_progress(_("Building report..."), |
4739 | self._manager.reactor.fire, "report") |
4740 | |
4741 | + self._manager.reactor.cancel_call(event_id) |
4742 | + |
4743 | |
4744 | factory = ReportPrompt |
4745 | |
4746 | === modified file 'plugins/resource_info.py' |
4747 | --- plugins/resource_info.py 2010-03-04 03:41:05 +0000 |
4748 | +++ plugins/resource_info.py 2012-02-21 14:59:19 +0000 |
4749 | @@ -34,17 +34,31 @@ |
4750 | def prompt_job(self, interface, job): |
4751 | mask = [] |
4752 | values = [] |
4753 | + failed_requirements = [] |
4754 | + |
4755 | for require in job.get("requires", []): |
4756 | new_values = self.resources.eval(require) |
4757 | mask.append(bool(new_values)) |
4758 | - values.extend(new_values) |
4759 | + |
4760 | + if not bool(new_values): |
4761 | + failed_requirements.append(require) |
4762 | + |
4763 | + if new_values is not None: |
4764 | + values.extend(new_values) |
4765 | |
4766 | if all(mask): |
4767 | job["resources"] = values |
4768 | |
4769 | else: |
4770 | job["status"] = UNSUPPORTED |
4771 | - job["data"] = "Job requirements not met." |
4772 | + |
4773 | + data = "Job requirement%s not met:" % ( |
4774 | + 's' if len(failed_requirements) > 1 else '') |
4775 | + |
4776 | + for failed_require in failed_requirements: |
4777 | + data = data + " '" + failed_require + "'" |
4778 | + |
4779 | + job["data"] = data |
4780 | self._manager.reactor.stop() |
4781 | |
4782 | def report_resource(self, resource): |
4783 | |
4784 | === modified file 'plugins/scripts_info.py' |
4785 | --- plugins/scripts_info.py 2010-02-16 15:18:39 +0000 |
4786 | +++ plugins/scripts_info.py 2012-02-21 14:59:19 +0000 |
4787 | @@ -16,7 +16,7 @@ |
4788 | # You should have received a copy of the GNU General Public License |
4789 | # along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
4790 | # |
4791 | -from checkbox.lib.environ import prepend_path |
4792 | +from checkbox.lib.environ import append_path, prepend_path |
4793 | |
4794 | from checkbox.plugin import Plugin |
4795 | from checkbox.properties import Path |
4796 | @@ -34,6 +34,8 @@ |
4797 | |
4798 | def gather(self): |
4799 | prepend_path(self.scripts_path) |
4800 | + append_path("/sbin") |
4801 | + append_path("/usr/sbin") |
4802 | |
4803 | |
4804 | factory = ScriptsInfo |
4805 | |
4806 | === modified file 'plugins/shell_test.py' |
4807 | --- plugins/shell_test.py 2010-03-05 19:31:13 +0000 |
4808 | +++ plugins/shell_test.py 2012-02-21 14:59:19 +0000 |
4809 | @@ -35,7 +35,8 @@ |
4810 | def prompt_shell(self, interface, test): |
4811 | command = test.get("command") |
4812 | status = test.get("status", UNINITIATED) |
4813 | - if command and status == UNINITIATED: |
4814 | + plugin = test.get("plugin", "shell") |
4815 | + if command and (status == UNINITIATED or plugin != "shell"): |
4816 | # Register temporary handler for message-result events |
4817 | def message_result(status, data, duration): |
4818 | test["status"] = status |
4819 | @@ -48,7 +49,7 @@ |
4820 | event_id = self._manager.reactor.call_on("message-result", message_result, -100) |
4821 | |
4822 | interface.show_progress( |
4823 | - _("Running %s..." % test["name"]), self._manager.reactor.fire, |
4824 | + _("Running %s...") % test["name"], self._manager.reactor.fire, |
4825 | "message-exec", test) |
4826 | |
4827 | self._manager.reactor.cancel_call(event_id) |
4828 | |
4829 | === modified file 'plugins/suites_prompt.py' |
4830 | --- plugins/suites_prompt.py 2010-02-22 17:01:03 +0000 |
4831 | +++ plugins/suites_prompt.py 2012-02-21 14:59:19 +0000 |
4832 | @@ -18,6 +18,10 @@ |
4833 | # |
4834 | import copy |
4835 | |
4836 | +from checkbox.contrib.persist import Persist, MemoryBackend |
4837 | + |
4838 | +from checkbox.job import UNINITIATED |
4839 | + |
4840 | from checkbox.lib.resolver import Resolver |
4841 | |
4842 | from checkbox.plugin import Plugin |
4843 | @@ -28,12 +32,21 @@ |
4844 | |
4845 | class SuitesPrompt(Plugin): |
4846 | |
4847 | + @property |
4848 | + def persist(self): |
4849 | + if self._persist is None: |
4850 | + self._persist = Persist(backend=MemoryBackend()) |
4851 | + |
4852 | + return self._persist.root_at("suites_prompt") |
4853 | + |
4854 | def register(self, manager): |
4855 | super(SuitesPrompt, self).register(manager) |
4856 | |
4857 | self._depends = {} |
4858 | self._jobs = {} |
4859 | - self._recover = False |
4860 | + self._statuses = {} |
4861 | + self._persist = None |
4862 | + self._recover = False |
4863 | |
4864 | for (rt, rh) in [ |
4865 | ("begin-persist", self.begin_persist), |
4866 | @@ -48,10 +61,13 @@ |
4867 | self._manager.reactor.call_on(rt, rh, 100) |
4868 | |
4869 | def begin_persist(self, persist): |
4870 | - self.persist = persist.root_at("suites_prompt") |
4871 | - |
4872 | - def begin_recover(self): |
4873 | - self._recover = True |
4874 | + self._persist = persist |
4875 | + |
4876 | + def begin_recover(self, recover): |
4877 | + self._recover = recover |
4878 | + |
4879 | + if not self._recover: |
4880 | + self.persist.remove("default") |
4881 | |
4882 | def report_suite(self, suite): |
4883 | suite.setdefault("type", "suite") |
4884 | @@ -66,6 +82,8 @@ |
4885 | self._jobs[job["name"]] = job[attribute] |
4886 | if "suite" in job: |
4887 | self._depends[job["name"]] = [job["suite"]] |
4888 | + if job.get("type") == "test": |
4889 | + self._statuses[job["name"]] = job["status"] |
4890 | |
4891 | def prompt_gather(self, interface): |
4892 | # Resolve dependencies |
4893 | @@ -80,31 +98,28 @@ |
4894 | suboptions = options |
4895 | dependencies = resolver.get_dependencies(job) |
4896 | for dependency in dependencies: |
4897 | - suboptions = suboptions.setdefault(self._jobs[dependency], {}) |
4898 | + value = self._statuses.get(dependency, {}) |
4899 | + suboptions = suboptions.setdefault(self._jobs[dependency], value) |
4900 | |
4901 | # Build defaults |
4902 | defaults = self.persist.get("default") |
4903 | if defaults is None: |
4904 | defaults = copy.deepcopy(options) |
4905 | |
4906 | - # Only prompt if not recovering |
4907 | - if interface.direction == PREV or not self._recover: |
4908 | - self._recover = False |
4909 | - |
4910 | - # Get results |
4911 | - defaults = interface.show_tree(_("Select the suites to test"), |
4912 | - options, defaults) |
4913 | - self.persist.set("default", defaults) |
4914 | + # Get results |
4915 | + defaults = interface.show_tree(_("Select the suites to test"), |
4916 | + options, defaults) |
4917 | + self.persist.set("default", defaults) |
4918 | |
4919 | # Get tests to ignore |
4920 | def get_ignore_jobs(options, results): |
4921 | jobs = [] |
4922 | - for k, v in options.iteritems(): |
4923 | - if not v and k not in results: |
4924 | - jobs.append(k) |
4925 | - |
4926 | - else: |
4927 | - jobs.extend(get_ignore_jobs(options[k], results.get(k, {}))) |
4928 | + if isinstance(options,dict): |
4929 | + for k, v in options.iteritems(): |
4930 | + if v == UNINITIATED and k not in results: |
4931 | + jobs.append(k) |
4932 | + else: |
4933 | + jobs.extend(get_ignore_jobs(options[k], results.get(k, {}))) |
4934 | |
4935 | return jobs |
4936 | |
4937 | |
4938 | === modified file 'plugins/system_info.py' |
4939 | --- plugins/system_info.py 2010-03-08 15:29:47 +0000 |
4940 | +++ plugins/system_info.py 2012-02-21 14:59:19 +0000 |
4941 | @@ -33,11 +33,12 @@ |
4942 | def register(self, manager): |
4943 | super(SystemInfo, self).register(manager) |
4944 | |
4945 | + self.persist = None |
4946 | self.resource = None |
4947 | |
4948 | for (rt, rh) in [ |
4949 | ("begin-persist", self.begin_persist), |
4950 | - ("report-device", self.report_device)]: |
4951 | + ("report-dmi", self.report_dmi)]: |
4952 | self._manager.reactor.call_on(rt, rh) |
4953 | |
4954 | # System report should be generated early. |
4955 | @@ -46,14 +47,20 @@ |
4956 | def begin_persist(self, persist): |
4957 | self.persist = persist.root_at("system_info") |
4958 | |
4959 | - def report_device(self, resources): |
4960 | + def report_dmi(self, resources): |
4961 | for resource in resources: |
4962 | if resource.get("category") == "CHASSIS": |
4963 | self.resource = resource |
4964 | |
4965 | # TODO: report this upon gathering |
4966 | def report(self): |
4967 | - system_id = self.system_id or self.persist.get("system_id") |
4968 | + if self.system_id: |
4969 | + system_id = self.system_id |
4970 | + elif self.persist and self.persist.has("system_id"): |
4971 | + system_id = self.persist.get("system_id") |
4972 | + else: |
4973 | + system_id = None |
4974 | + |
4975 | if not system_id: |
4976 | resource = self.resource |
4977 | if resource is None or "product" not in resource: |
4978 | @@ -71,7 +78,8 @@ |
4979 | fingerprint.update(str(field)) |
4980 | |
4981 | system_id = fingerprint.hexdigest() |
4982 | - self.persist.set("system_id", system_id) |
4983 | + if self.persist: |
4984 | + self.persist.set("system_id", system_id) |
4985 | |
4986 | message = system_id |
4987 | logging.info("System ID: %s", message) |
4988 | |
4989 | === modified file 'plugins/user_interface.py' |
4990 | --- plugins/user_interface.py 2010-02-16 15:18:39 +0000 |
4991 | +++ plugins/user_interface.py 2012-02-21 14:59:19 +0000 |
4992 | @@ -21,6 +21,8 @@ |
4993 | from checkbox.user_interface import PREV |
4994 | |
4995 | import gettext |
4996 | +import posixpath |
4997 | + |
4998 | from gettext import gettext as _ |
4999 | |
5000 |
The diff has been truncated for viewing.
Added python-dateutil to checkbox recommends, thanks!