Merge lp:~adeuring/launchpad/bug-460935-hwdb-better-consistency-check-udev-usb-devices into lp:launchpad/db-devel

Proposed by Abel Deuring
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
Reviewer Review Type Date Requested Status
Eleanor Berger (community) code Approve
Review via email: mp+13942@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Abel Deuring (adeuring) wrote :

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 SubmissionPasresr.checkUdevUsbProerties(), 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:
  lib/canonical/launchpad/scripts/hwdbsubmissions.py
  lib/canonical/launchpad/scripts/tests/test_hwdb_submission_parser.py

== 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
    20: [F0401] Unable to import 'xml.etree.cElementTree' (No module named etree)

lib/canonical/launchpad/scripts/tests/test_hwdb_submission_parser.py
    8: [F0401] Unable to import 'xml.etree.cElementTree' (No module named etree)

The etree complaint is not related to my changes.

Revision history for this message
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/launchpad/scripts/tests/test_hwdb_submission_parser.py 2009-10-23 08:02:11 +0000
+++ lib/canonical/launchpad/scripts/tests/test_hwdb_submission_parser.py 2009-10-26 09:59:21 +0000
@@ -1929,27 +1929,41 @@
             "'/devices/pci0000:00/0000:00:1f.2'")

     def testCheckUdevUsbProperties(self):
- """Test of SubmissionParser.checkUdevUsbProperties()."""
+ """Test of SubmissionParser.checkUdevUsbProperties().
+
+ udev nodes for USB devices must define the three properties
+ DEVTYPE, PRODUCT, TYPE or none of them.
+ """
         parser = SubmissionParser()
         self.assertTrue(parser.checkUdevUsbProperties(
             [self.udev_root_device, self.udev_usb_device,
              self.udev_usb_interface]))

+ for property_name in ('DEVTYPE', 'PRODUCT', 'TYPE'):
+ del self.udev_usb_device['E'][property_name]
+ self.assertTrue(parser.checkUdevUsbProperties(
+ [self.udev_root_device, self.udev_usb_device,
+ self.udev_usb_interface]))
+
     def testCheckUdevUsbProperties_missing_required_property(self):
         """Test of SubmissionParser.checkUdevUsbProperties().

- 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_usb_device['E']['DEVTYPE']
- parser = SubmissionParser(self.log)
- parser.submission_key = 'USB device without DEVTYPE property'
- self.assertFalse(parser.checkUdevUsbProperties(
- [self.udev_root_device, self.udev_usb_device]))
- self.assertErrorMessage(
- parser.submission_key,
- "USB udev device found without required properties: "
- "set(['DEVTYPE']) '/devices/pci0000:00/0000:00:1d.1/usb3/3-2'")
+ for property_name in ('DEVTYPE', 'PRODUCT', 'TYPE'):
+ saved_property = self.udev_usb_device['E'].pop(property_name)
+ parser = SubmissionParser(self.log)
+ parser.submission_key = (
+ 'USB device without %s property' % property_name)
+ self.assertFalse(parser.checkUdevUsbProperties(
+ [self.udev_root_device, self.udev_usb_device]))
+ self.assertErrorMessage(
+ parser.submission_key,
+ "USB udev device found without required properties: "
+ "set(['%s']) '/devices/pci0000:00/0000:00:1d.1/usb3/3-2'"
+ % property_name)
+ self.udev_usb_device['E'][property_name] = saved_property

     def testCheckUdevUsbProperties_with_invalid_product_id(self):
         """Test of SubmissionParser.checkUdevUsbProperties().

