Merge lp:~adeuring/launchpad/bug-460935-hwdb-better-consistency-check-udev-usb-devices into lp:launchpad/db-devel
- bug-460935-hwdb-better-consistency-check-udev-usb-devices
- Merge into db-devel
Status: | Merged |
---|---|
Merged at revision: | not available |
Proposed branch: | lp:~adeuring/launchpad/bug-460935-hwdb-better-consistency-check-udev-usb-devices |
Merge into: | lp:launchpad/db-devel |
Diff against target: |
538 lines 4 files modified
lib/canonical/launchpad/scripts/hwdbsubmissions.py (+11/-4) lib/canonical/launchpad/scripts/tests/test_hwdb_submission_parser.py (+121/-96) lib/lp/testing/__init__.py (+67/-0) lib/lp/testing/tests/test_inlinetests.py (+20/-0) |
To merge this branch: | bzr merge lp:~adeuring/launchpad/bug-460935-hwdb-better-consistency-check-udev-usb-devices |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Eleanor Berger (community) | code | Approve | |
Review via email: mp+13942@code.launchpad.net |
Commit message
Description of the change
Abel Deuring (adeuring) wrote : | # |
Abel Deuring (adeuring) wrote : | # |
This branch is based on
lp:~adeuring/launchpad/bug-458160-fix-submissionparser-checkconsistency, which I did not yet land.
the diff against that branch:
--- lib/canonical/
+++ lib/canonical/
@@ -1929,27 +1929,41 @@
def testCheckUdevUs
- """Test of SubmissionParse
+ """Test of SubmissionParse
+
+ udev nodes for USB devices must define the three properties
+ DEVTYPE, PRODUCT, TYPE or none of them.
+ """
parser = SubmissionParser()
+ for property_name in ('DEVTYPE', 'PRODUCT', 'TYPE'):
+ del self.udev_
+ self.assertTrue
+ [self.udev_
+ self.udev_
+
def testCheckUdevUs
"""Test of SubmissionParse
- A USB device that does not have a required property makes a
- submission invalid.
+ A USB device where some but not all of the properties DEVTYPE,
+ PRODUCT, TYPE are defined makes a submission invalid.
"""
- del self.udev_
- parser = SubmissionParse
- parser.
- self.assertFals
- [self.udev_
- self.assertErro
- parser.
- "USB udev device found without required properties: "
- "set(['DEVTYPE']) '/devices/
+ for property_name in ('DEVTYPE', 'PRODUCT', 'TYPE'):
+ saved_property = self.udev_
+ parser = SubmissionParse
+ parser.
+ 'USB device without %s property' % property_name)
+ self.assertFals
+ [self.udev_
+ self.assertErro
+ parser.
+ "USB udev device found without required properties: "
+ "set(['%s']) '/devices/
+ % property_name)
+ self.udev_
def testCheckUdevUs
"""Test of SubmissionParse
Eleanor Berger (intellectronica) : | # |
Preview Diff
1 | === modified file 'lib/canonical/launchpad/scripts/hwdbsubmissions.py' |
2 | --- lib/canonical/launchpad/scripts/hwdbsubmissions.py 2009-10-22 11:08:18 +0000 |
3 | +++ lib/canonical/launchpad/scripts/hwdbsubmissions.py 2009-10-26 10:45:33 +0000 |
4 | @@ -1228,9 +1228,11 @@ |
5 | def checkUdevUsbProperties(self, udev_data): |
6 | """Validation of udev USB devices. |
7 | |
8 | - USB devices must have the properties DEVTYPE (value |
9 | - 'usb_device' or 'usb_interface'), PRODUCT and TYPE. PRODUCT |
10 | - must be a tuple of three integers in hexadecimal |
11 | + USB devices must either have the three properties DEVTYPE |
12 | + (value 'usb_device' or 'usb_interface'), PRODUCT and TYPE, |
13 | + or they must have none of them. |
14 | + |
15 | + PRODUCT must be a tuple of three integers in hexadecimal |
16 | representation, separates by '/'. TYPE must be a a tuple of |
17 | three integers in decimal representation, separated by '/'. |
18 | usb_interface nodes must additionally have a property |
19 | @@ -1245,6 +1247,10 @@ |
20 | property_names = set(properties) |
21 | existing_usb_properties = property_names.intersection( |
22 | UDEV_USB_DEVICE_PROPERTIES) |
23 | + |
24 | + if len(existing_usb_properties) == 0: |
25 | + continue |
26 | + |
27 | if existing_usb_properties != UDEV_USB_DEVICE_PROPERTIES: |
28 | missing_properties = UDEV_USB_DEVICE_PROPERTIES.difference( |
29 | existing_usb_properties) |
30 | @@ -1365,7 +1371,8 @@ |
31 | if ('udev' in parsed_data['hardware'] |
32 | and not self.checkConsistentUdevDeviceData( |
33 | parsed_data['hardware']['udev'], |
34 | - parsed_data['hardware']['sysfs-attributes'])): |
35 | + parsed_data['hardware']['sysfs-attributes'], |
36 | + parsed_data['hardware']['dmi'],)): |
37 | return False |
38 | duplicate_ids = self.findDuplicateIDs(parsed_data) |
39 | if duplicate_ids: |
40 | |
41 | === modified file 'lib/canonical/launchpad/scripts/tests/test_hwdb_submission_parser.py' |
42 | --- lib/canonical/launchpad/scripts/tests/test_hwdb_submission_parser.py 2009-10-21 16:49:03 +0000 |
43 | +++ lib/canonical/launchpad/scripts/tests/test_hwdb_submission_parser.py 2009-10-26 10:45:33 +0000 |
44 | @@ -23,6 +23,7 @@ |
45 | ROOT_UDI) |
46 | from canonical.testing import BaseLayer |
47 | |
48 | +from lp.testing import validate_mock_class |
49 | |
50 | class SubmissionParserTestParseSoftware(SubmissionParser): |
51 | """A Variant used to test SubmissionParser._parseSoftware. |
52 | @@ -1928,27 +1929,41 @@ |
53 | "'/devices/pci0000:00/0000:00:1f.2'") |
54 | |
55 | def testCheckUdevUsbProperties(self): |
56 | - """Test of SubmissionParser.checkUdevUsbProperties().""" |
57 | + """Test of SubmissionParser.checkUdevUsbProperties(). |
58 | + |
59 | + udev nodes for USB devices must define the three properties |
60 | + DEVTYPE, PRODUCT, TYPE or none of them. |
61 | + """ |
62 | parser = SubmissionParser() |
63 | self.assertTrue(parser.checkUdevUsbProperties( |
64 | [self.udev_root_device, self.udev_usb_device, |
65 | self.udev_usb_interface])) |
66 | |
67 | + for property_name in ('DEVTYPE', 'PRODUCT', 'TYPE'): |
68 | + del self.udev_usb_device['E'][property_name] |
69 | + self.assertTrue(parser.checkUdevUsbProperties( |
70 | + [self.udev_root_device, self.udev_usb_device, |
71 | + self.udev_usb_interface])) |
72 | + |
73 | def testCheckUdevUsbProperties_missing_required_property(self): |
74 | """Test of SubmissionParser.checkUdevUsbProperties(). |
75 | |
76 | - A USB device that does not have a required property makes a |
77 | - submission invalid. |
78 | + A USB device where some but not all of the properties DEVTYPE, |
79 | + PRODUCT, TYPE are defined makes a submission invalid. |
80 | """ |
81 | - del self.udev_usb_device['E']['DEVTYPE'] |
82 | - parser = SubmissionParser(self.log) |
83 | - parser.submission_key = 'USB device without DEVTYPE property' |
84 | - self.assertFalse(parser.checkUdevUsbProperties( |
85 | - [self.udev_root_device, self.udev_usb_device])) |
86 | - self.assertErrorMessage( |
87 | - parser.submission_key, |
88 | - "USB udev device found without required properties: " |
89 | - "set(['DEVTYPE']) '/devices/pci0000:00/0000:00:1d.1/usb3/3-2'") |
90 | + for property_name in ('DEVTYPE', 'PRODUCT', 'TYPE'): |
91 | + saved_property = self.udev_usb_device['E'].pop(property_name) |
92 | + parser = SubmissionParser(self.log) |
93 | + parser.submission_key = ( |
94 | + 'USB device without %s property' % property_name) |
95 | + self.assertFalse(parser.checkUdevUsbProperties( |
96 | + [self.udev_root_device, self.udev_usb_device])) |
97 | + self.assertErrorMessage( |
98 | + parser.submission_key, |
99 | + "USB udev device found without required properties: " |
100 | + "set(['%s']) '/devices/pci0000:00/0000:00:1d.1/usb3/3-2'" |
101 | + % property_name) |
102 | + self.udev_usb_device['E'][property_name] = saved_property |
103 | |
104 | def testCheckUdevUsbProperties_with_invalid_product_id(self): |
105 | """Test of SubmissionParser.checkUdevUsbProperties(). |
106 | @@ -2102,7 +2117,7 @@ |
107 | |
108 | All shortcut methods return True. |
109 | """ |
110 | - def checkUdevDictsHavePathKey(self, udev_data): |
111 | + def checkUdevDictsHavePathKey(self, udev_nodes): |
112 | """See `SubmissionParser`.""" |
113 | return True |
114 | |
115 | @@ -2114,7 +2129,7 @@ |
116 | """See `SubmissionParser`.""" |
117 | return True |
118 | |
119 | - def checkUdevScsiProperties(self, udev_data, syfs_data): |
120 | + def checkUdevScsiProperties(self, udev_data, sysfs_data): |
121 | """See `SubmissionParser`.""" |
122 | return True |
123 | |
124 | @@ -2122,6 +2137,8 @@ |
125 | """See `SubmissionParser`.""" |
126 | return True |
127 | |
128 | + validate_mock_class(UdevTestSubmissionParser) |
129 | + |
130 | def testCheckConsistentUdevDeviceData(self): |
131 | """Test of SubmissionParser.checkConsistentUdevDeviceData(),""" |
132 | parser = self.UdevTestSubmissionParser() |
133 | @@ -2137,10 +2154,12 @@ |
134 | self.UdevTestSubmissionParser): |
135 | """A SubmissionPaser where checkUdevDictsHavePathKey() fails.""" |
136 | |
137 | - def checkUdevDictsHavePathKey(self, udev_data): |
138 | + def checkUdevDictsHavePathKey(self, udev_nodes): |
139 | """See `SubmissionParser`.""" |
140 | return False |
141 | |
142 | + validate_mock_class(SubmissionParserUdevPathCheckFails) |
143 | + |
144 | parser = SubmissionParserUdevPathCheckFails() |
145 | self.assertFalse(parser.checkConsistentUdevDeviceData( |
146 | None, None, None)) |
147 | @@ -2158,6 +2177,8 @@ |
148 | """See `SubmissionParser`.""" |
149 | return False |
150 | |
151 | + validate_mock_class(SubmissionParserUdevPciCheckFails) |
152 | + |
153 | parser = SubmissionParserUdevPciCheckFails() |
154 | self.assertFalse(parser.checkConsistentUdevDeviceData( |
155 | None, None, None)) |
156 | @@ -2175,6 +2196,8 @@ |
157 | """See `SubmissionParser`.""" |
158 | return False |
159 | |
160 | + validate_mock_class(SubmissionParserUdevUsbCheckFails) |
161 | + |
162 | parser = SubmissionParserUdevUsbCheckFails() |
163 | self.assertFalse(parser.checkConsistentUdevDeviceData( |
164 | None, None, None)) |
165 | @@ -2192,6 +2215,8 @@ |
166 | """See `SubmissionParser`.""" |
167 | return False |
168 | |
169 | + validate_mock_class(SubmissionParserUdevUsbCheckFails) |
170 | + |
171 | parser = SubmissionParserUdevUsbCheckFails() |
172 | self.assertFalse(parser.checkConsistentUdevDeviceData( |
173 | None, None, None)) |
174 | @@ -2209,55 +2234,38 @@ |
175 | """See `SubmissionParser`.""" |
176 | return False |
177 | |
178 | + validate_mock_class(SubmissionParserUdevUsbCheckFails) |
179 | + |
180 | parser = SubmissionParserUdevUsbCheckFails() |
181 | self.assertFalse(parser.checkConsistentUdevDeviceData( |
182 | None, None, None)) |
183 | |
184 | - def _setupConsistencyCheckParser(self): |
185 | - """Prepare and return a SubmissionParser instance. |
186 | + class MockSubmissionParser(SubmissionParser): |
187 | + """A SubmissionParser variant for testing checkCOnsistentData() |
188 | |
189 | All "method substitutes" return a valid result. |
190 | """ |
191 | - test = self |
192 | + |
193 | def findDuplicateIDs(self, parsed_data): |
194 | - test.assertTrue(isinstance(self, SubmissionParser)) |
195 | return set() |
196 | |
197 | def findInvalidIDReferences(self, parsed_data): |
198 | - test.assertTrue(isinstance(self, SubmissionParser)) |
199 | return set() |
200 | |
201 | def getUDIDeviceMap(self, devices): |
202 | - test.assertTrue(isinstance(self, SubmissionParser)) |
203 | return {} |
204 | |
205 | def getUDIChildren(self, udi_device_map): |
206 | - test.assertTrue(isinstance(self, SubmissionParser)) |
207 | return {} |
208 | |
209 | - def checkHALDevicesParentChildConsistency(self, devices): |
210 | - test.assertTrue(isinstance(self, SubmissionParser)) |
211 | + def checkHALDevicesParentChildConsistency(self, udi_children): |
212 | return [] |
213 | |
214 | - def checkConsistentUdevDeviceData(self, udev_data, sysfs_data): |
215 | + def checkConsistentUdevDeviceData( |
216 | + self, udev_data, sysfs_data, dmi_data): |
217 | return True |
218 | |
219 | - parser = SubmissionParser(self.log) |
220 | - parser.findDuplicateIDs = ( |
221 | - lambda parsed_data: findDuplicateIDs(parser, parsed_data)) |
222 | - parser.findInvalidIDReferences = ( |
223 | - lambda parsed_data: findInvalidIDReferences(parser, parsed_data)) |
224 | - parser.getUDIDeviceMap = ( |
225 | - lambda devices: getUDIDeviceMap(parser, devices)) |
226 | - parser.getUDIChildren = ( |
227 | - lambda udi_device_map: getUDIChildren(parser, udi_device_map)) |
228 | - parser.checkHALDevicesParentChildConsistency = ( |
229 | - lambda udi_children: checkHALDevicesParentChildConsistency( |
230 | - parser, udi_children)) |
231 | - parser.checkConsistentUdevDeviceData = ( |
232 | - lambda udev_data, sysfs_data: checkConsistentUdevDeviceData( |
233 | - parser, udev_data, sysfs_data)) |
234 | - return parser |
235 | + validate_mock_class(MockSubmissionParser) |
236 | |
237 | def assertErrorMessage(self, submission_key, log_message): |
238 | """Search for message in the log entries for submission_key. |
239 | @@ -2309,7 +2317,7 @@ |
240 | |
241 | def testConsistencyCheck(self): |
242 | """Test of SubmissionParser.checkConsistency.""" |
243 | - parser = self._setupConsistencyCheckParser() |
244 | + parser = self.MockSubmissionParser() |
245 | result = parser.checkConsistency({'hardware': |
246 | {'hal': {'devices': []}}}) |
247 | self.assertEqual(result, True, |
248 | @@ -2318,45 +2326,53 @@ |
249 | |
250 | def testConsistencyCheckValidUdevData(self): |
251 | """Test of SubmissionParser.checkConsistency.""" |
252 | - parser = self._setupConsistencyCheckParser() |
253 | + parser = self.MockSubmissionParser() |
254 | self.assertTrue(parser.checkConsistency( |
255 | { |
256 | 'hardware': { |
257 | - 'udev': [], |
258 | - 'sysfs-attributes': [] |
259 | + 'udev': None, |
260 | + 'sysfs-attributes': None, |
261 | + 'dmi': None, |
262 | } |
263 | } |
264 | )) |
265 | |
266 | def testConsistencyCheck_invalid_udev_data(self): |
267 | """Test of SubmissionParser.checkConsistency.""" |
268 | - def checkConsistentUdevDeviceData(self, udev_data, sysfs_data): |
269 | - return False |
270 | - |
271 | - parser = self._setupConsistencyCheckParser() |
272 | - parser.checkConsistentUdevDeviceData = ( |
273 | - lambda udev_data, sysfs_data: checkConsistentUdevDeviceData( |
274 | - parser, udev_data, sysfs_data)) |
275 | + class MockSubmissionParserBadUdevDeviceData( |
276 | + self.MockSubmissionParser): |
277 | + """A parser where checkConsistentUdevDeviceData() fails.""" |
278 | + |
279 | + def checkConsistentUdevDeviceData(self, udev_data, sysfs_data, |
280 | + dmi_data): |
281 | + return False |
282 | + |
283 | + validate_mock_class(MockSubmissionParserBadUdevDeviceData) |
284 | + |
285 | + parser = MockSubmissionParserBadUdevDeviceData() |
286 | self.assertFalse(parser.checkConsistency( |
287 | { |
288 | 'hardware': { |
289 | - 'udev': [{}], |
290 | - 'sysfs-attributes': [] |
291 | + 'udev': None, |
292 | + 'sysfs-attributes': None, |
293 | + 'dmi': None, |
294 | } |
295 | } |
296 | )) |
297 | |
298 | def testConsistencyCheckWithDuplicateIDs(self): |
299 | """SubmissionParser.checkConsistency detects duplicate IDs.""" |
300 | - test = self |
301 | - def findDuplicateIDs(self, parsed_data): |
302 | - test.assertTrue(isinstance(self, SubmissionParser)) |
303 | - return set([1]) |
304 | - |
305 | - parser = self._setupConsistencyCheckParser() |
306 | + class MockSubmissionParserDuplicateIds( |
307 | + self.MockSubmissionParser): |
308 | + """A parser where findDuplicateIDs() fails.""" |
309 | + |
310 | + def findDuplicateIDs(self, parsed_data): |
311 | + return set([1]) |
312 | + |
313 | + validate_mock_class(MockSubmissionParserDuplicateIds) |
314 | + |
315 | + parser = MockSubmissionParserDuplicateIds(self.log) |
316 | parser.submission_key = 'Consistency check detects duplicate IDs' |
317 | - parser.findDuplicateIDs = ( |
318 | - lambda parsed_data: findDuplicateIDs(parser, parsed_data)) |
319 | result = parser.checkConsistency({'hardware': |
320 | {'hal': {'devices': []}}}) |
321 | self.assertEqual(result, False, |
322 | @@ -2366,15 +2382,16 @@ |
323 | |
324 | def testConsistencyCheckWithInvalidIDReferences(self): |
325 | """SubmissionParser.checkConsistency detects invalid ID references.""" |
326 | - test = self |
327 | - def findInvalidIDReferences(self, parsed_data): |
328 | - test.assertTrue(isinstance(self, SubmissionParser)) |
329 | - return set([1]) |
330 | - |
331 | - parser = self._setupConsistencyCheckParser() |
332 | + class MockSubmissionParserInvalidIDReferences( |
333 | + self.MockSubmissionParser): |
334 | + """A parser where findInvalidIDReferences() fails.""" |
335 | + def findInvalidIDReferences(self, parsed_data): |
336 | + return set([1]) |
337 | + |
338 | + validate_mock_class(MockSubmissionParserInvalidIDReferences) |
339 | + |
340 | + parser = MockSubmissionParserInvalidIDReferences(self.log) |
341 | parser.submission_key = 'Consistency check detects invalid ID refs' |
342 | - parser.findInvalidIDReferences = ( |
343 | - lambda parsed_data: findInvalidIDReferences(parser, parsed_data)) |
344 | result = parser.checkConsistency({'hardware': |
345 | {'hal': {'devices': []}}}) |
346 | self.assertEqual(result, False, |
347 | @@ -2384,16 +2401,18 @@ |
348 | |
349 | def testConsistencyCheckWithDuplicateUDI(self): |
350 | """SubmissionParser.checkConsistency detects duplicate UDIs.""" |
351 | - test = self |
352 | - def getUDIDeviceMap(self, parsed_data): |
353 | - test.assertTrue(isinstance(self, SubmissionParser)) |
354 | - raise ValueError( |
355 | - 'Duplicate UDI: /org/freedesktop/Hal/devices/computer') |
356 | - |
357 | - parser = self._setupConsistencyCheckParser() |
358 | + class MockSubmissionParserUDIDeviceMapFails( |
359 | + self.MockSubmissionParser): |
360 | + """A parser where getUDIDeviceMap() fails.""" |
361 | + |
362 | + def getUDIDeviceMap(self, devices): |
363 | + raise ValueError( |
364 | + 'Duplicate UDI: /org/freedesktop/Hal/devices/computer') |
365 | + |
366 | + validate_mock_class(MockSubmissionParserUDIDeviceMapFails) |
367 | + |
368 | + parser = MockSubmissionParserUDIDeviceMapFails(self.log) |
369 | parser.submission_key = 'Consistency check detects invalid ID refs' |
370 | - parser.getUDIDeviceMap = ( |
371 | - lambda devices: getUDIDeviceMap(parser, devices)) |
372 | result = parser.checkConsistency({'hardware': |
373 | {'hal': {'devices': []}}}) |
374 | self.assertEqual(result, False, |
375 | @@ -2404,15 +2423,17 @@ |
376 | |
377 | def testConsistencyCheckChildUDIWithoutParent(self): |
378 | """SubmissionParser.checkConsistency detects "orphaned" devices.""" |
379 | - test = self |
380 | - def getUDIChildren(self, udi_device_map): |
381 | - test.assertTrue(isinstance(self, SubmissionParser)) |
382 | - raise ValueError('Unknown parent UDI /foo in <device id="3">') |
383 | - |
384 | - parser = self._setupConsistencyCheckParser() |
385 | + class MockSubmissionParserUDIChildrenFails( |
386 | + self.MockSubmissionParser): |
387 | + """A parser where getUDIChildren() fails.""" |
388 | + |
389 | + def getUDIChildren(self, udi_device_map): |
390 | + raise ValueError('Unknown parent UDI /foo in <device id="3">') |
391 | + |
392 | + validate_mock_class(MockSubmissionParserUDIChildrenFails) |
393 | + |
394 | + parser = MockSubmissionParserUDIChildrenFails(self.log) |
395 | parser.submission_key = 'Consistency check detects invalid ID refs' |
396 | - parser.getUDIChildren = ( |
397 | - lambda udi_device_map: getUDIChildren(parser, udi_device_map)) |
398 | result = parser.checkConsistency({'hardware': |
399 | {'hal': {'devices': []}}}) |
400 | self.assertEqual(result, False, |
401 | @@ -2423,17 +2444,21 @@ |
402 | |
403 | def testConsistencyCheckCircularParentChildRelation(self): |
404 | """SubmissionParser.checkConsistency detects "orphaned" devices.""" |
405 | - test = self |
406 | - def checkHALDevicesParentChildConsistency(self, devices): |
407 | - test.assertTrue(isinstance(self, SubmissionParser)) |
408 | - return ['/foo', '/bar'] |
409 | - |
410 | - parser = self._setupConsistencyCheckParser() |
411 | + class MockSubmissionParserHALDevicesParentChildConsistency( |
412 | + self.MockSubmissionParser): |
413 | + """A parser where checkHALDevicesParentChildConsistency() fails. |
414 | + """ |
415 | + |
416 | + def checkHALDevicesParentChildConsistency(self, udi_children): |
417 | + return ['/foo', '/bar'] |
418 | + |
419 | + validate_mock_class( |
420 | + MockSubmissionParserHALDevicesParentChildConsistency) |
421 | + |
422 | + parser = MockSubmissionParserHALDevicesParentChildConsistency( |
423 | + self.log) |
424 | parser.submission_key = ('Consistency check detects circular ' |
425 | 'parent-child relationships') |
426 | - parser.checkHALDevicesParentChildConsistency = ( |
427 | - lambda devices: checkHALDevicesParentChildConsistency( |
428 | - parser, devices)) |
429 | result = parser.checkConsistency({'hardware': |
430 | {'hal': {'devices': []}}}) |
431 | self.assertEqual(result, False, |
432 | |
433 | === modified file 'lib/lp/testing/__init__.py' |
434 | --- lib/lp/testing/__init__.py 2009-10-23 11:07:32 +0000 |
435 | +++ lib/lp/testing/__init__.py 2009-10-26 10:45:33 +0000 |
436 | @@ -8,6 +8,7 @@ |
437 | from datetime import datetime, timedelta |
438 | from pprint import pformat |
439 | import copy |
440 | +from inspect import getargspec, getmembers, getmro, isclass, ismethod |
441 | import os |
442 | import shutil |
443 | import subprocess |
444 | @@ -793,3 +794,69 @@ |
445 | tree.unlock() |
446 | |
447 | return contents |
448 | + |
449 | +def validate_mock_class(mock_class): |
450 | + """Validate method signatures in mock classes derived from real classes. |
451 | + |
452 | + We often use mock classes in tests which are derived from real |
453 | + classes. |
454 | + |
455 | + This function ensures that methods redefined in the mock |
456 | + class have the same signature as the corresponding methods of |
457 | + the base class. |
458 | + |
459 | + >>> class A: |
460 | + ... |
461 | + ... def method_one(self, a): |
462 | + ... pass |
463 | + |
464 | + >>> |
465 | + >>> class B(A): |
466 | + ... def method_one(self, a): |
467 | + ... pass |
468 | + >>> validate_mock_class(B) |
469 | + |
470 | + If a class derived from A defines method_one with a different |
471 | + signature, we get an AssertionError. |
472 | + |
473 | + >>> class C(A): |
474 | + ... def method_one(self, a, b): |
475 | + ... pass |
476 | + >>> validate_mock_class(C) |
477 | + Traceback (most recent call last): |
478 | + ... |
479 | + AssertionError: Different method signature for method_one:... |
480 | + |
481 | + Even a parameter name must not be modified. |
482 | + |
483 | + >>> class D(A): |
484 | + ... def method_one(self, b): |
485 | + ... pass |
486 | + >>> validate_mock_class(D) |
487 | + Traceback (most recent call last): |
488 | + ... |
489 | + AssertionError: Different method signature for method_one:... |
490 | + |
491 | + If validate_mock_class() for anything but a class, we get an |
492 | + AssertionError. |
493 | + |
494 | + >>> validate_mock_class('a string') |
495 | + Traceback (most recent call last): |
496 | + ... |
497 | + AssertionError: validate_mock_class() must be called for a class |
498 | + """ |
499 | + assert isclass(mock_class), ( |
500 | + "validate_mock_class() must be called for a class") |
501 | + base_classes = getmro(mock_class) |
502 | + for name, obj in getmembers(mock_class): |
503 | + if ismethod(obj): |
504 | + for base_class in base_classes[1:]: |
505 | + if name in base_class.__dict__: |
506 | + mock_args = getargspec(obj) |
507 | + real_args = getargspec(base_class.__dict__[name]) |
508 | + if mock_args != real_args: |
509 | + raise AssertionError( |
510 | + 'Different method signature for %s: %r %r' % ( |
511 | + name, mock_args, real_args)) |
512 | + else: |
513 | + break |
514 | |
515 | === added file 'lib/lp/testing/tests/test_inlinetests.py' |
516 | --- lib/lp/testing/tests/test_inlinetests.py 1970-01-01 00:00:00 +0000 |
517 | +++ lib/lp/testing/tests/test_inlinetests.py 2009-10-26 10:45:33 +0000 |
518 | @@ -0,0 +1,20 @@ |
519 | +# Copyright 2009 Canonical Ltd. This software is licensed under the |
520 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
521 | + |
522 | +"""Run the doc string tests.""" |
523 | + |
524 | +import doctest |
525 | + |
526 | +from zope.testing.doctest import NORMALIZE_WHITESPACE, ELLIPSIS |
527 | + |
528 | +from canonical.launchpad.testing.systemdocs import LayeredDocFileSuite |
529 | +from canonical.testing import BaseLayer |
530 | +from lp import testing |
531 | + |
532 | +def test_suite(): |
533 | + suite = LayeredDocFileSuite( |
534 | + layer=BaseLayer) |
535 | + suite.addTest(doctest.DocTestSuite( |
536 | + testing, optionflags=NORMALIZE_WHITESPACE|ELLIPSIS)) |
537 | + return suite |
538 | + |
When a HWDB submission is processed, many sanity checks are run. One of them is to see if a udev node where the property SUBSYSTEM is 'usb' also provides the properties DEVTYPE, PRODUCT and TYPE. (SUBSYSTEM tells us the "coarse" type of a udev node for "kernel viewpoint"; DEVTYPE, if defined, specifies a more fine-grained type; PRODUCT contains the vendor and product ID of a USB device; TYPE tell us the "class" of a USB device: printer/sound/input etc.
The udev data is quite fine-grained, especially it contains for many devices more than one node, where each node describes a different "aspect" of a physical device.
Writing the method SubmissionPasre sr.checkUdevUsb Proerties( ), I assumed that each udev node where the property SUBSYSTEM is 'usb' also defined the properties DEVTYPE, PRODUCT and TYPE. Running the script for a few hundred real-world HWDB submissions, it turned out that this assumption is wrong: One sub-node of USB printers and USB input devices does not have these three properties, so I relaxed the sanity check: Each USB related node must now either provide all three properties or none of them.
test: ./bin/test --test= test_hwdb_ submission_ parser
= Launchpad lint =
Checking for conflicts. and issues in doctests and templates.
Running jslint, xmllint, pyflakes, and pylint.
Using normal rules.
Linting changed files: /launchpad/ scripts/ hwdbsubmissions .py /launchpad/ scripts/ tests/test_ hwdb_submission _parser. py
lib/canonical
lib/canonical
== Pyflakes notices ==
lib/canonical/ launchpad/ scripts/ hwdbsubmissions .py
22: redefinition of unused 'etree' from line 20
lib/canonical/ launchpad/ scripts/ tests/test_ hwdb_submission _parser. py
10: redefinition of unused 'etree' from line 8
== Pylint notices ==
lib/canonical/ launchpad/ scripts/ hwdbsubmissions .py cElementTree' (No module named etree)
20: [F0401] Unable to import 'xml.etree.
lib/canonical/ launchpad/ scripts/ tests/test_ hwdb_submission _parser. py cElementTree' (No module named etree)
8: [F0401] Unable to import 'xml.etree.
The etree complaint is not related to my changes.