Revision history for this message
Eleanor Berger (intellectronica) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/canonical/launchpad/scripts/hwdbsubmissions.py'
--- lib/canonical/launchpad/scripts/hwdbsubmissions.py 2009-10-22 11:08:18 +0000
+++ lib/canonical/launchpad/scripts/hwdbsubmissions.py 2009-10-26 10:45:33 +0000
@@ -1228,9 +1228,11 @@
1228 def checkUdevUsbProperties(self, udev_data):1228 def checkUdevUsbProperties(self, udev_data):
1229 """Validation of udev USB devices.1229 """Validation of udev USB devices.
12301230
1231 USB devices must have the properties DEVTYPE (value1231 USB devices must either have the three properties DEVTYPE
1232 'usb_device' or 'usb_interface'), PRODUCT and TYPE. PRODUCT1232 (value 'usb_device' or 'usb_interface'), PRODUCT and TYPE,
1233 must be a tuple of three integers in hexadecimal1233 or they must have none of them.
1234
1235 PRODUCT must be a tuple of three integers in hexadecimal
1234 representation, separates by '/'. TYPE must be a a tuple of1236 representation, separates by '/'. TYPE must be a a tuple of
1235 three integers in decimal representation, separated by '/'.1237 three integers in decimal representation, separated by '/'.
1236 usb_interface nodes must additionally have a property1238 usb_interface nodes must additionally have a property
@@ -1245,6 +1247,10 @@
1245 property_names = set(properties)1247 property_names = set(properties)
1246 existing_usb_properties = property_names.intersection(1248 existing_usb_properties = property_names.intersection(
1247 UDEV_USB_DEVICE_PROPERTIES)1249 UDEV_USB_DEVICE_PROPERTIES)
1250
1251 if len(existing_usb_properties) == 0:
1252 continue
1253
1248 if existing_usb_properties != UDEV_USB_DEVICE_PROPERTIES:1254 if existing_usb_properties != UDEV_USB_DEVICE_PROPERTIES:
1249 missing_properties = UDEV_USB_DEVICE_PROPERTIES.difference(1255 missing_properties = UDEV_USB_DEVICE_PROPERTIES.difference(
1250 existing_usb_properties)1256 existing_usb_properties)
@@ -1365,7 +1371,8 @@
1365 if ('udev' in parsed_data['hardware']1371 if ('udev' in parsed_data['hardware']
1366 and not self.checkConsistentUdevDeviceData(1372 and not self.checkConsistentUdevDeviceData(
1367 parsed_data['hardware']['udev'],1373 parsed_data['hardware']['udev'],
1368 parsed_data['hardware']['sysfs-attributes'])):1374 parsed_data['hardware']['sysfs-attributes'],
1375 parsed_data['hardware']['dmi'],)):
1369 return False1376 return False
1370 duplicate_ids = self.findDuplicateIDs(parsed_data)1377 duplicate_ids = self.findDuplicateIDs(parsed_data)
1371 if duplicate_ids:1378 if duplicate_ids:
13721379
=== modified file 'lib/canonical/launchpad/scripts/tests/test_hwdb_submission_parser.py'
--- lib/canonical/launchpad/scripts/tests/test_hwdb_submission_parser.py 2009-10-21 16:49:03 +0000
+++ lib/canonical/launchpad/scripts/tests/test_hwdb_submission_parser.py 2009-10-26 10:45:33 +0000
@@ -23,6 +23,7 @@
23 ROOT_UDI)23 ROOT_UDI)
24from canonical.testing import BaseLayer24from canonical.testing import BaseLayer
2525
26from lp.testing import validate_mock_class
2627
27class SubmissionParserTestParseSoftware(SubmissionParser):28class SubmissionParserTestParseSoftware(SubmissionParser):
28 """A Variant used to test SubmissionParser._parseSoftware.29 """A Variant used to test SubmissionParser._parseSoftware.
@@ -1928,27 +1929,41 @@
1928 "'/devices/pci0000:00/0000:00:1f.2'")1929 "'/devices/pci0000:00/0000:00:1f.2'")
19291930
1930 def testCheckUdevUsbProperties(self):1931 def testCheckUdevUsbProperties(self):
1931 """Test of SubmissionParser.checkUdevUsbProperties()."""1932 """Test of SubmissionParser.checkUdevUsbProperties().
1933
1934 udev nodes for USB devices must define the three properties
1935 DEVTYPE, PRODUCT, TYPE or none of them.
1936 """
1932 parser = SubmissionParser()1937 parser = SubmissionParser()
1933 self.assertTrue(parser.checkUdevUsbProperties(1938 self.assertTrue(parser.checkUdevUsbProperties(
1934 [self.udev_root_device, self.udev_usb_device,1939 [self.udev_root_device, self.udev_usb_device,
1935 self.udev_usb_interface]))1940 self.udev_usb_interface]))
19361941
1942 for property_name in ('DEVTYPE', 'PRODUCT', 'TYPE'):
1943 del self.udev_usb_device['E'][property_name]
1944 self.assertTrue(parser.checkUdevUsbProperties(
1945 [self.udev_root_device, self.udev_usb_device,
1946 self.udev_usb_interface]))
1947
1937 def testCheckUdevUsbProperties_missing_required_property(self):1948 def testCheckUdevUsbProperties_missing_required_property(self):
1938 """Test of SubmissionParser.checkUdevUsbProperties().1949 """Test of SubmissionParser.checkUdevUsbProperties().
19391950
1940 A USB device that does not have a required property makes a1951 A USB device where some but not all of the properties DEVTYPE,
1941 submission invalid.1952 PRODUCT, TYPE are defined makes a submission invalid.
1942 """1953 """
1943 del self.udev_usb_device['E']['DEVTYPE']1954 for property_name in ('DEVTYPE', 'PRODUCT', 'TYPE'):
1944 parser = SubmissionParser(self.log)1955 saved_property = self.udev_usb_device['E'].pop(property_name)
1945 parser.submission_key = 'USB device without DEVTYPE property'1956 parser = SubmissionParser(self.log)
1946 self.assertFalse(parser.checkUdevUsbProperties(1957 parser.submission_key = (
1947 [self.udev_root_device, self.udev_usb_device]))1958 'USB device without %s property' % property_name)
1948 self.assertErrorMessage(1959 self.assertFalse(parser.checkUdevUsbProperties(
1949 parser.submission_key,1960 [self.udev_root_device, self.udev_usb_device]))
1950 "USB udev device found without required properties: "1961 self.assertErrorMessage(
1951 "set(['DEVTYPE']) '/devices/pci0000:00/0000:00:1d.1/usb3/3-2'")1962 parser.submission_key,
1963 "USB udev device found without required properties: "
1964 "set(['%s']) '/devices/pci0000:00/0000:00:1d.1/usb3/3-2'"
1965 % property_name)
1966 self.udev_usb_device['E'][property_name] = saved_property
19521967
1953 def testCheckUdevUsbProperties_with_invalid_product_id(self):1968 def testCheckUdevUsbProperties_with_invalid_product_id(self):
1954 """Test of SubmissionParser.checkUdevUsbProperties().1969 """Test of SubmissionParser.checkUdevUsbProperties().
@@ -2102,7 +2117,7 @@
21022117
2103 All shortcut methods return True.2118 All shortcut methods return True.
2104 """2119 """
2105 def checkUdevDictsHavePathKey(self, udev_data):2120 def checkUdevDictsHavePathKey(self, udev_nodes):
2106 """See `SubmissionParser`."""2121 """See `SubmissionParser`."""
2107 return True2122 return True
21082123
@@ -2114,7 +2129,7 @@
2114 """See `SubmissionParser`."""2129 """See `SubmissionParser`."""
2115 return True2130 return True
21162131
2117 def checkUdevScsiProperties(self, udev_data, syfs_data):2132 def checkUdevScsiProperties(self, udev_data, sysfs_data):
2118 """See `SubmissionParser`."""2133 """See `SubmissionParser`."""
2119 return True2134 return True
21202135
@@ -2122,6 +2137,8 @@
2122 """See `SubmissionParser`."""2137 """See `SubmissionParser`."""
2123 return True2138 return True
21242139
2140 validate_mock_class(UdevTestSubmissionParser)
2141
2125 def testCheckConsistentUdevDeviceData(self):2142 def testCheckConsistentUdevDeviceData(self):
2126 """Test of SubmissionParser.checkConsistentUdevDeviceData(),"""2143 """Test of SubmissionParser.checkConsistentUdevDeviceData(),"""
2127 parser = self.UdevTestSubmissionParser()2144 parser = self.UdevTestSubmissionParser()
@@ -2137,10 +2154,12 @@
2137 self.UdevTestSubmissionParser):2154 self.UdevTestSubmissionParser):
2138 """A SubmissionPaser where checkUdevDictsHavePathKey() fails."""2155 """A SubmissionPaser where checkUdevDictsHavePathKey() fails."""
21392156
2140 def checkUdevDictsHavePathKey(self, udev_data):2157 def checkUdevDictsHavePathKey(self, udev_nodes):
2141 """See `SubmissionParser`."""2158 """See `SubmissionParser`."""
2142 return False2159 return False
21432160
2161 validate_mock_class(SubmissionParserUdevPathCheckFails)
2162
2144 parser = SubmissionParserUdevPathCheckFails()2163 parser = SubmissionParserUdevPathCheckFails()
2145 self.assertFalse(parser.checkConsistentUdevDeviceData(2164 self.assertFalse(parser.checkConsistentUdevDeviceData(
2146 None, None, None))2165 None, None, None))
@@ -2158,6 +2177,8 @@
2158 """See `SubmissionParser`."""2177 """See `SubmissionParser`."""
2159 return False2178 return False
21602179
2180 validate_mock_class(SubmissionParserUdevPciCheckFails)
2181
2161 parser = SubmissionParserUdevPciCheckFails()2182 parser = SubmissionParserUdevPciCheckFails()
2162 self.assertFalse(parser.checkConsistentUdevDeviceData(2183 self.assertFalse(parser.checkConsistentUdevDeviceData(
2163 None, None, None))2184 None, None, None))
@@ -2175,6 +2196,8 @@
2175 """See `SubmissionParser`."""2196 """See `SubmissionParser`."""
2176 return False2197 return False
21772198
2199 validate_mock_class(SubmissionParserUdevUsbCheckFails)
2200
2178 parser = SubmissionParserUdevUsbCheckFails()2201 parser = SubmissionParserUdevUsbCheckFails()
2179 self.assertFalse(parser.checkConsistentUdevDeviceData(2202 self.assertFalse(parser.checkConsistentUdevDeviceData(
2180 None, None, None))2203 None, None, None))
@@ -2192,6 +2215,8 @@
2192 """See `SubmissionParser`."""2215 """See `SubmissionParser`."""
2193 return False2216 return False
21942217
2218 validate_mock_class(SubmissionParserUdevUsbCheckFails)
2219
2195 parser = SubmissionParserUdevUsbCheckFails()2220 parser = SubmissionParserUdevUsbCheckFails()
2196 self.assertFalse(parser.checkConsistentUdevDeviceData(2221 self.assertFalse(parser.checkConsistentUdevDeviceData(
2197 None, None, None))2222 None, None, None))
@@ -2209,55 +2234,38 @@
2209 """See `SubmissionParser`."""2234 """See `SubmissionParser`."""
2210 return False2235 return False
22112236
2237 validate_mock_class(SubmissionParserUdevUsbCheckFails)
2238
2212 parser = SubmissionParserUdevUsbCheckFails()2239 parser = SubmissionParserUdevUsbCheckFails()
2213 self.assertFalse(parser.checkConsistentUdevDeviceData(2240 self.assertFalse(parser.checkConsistentUdevDeviceData(
2214 None, None, None))2241 None, None, None))
22152242
2216 def _setupConsistencyCheckParser(self):2243 class MockSubmissionParser(SubmissionParser):
2217 """Prepare and return a SubmissionParser instance.2244 """A SubmissionParser variant for testing checkCOnsistentData()
22182245
2219 All "method substitutes" return a valid result.2246 All "method substitutes" return a valid result.
2220 """2247 """
2221 test = self2248
2222 def findDuplicateIDs(self, parsed_data):2249 def findDuplicateIDs(self, parsed_data):
2223 test.assertTrue(isinstance(self, SubmissionParser))
2224 return set()2250 return set()
22252251
2226 def findInvalidIDReferences(self, parsed_data):2252 def findInvalidIDReferences(self, parsed_data):
2227 test.assertTrue(isinstance(self, SubmissionParser))
2228 return set()2253 return set()
22292254
2230 def getUDIDeviceMap(self, devices):2255 def getUDIDeviceMap(self, devices):
2231 test.assertTrue(isinstance(self, SubmissionParser))
2232 return {}2256 return {}
22332257
2234 def getUDIChildren(self, udi_device_map):2258 def getUDIChildren(self, udi_device_map):
2235 test.assertTrue(isinstance(self, SubmissionParser))
2236 return {}2259 return {}
22372260
2238 def checkHALDevicesParentChildConsistency(self, devices):2261 def checkHALDevicesParentChildConsistency(self, udi_children):
2239 test.assertTrue(isinstance(self, SubmissionParser))
2240 return []2262 return []
22412263
2242 def checkConsistentUdevDeviceData(self, udev_data, sysfs_data):2264 def checkConsistentUdevDeviceData(
2265 self, udev_data, sysfs_data, dmi_data):
2243 return True2266 return True
22442267
2245 parser = SubmissionParser(self.log)2268 validate_mock_class(MockSubmissionParser)
2246 parser.findDuplicateIDs = (
2247 lambda parsed_data: findDuplicateIDs(parser, parsed_data))
2248 parser.findInvalidIDReferences = (
2249 lambda parsed_data: findInvalidIDReferences(parser, parsed_data))
2250 parser.getUDIDeviceMap = (
2251 lambda devices: getUDIDeviceMap(parser, devices))
2252 parser.getUDIChildren = (
2253 lambda udi_device_map: getUDIChildren(parser, udi_device_map))
2254 parser.checkHALDevicesParentChildConsistency = (
2255 lambda udi_children: checkHALDevicesParentChildConsistency(
2256 parser, udi_children))
2257 parser.checkConsistentUdevDeviceData = (
2258 lambda udev_data, sysfs_data: checkConsistentUdevDeviceData(
2259 parser, udev_data, sysfs_data))
2260 return parser
22612269
2262 def assertErrorMessage(self, submission_key, log_message):2270 def assertErrorMessage(self, submission_key, log_message):
2263 """Search for message in the log entries for submission_key.2271 """Search for message in the log entries for submission_key.
@@ -2309,7 +2317,7 @@
23092317
2310 def testConsistencyCheck(self):2318 def testConsistencyCheck(self):
2311 """Test of SubmissionParser.checkConsistency."""2319 """Test of SubmissionParser.checkConsistency."""
2312 parser = self._setupConsistencyCheckParser()2320 parser = self.MockSubmissionParser()
2313 result = parser.checkConsistency({'hardware':2321 result = parser.checkConsistency({'hardware':
2314 {'hal': {'devices': []}}})2322 {'hal': {'devices': []}}})
2315 self.assertEqual(result, True,2323 self.assertEqual(result, True,
@@ -2318,45 +2326,53 @@
23182326
2319 def testConsistencyCheckValidUdevData(self):2327 def testConsistencyCheckValidUdevData(self):
2320 """Test of SubmissionParser.checkConsistency."""2328 """Test of SubmissionParser.checkConsistency."""
2321 parser = self._setupConsistencyCheckParser()2329 parser = self.MockSubmissionParser()
2322 self.assertTrue(parser.checkConsistency(2330 self.assertTrue(parser.checkConsistency(
2323 {2331 {
2324 'hardware': {2332 'hardware': {
2325 'udev': [],2333 'udev': None,
2326 'sysfs-attributes': []2334 'sysfs-attributes': None,
2335 'dmi': None,
2327 }2336 }
2328 }2337 }
2329 ))2338 ))
23302339
2331 def testConsistencyCheck_invalid_udev_data(self):2340 def testConsistencyCheck_invalid_udev_data(self):
2332 """Test of SubmissionParser.checkConsistency."""2341 """Test of SubmissionParser.checkConsistency."""
2333 def checkConsistentUdevDeviceData(self, udev_data, sysfs_data):2342 class MockSubmissionParserBadUdevDeviceData(
2334 return False2343 self.MockSubmissionParser):
23352344 """A parser where checkConsistentUdevDeviceData() fails."""
2336 parser = self._setupConsistencyCheckParser()2345
2337 parser.checkConsistentUdevDeviceData = (2346 def checkConsistentUdevDeviceData(self, udev_data, sysfs_data,
2338 lambda udev_data, sysfs_data: checkConsistentUdevDeviceData(2347 dmi_data):
2339 parser, udev_data, sysfs_data))2348 return False
2349
2350 validate_mock_class(MockSubmissionParserBadUdevDeviceData)
2351
2352 parser = MockSubmissionParserBadUdevDeviceData()
2340 self.assertFalse(parser.checkConsistency(2353 self.assertFalse(parser.checkConsistency(
2341 {2354 {
2342 'hardware': {2355 'hardware': {
2343 'udev': [{}],2356 'udev': None,
2344 'sysfs-attributes': []2357 'sysfs-attributes': None,
2358 'dmi': None,
2345 }2359 }
2346 }2360 }
2347 ))2361 ))
23482362
2349 def testConsistencyCheckWithDuplicateIDs(self):2363 def testConsistencyCheckWithDuplicateIDs(self):
2350 """SubmissionParser.checkConsistency detects duplicate IDs."""2364 """SubmissionParser.checkConsistency detects duplicate IDs."""
2351 test = self2365 class MockSubmissionParserDuplicateIds(
2352 def findDuplicateIDs(self, parsed_data):2366 self.MockSubmissionParser):
2353 test.assertTrue(isinstance(self, SubmissionParser))2367 """A parser where findDuplicateIDs() fails."""
2354 return set([1])2368
23552369 def findDuplicateIDs(self, parsed_data):
2356 parser = self._setupConsistencyCheckParser()2370 return set([1])
2371
2372 validate_mock_class(MockSubmissionParserDuplicateIds)
2373
2374 parser = MockSubmissionParserDuplicateIds(self.log)
2357 parser.submission_key = 'Consistency check detects duplicate IDs'2375 parser.submission_key = 'Consistency check detects duplicate IDs'
2358 parser.findDuplicateIDs = (
2359 lambda parsed_data: findDuplicateIDs(parser, parsed_data))
2360 result = parser.checkConsistency({'hardware':2376 result = parser.checkConsistency({'hardware':
2361 {'hal': {'devices': []}}})2377 {'hal': {'devices': []}}})
2362 self.assertEqual(result, False,2378 self.assertEqual(result, False,
@@ -2366,15 +2382,16 @@
23662382
2367 def testConsistencyCheckWithInvalidIDReferences(self):2383 def testConsistencyCheckWithInvalidIDReferences(self):
2368 """SubmissionParser.checkConsistency detects invalid ID references."""2384 """SubmissionParser.checkConsistency detects invalid ID references."""
2369 test = self2385 class MockSubmissionParserInvalidIDReferences(
2370 def findInvalidIDReferences(self, parsed_data):2386 self.MockSubmissionParser):
2371 test.assertTrue(isinstance(self, SubmissionParser))2387 """A parser where findInvalidIDReferences() fails."""
2372 return set([1])2388 def findInvalidIDReferences(self, parsed_data):
23732389 return set([1])
2374 parser = self._setupConsistencyCheckParser()2390
2391 validate_mock_class(MockSubmissionParserInvalidIDReferences)
2392
2393 parser = MockSubmissionParserInvalidIDReferences(self.log)
2375 parser.submission_key = 'Consistency check detects invalid ID refs'2394 parser.submission_key = 'Consistency check detects invalid ID refs'
2376 parser.findInvalidIDReferences = (
2377 lambda parsed_data: findInvalidIDReferences(parser, parsed_data))
2378 result = parser.checkConsistency({'hardware':2395 result = parser.checkConsistency({'hardware':
2379 {'hal': {'devices': []}}})2396 {'hal': {'devices': []}}})
2380 self.assertEqual(result, False,2397 self.assertEqual(result, False,
@@ -2384,16 +2401,18 @@
23842401
2385 def testConsistencyCheckWithDuplicateUDI(self):2402 def testConsistencyCheckWithDuplicateUDI(self):
2386 """SubmissionParser.checkConsistency detects duplicate UDIs."""2403 """SubmissionParser.checkConsistency detects duplicate UDIs."""
2387 test = self2404 class MockSubmissionParserUDIDeviceMapFails(
2388 def getUDIDeviceMap(self, parsed_data):2405 self.MockSubmissionParser):
2389 test.assertTrue(isinstance(self, SubmissionParser))2406 """A parser where getUDIDeviceMap() fails."""
2390 raise ValueError(2407
2391 'Duplicate UDI: /org/freedesktop/Hal/devices/computer')2408 def getUDIDeviceMap(self, devices):
23922409 raise ValueError(
2393 parser = self._setupConsistencyCheckParser()2410 'Duplicate UDI: /org/freedesktop/Hal/devices/computer')
2411
2412 validate_mock_class(MockSubmissionParserUDIDeviceMapFails)
2413
2414 parser = MockSubmissionParserUDIDeviceMapFails(self.log)
2394 parser.submission_key = 'Consistency check detects invalid ID refs'2415 parser.submission_key = 'Consistency check detects invalid ID refs'
2395 parser.getUDIDeviceMap = (
2396 lambda devices: getUDIDeviceMap(parser, devices))
2397 result = parser.checkConsistency({'hardware':2416 result = parser.checkConsistency({'hardware':
2398 {'hal': {'devices': []}}})2417 {'hal': {'devices': []}}})
2399 self.assertEqual(result, False,2418 self.assertEqual(result, False,
@@ -2404,15 +2423,17 @@
24042423
2405 def testConsistencyCheckChildUDIWithoutParent(self):2424 def testConsistencyCheckChildUDIWithoutParent(self):
2406 """SubmissionParser.checkConsistency detects "orphaned" devices."""2425 """SubmissionParser.checkConsistency detects "orphaned" devices."""
2407 test = self2426 class MockSubmissionParserUDIChildrenFails(
2408 def getUDIChildren(self, udi_device_map):2427 self.MockSubmissionParser):
2409 test.assertTrue(isinstance(self, SubmissionParser))2428 """A parser where getUDIChildren() fails."""
2410 raise ValueError('Unknown parent UDI /foo in <device id="3">')2429
24112430 def getUDIChildren(self, udi_device_map):
2412 parser = self._setupConsistencyCheckParser()2431 raise ValueError('Unknown parent UDI /foo in <device id="3">')
2432
2433 validate_mock_class(MockSubmissionParserUDIChildrenFails)
2434
2435 parser = MockSubmissionParserUDIChildrenFails(self.log)
2413 parser.submission_key = 'Consistency check detects invalid ID refs'2436 parser.submission_key = 'Consistency check detects invalid ID refs'
2414 parser.getUDIChildren = (
2415 lambda udi_device_map: getUDIChildren(parser, udi_device_map))
2416 result = parser.checkConsistency({'hardware':2437 result = parser.checkConsistency({'hardware':
2417 {'hal': {'devices': []}}})2438 {'hal': {'devices': []}}})
2418 self.assertEqual(result, False,2439 self.assertEqual(result, False,
@@ -2423,17 +2444,21 @@
24232444
2424 def testConsistencyCheckCircularParentChildRelation(self):2445 def testConsistencyCheckCircularParentChildRelation(self):
2425 """SubmissionParser.checkConsistency detects "orphaned" devices."""2446 """SubmissionParser.checkConsistency detects "orphaned" devices."""
2426 test = self2447 class MockSubmissionParserHALDevicesParentChildConsistency(
2427 def checkHALDevicesParentChildConsistency(self, devices):2448 self.MockSubmissionParser):
2428 test.assertTrue(isinstance(self, SubmissionParser))2449 """A parser where checkHALDevicesParentChildConsistency() fails.
2429 return ['/foo', '/bar']2450 """
24302451
2431 parser = self._setupConsistencyCheckParser()2452 def checkHALDevicesParentChildConsistency(self, udi_children):
2453 return ['/foo', '/bar']
2454
2455 validate_mock_class(
2456 MockSubmissionParserHALDevicesParentChildConsistency)
2457
2458 parser = MockSubmissionParserHALDevicesParentChildConsistency(
2459 self.log)
2432 parser.submission_key = ('Consistency check detects circular '2460 parser.submission_key = ('Consistency check detects circular '
2433 'parent-child relationships')2461 'parent-child relationships')
2434 parser.checkHALDevicesParentChildConsistency = (
2435 lambda devices: checkHALDevicesParentChildConsistency(
2436 parser, devices))
2437 result = parser.checkConsistency({'hardware':2462 result = parser.checkConsistency({'hardware':
2438 {'hal': {'devices': []}}})2463 {'hal': {'devices': []}}})
2439 self.assertEqual(result, False,2464 self.assertEqual(result, False,
24402465
=== modified file 'lib/lp/testing/__init__.py'
--- lib/lp/testing/__init__.py 2009-10-23 11:07:32 +0000
+++ lib/lp/testing/__init__.py 2009-10-26 10:45:33 +0000
@@ -8,6 +8,7 @@
8from datetime import datetime, timedelta8from datetime import datetime, timedelta
9from pprint import pformat9from pprint import pformat
10import copy10import copy
11from inspect import getargspec, getmembers, getmro, isclass, ismethod
11import os12import os
12import shutil13import shutil
13import subprocess14import subprocess
@@ -793,3 +794,69 @@
793 tree.unlock()794 tree.unlock()
794795
795 return contents796 return contents
797
798def validate_mock_class(mock_class):
799 """Validate method signatures in mock classes derived from real classes.
800
801 We often use mock classes in tests which are derived from real
802 classes.
803
804 This function ensures that methods redefined in the mock
805 class have the same signature as the corresponding methods of
806 the base class.
807
808 >>> class A:
809 ...
810 ... def method_one(self, a):
811 ... pass
812
813 >>>
814 >>> class B(A):
815 ... def method_one(self, a):
816 ... pass
817 >>> validate_mock_class(B)
818
819 If a class derived from A defines method_one with a different
820 signature, we get an AssertionError.
821
822 >>> class C(A):
823 ... def method_one(self, a, b):
824 ... pass
825 >>> validate_mock_class(C)
826 Traceback (most recent call last):
827 ...
828 AssertionError: Different method signature for method_one:...
829
830 Even a parameter name must not be modified.
831
832 >>> class D(A):
833 ... def method_one(self, b):
834 ... pass
835 >>> validate_mock_class(D)
836 Traceback (most recent call last):
837 ...
838 AssertionError: Different method signature for method_one:...
839
840 If validate_mock_class() for anything but a class, we get an
841 AssertionError.
842
843 >>> validate_mock_class('a string')
844 Traceback (most recent call last):
845 ...
846 AssertionError: validate_mock_class() must be called for a class
847 """
848 assert isclass(mock_class), (
849 "validate_mock_class() must be called for a class")
850 base_classes = getmro(mock_class)
851 for name, obj in getmembers(mock_class):
852 if ismethod(obj):
853 for base_class in base_classes[1:]:
854 if name in base_class.__dict__:
855 mock_args = getargspec(obj)
856 real_args = getargspec(base_class.__dict__[name])
857 if mock_args != real_args:
858 raise AssertionError(
859 'Different method signature for %s: %r %r' % (
860 name, mock_args, real_args))
861 else:
862 break
796863
=== added file 'lib/lp/testing/tests/test_inlinetests.py'
--- lib/lp/testing/tests/test_inlinetests.py 1970-01-01 00:00:00 +0000
+++ lib/lp/testing/tests/test_inlinetests.py 2009-10-26 10:45:33 +0000
@@ -0,0 +1,20 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Run the doc string tests."""
5
6import doctest
7
8from zope.testing.doctest import NORMALIZE_WHITESPACE, ELLIPSIS
9
10from canonical.launchpad.testing.systemdocs import LayeredDocFileSuite
11from canonical.testing import BaseLayer
12from lp import testing
13
14def test_suite():
15 suite = LayeredDocFileSuite(
16 layer=BaseLayer)
17 suite.addTest(doctest.DocTestSuite(
18 testing, optionflags=NORMALIZE_WHITESPACE|ELLIPSIS))
19 return suite
20

Subscribers

People subscribed via source and target branches

to status/vote changes: