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 | 1228 | def checkUdevUsbProperties(self, udev_data): | 1228 | def checkUdevUsbProperties(self, udev_data): |
6 | 1229 | """Validation of udev USB devices. | 1229 | """Validation of udev USB devices. |
7 | 1230 | 1230 | ||
11 | 1231 | USB devices must have the properties DEVTYPE (value | 1231 | USB devices must either have the three properties DEVTYPE |
12 | 1232 | 'usb_device' or 'usb_interface'), PRODUCT and TYPE. PRODUCT | 1232 | (value 'usb_device' or 'usb_interface'), PRODUCT and TYPE, |
13 | 1233 | must be a tuple of three integers in hexadecimal | 1233 | or they must have none of them. |
14 | 1234 | |||
15 | 1235 | PRODUCT must be a tuple of three integers in hexadecimal | ||
16 | 1234 | representation, separates by '/'. TYPE must be a a tuple of | 1236 | representation, separates by '/'. TYPE must be a a tuple of |
17 | 1235 | three integers in decimal representation, separated by '/'. | 1237 | three integers in decimal representation, separated by '/'. |
18 | 1236 | usb_interface nodes must additionally have a property | 1238 | usb_interface nodes must additionally have a property |
19 | @@ -1245,6 +1247,10 @@ | |||
20 | 1245 | property_names = set(properties) | 1247 | property_names = set(properties) |
21 | 1246 | existing_usb_properties = property_names.intersection( | 1248 | existing_usb_properties = property_names.intersection( |
22 | 1247 | UDEV_USB_DEVICE_PROPERTIES) | 1249 | UDEV_USB_DEVICE_PROPERTIES) |
23 | 1250 | |||
24 | 1251 | if len(existing_usb_properties) == 0: | ||
25 | 1252 | continue | ||
26 | 1253 | |||
27 | 1248 | if existing_usb_properties != UDEV_USB_DEVICE_PROPERTIES: | 1254 | if existing_usb_properties != UDEV_USB_DEVICE_PROPERTIES: |
28 | 1249 | missing_properties = UDEV_USB_DEVICE_PROPERTIES.difference( | 1255 | missing_properties = UDEV_USB_DEVICE_PROPERTIES.difference( |
29 | 1250 | existing_usb_properties) | 1256 | existing_usb_properties) |
30 | @@ -1365,7 +1371,8 @@ | |||
31 | 1365 | if ('udev' in parsed_data['hardware'] | 1371 | if ('udev' in parsed_data['hardware'] |
32 | 1366 | and not self.checkConsistentUdevDeviceData( | 1372 | and not self.checkConsistentUdevDeviceData( |
33 | 1367 | parsed_data['hardware']['udev'], | 1373 | parsed_data['hardware']['udev'], |
35 | 1368 | parsed_data['hardware']['sysfs-attributes'])): | 1374 | parsed_data['hardware']['sysfs-attributes'], |
36 | 1375 | parsed_data['hardware']['dmi'],)): | ||
37 | 1369 | return False | 1376 | return False |
38 | 1370 | duplicate_ids = self.findDuplicateIDs(parsed_data) | 1377 | duplicate_ids = self.findDuplicateIDs(parsed_data) |
39 | 1371 | if duplicate_ids: | 1378 | if duplicate_ids: |
40 | 1372 | 1379 | ||
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 | 23 | ROOT_UDI) | 23 | ROOT_UDI) |
46 | 24 | from canonical.testing import BaseLayer | 24 | from canonical.testing import BaseLayer |
47 | 25 | 25 | ||
48 | 26 | from lp.testing import validate_mock_class | ||
49 | 26 | 27 | ||
50 | 27 | class SubmissionParserTestParseSoftware(SubmissionParser): | 28 | class SubmissionParserTestParseSoftware(SubmissionParser): |
51 | 28 | """A Variant used to test SubmissionParser._parseSoftware. | 29 | """A Variant used to test SubmissionParser._parseSoftware. |
52 | @@ -1928,27 +1929,41 @@ | |||
53 | 1928 | "'/devices/pci0000:00/0000:00:1f.2'") | 1929 | "'/devices/pci0000:00/0000:00:1f.2'") |
54 | 1929 | 1930 | ||
55 | 1930 | def testCheckUdevUsbProperties(self): | 1931 | def testCheckUdevUsbProperties(self): |
57 | 1931 | """Test of SubmissionParser.checkUdevUsbProperties().""" | 1932 | """Test of SubmissionParser.checkUdevUsbProperties(). |
58 | 1933 | |||
59 | 1934 | udev nodes for USB devices must define the three properties | ||
60 | 1935 | DEVTYPE, PRODUCT, TYPE or none of them. | ||
61 | 1936 | """ | ||
62 | 1932 | parser = SubmissionParser() | 1937 | parser = SubmissionParser() |
63 | 1933 | self.assertTrue(parser.checkUdevUsbProperties( | 1938 | self.assertTrue(parser.checkUdevUsbProperties( |
64 | 1934 | [self.udev_root_device, self.udev_usb_device, | 1939 | [self.udev_root_device, self.udev_usb_device, |
65 | 1935 | self.udev_usb_interface])) | 1940 | self.udev_usb_interface])) |
66 | 1936 | 1941 | ||
67 | 1942 | for property_name in ('DEVTYPE', 'PRODUCT', 'TYPE'): | ||
68 | 1943 | del self.udev_usb_device['E'][property_name] | ||
69 | 1944 | self.assertTrue(parser.checkUdevUsbProperties( | ||
70 | 1945 | [self.udev_root_device, self.udev_usb_device, | ||
71 | 1946 | self.udev_usb_interface])) | ||
72 | 1947 | |||
73 | 1937 | def testCheckUdevUsbProperties_missing_required_property(self): | 1948 | def testCheckUdevUsbProperties_missing_required_property(self): |
74 | 1938 | """Test of SubmissionParser.checkUdevUsbProperties(). | 1949 | """Test of SubmissionParser.checkUdevUsbProperties(). |
75 | 1939 | 1950 | ||
78 | 1940 | A USB device that does not have a required property makes a | 1951 | A USB device where some but not all of the properties DEVTYPE, |
79 | 1941 | submission invalid. | 1952 | PRODUCT, TYPE are defined makes a submission invalid. |
80 | 1942 | """ | 1953 | """ |
90 | 1943 | del self.udev_usb_device['E']['DEVTYPE'] | 1954 | for property_name in ('DEVTYPE', 'PRODUCT', 'TYPE'): |
91 | 1944 | parser = SubmissionParser(self.log) | 1955 | saved_property = self.udev_usb_device['E'].pop(property_name) |
92 | 1945 | parser.submission_key = 'USB device without DEVTYPE property' | 1956 | parser = SubmissionParser(self.log) |
93 | 1946 | self.assertFalse(parser.checkUdevUsbProperties( | 1957 | parser.submission_key = ( |
94 | 1947 | [self.udev_root_device, self.udev_usb_device])) | 1958 | 'USB device without %s property' % property_name) |
95 | 1948 | self.assertErrorMessage( | 1959 | self.assertFalse(parser.checkUdevUsbProperties( |
96 | 1949 | parser.submission_key, | 1960 | [self.udev_root_device, self.udev_usb_device])) |
97 | 1950 | "USB udev device found without required properties: " | 1961 | self.assertErrorMessage( |
98 | 1951 | "set(['DEVTYPE']) '/devices/pci0000:00/0000:00:1d.1/usb3/3-2'") | 1962 | parser.submission_key, |
99 | 1963 | "USB udev device found without required properties: " | ||
100 | 1964 | "set(['%s']) '/devices/pci0000:00/0000:00:1d.1/usb3/3-2'" | ||
101 | 1965 | % property_name) | ||
102 | 1966 | self.udev_usb_device['E'][property_name] = saved_property | ||
103 | 1952 | 1967 | ||
104 | 1953 | def testCheckUdevUsbProperties_with_invalid_product_id(self): | 1968 | def testCheckUdevUsbProperties_with_invalid_product_id(self): |
105 | 1954 | """Test of SubmissionParser.checkUdevUsbProperties(). | 1969 | """Test of SubmissionParser.checkUdevUsbProperties(). |
106 | @@ -2102,7 +2117,7 @@ | |||
107 | 2102 | 2117 | ||
108 | 2103 | All shortcut methods return True. | 2118 | All shortcut methods return True. |
109 | 2104 | """ | 2119 | """ |
111 | 2105 | def checkUdevDictsHavePathKey(self, udev_data): | 2120 | def checkUdevDictsHavePathKey(self, udev_nodes): |
112 | 2106 | """See `SubmissionParser`.""" | 2121 | """See `SubmissionParser`.""" |
113 | 2107 | return True | 2122 | return True |
114 | 2108 | 2123 | ||
115 | @@ -2114,7 +2129,7 @@ | |||
116 | 2114 | """See `SubmissionParser`.""" | 2129 | """See `SubmissionParser`.""" |
117 | 2115 | return True | 2130 | return True |
118 | 2116 | 2131 | ||
120 | 2117 | def checkUdevScsiProperties(self, udev_data, syfs_data): | 2132 | def checkUdevScsiProperties(self, udev_data, sysfs_data): |
121 | 2118 | """See `SubmissionParser`.""" | 2133 | """See `SubmissionParser`.""" |
122 | 2119 | return True | 2134 | return True |
123 | 2120 | 2135 | ||
124 | @@ -2122,6 +2137,8 @@ | |||
125 | 2122 | """See `SubmissionParser`.""" | 2137 | """See `SubmissionParser`.""" |
126 | 2123 | return True | 2138 | return True |
127 | 2124 | 2139 | ||
128 | 2140 | validate_mock_class(UdevTestSubmissionParser) | ||
129 | 2141 | |||
130 | 2125 | def testCheckConsistentUdevDeviceData(self): | 2142 | def testCheckConsistentUdevDeviceData(self): |
131 | 2126 | """Test of SubmissionParser.checkConsistentUdevDeviceData(),""" | 2143 | """Test of SubmissionParser.checkConsistentUdevDeviceData(),""" |
132 | 2127 | parser = self.UdevTestSubmissionParser() | 2144 | parser = self.UdevTestSubmissionParser() |
133 | @@ -2137,10 +2154,12 @@ | |||
134 | 2137 | self.UdevTestSubmissionParser): | 2154 | self.UdevTestSubmissionParser): |
135 | 2138 | """A SubmissionPaser where checkUdevDictsHavePathKey() fails.""" | 2155 | """A SubmissionPaser where checkUdevDictsHavePathKey() fails.""" |
136 | 2139 | 2156 | ||
138 | 2140 | def checkUdevDictsHavePathKey(self, udev_data): | 2157 | def checkUdevDictsHavePathKey(self, udev_nodes): |
139 | 2141 | """See `SubmissionParser`.""" | 2158 | """See `SubmissionParser`.""" |
140 | 2142 | return False | 2159 | return False |
141 | 2143 | 2160 | ||
142 | 2161 | validate_mock_class(SubmissionParserUdevPathCheckFails) | ||
143 | 2162 | |||
144 | 2144 | parser = SubmissionParserUdevPathCheckFails() | 2163 | parser = SubmissionParserUdevPathCheckFails() |
145 | 2145 | self.assertFalse(parser.checkConsistentUdevDeviceData( | 2164 | self.assertFalse(parser.checkConsistentUdevDeviceData( |
146 | 2146 | None, None, None)) | 2165 | None, None, None)) |
147 | @@ -2158,6 +2177,8 @@ | |||
148 | 2158 | """See `SubmissionParser`.""" | 2177 | """See `SubmissionParser`.""" |
149 | 2159 | return False | 2178 | return False |
150 | 2160 | 2179 | ||
151 | 2180 | validate_mock_class(SubmissionParserUdevPciCheckFails) | ||
152 | 2181 | |||
153 | 2161 | parser = SubmissionParserUdevPciCheckFails() | 2182 | parser = SubmissionParserUdevPciCheckFails() |
154 | 2162 | self.assertFalse(parser.checkConsistentUdevDeviceData( | 2183 | self.assertFalse(parser.checkConsistentUdevDeviceData( |
155 | 2163 | None, None, None)) | 2184 | None, None, None)) |
156 | @@ -2175,6 +2196,8 @@ | |||
157 | 2175 | """See `SubmissionParser`.""" | 2196 | """See `SubmissionParser`.""" |
158 | 2176 | return False | 2197 | return False |
159 | 2177 | 2198 | ||
160 | 2199 | validate_mock_class(SubmissionParserUdevUsbCheckFails) | ||
161 | 2200 | |||
162 | 2178 | parser = SubmissionParserUdevUsbCheckFails() | 2201 | parser = SubmissionParserUdevUsbCheckFails() |
163 | 2179 | self.assertFalse(parser.checkConsistentUdevDeviceData( | 2202 | self.assertFalse(parser.checkConsistentUdevDeviceData( |
164 | 2180 | None, None, None)) | 2203 | None, None, None)) |
165 | @@ -2192,6 +2215,8 @@ | |||
166 | 2192 | """See `SubmissionParser`.""" | 2215 | """See `SubmissionParser`.""" |
167 | 2193 | return False | 2216 | return False |
168 | 2194 | 2217 | ||
169 | 2218 | validate_mock_class(SubmissionParserUdevUsbCheckFails) | ||
170 | 2219 | |||
171 | 2195 | parser = SubmissionParserUdevUsbCheckFails() | 2220 | parser = SubmissionParserUdevUsbCheckFails() |
172 | 2196 | self.assertFalse(parser.checkConsistentUdevDeviceData( | 2221 | self.assertFalse(parser.checkConsistentUdevDeviceData( |
173 | 2197 | None, None, None)) | 2222 | None, None, None)) |
174 | @@ -2209,55 +2234,38 @@ | |||
175 | 2209 | """See `SubmissionParser`.""" | 2234 | """See `SubmissionParser`.""" |
176 | 2210 | return False | 2235 | return False |
177 | 2211 | 2236 | ||
178 | 2237 | validate_mock_class(SubmissionParserUdevUsbCheckFails) | ||
179 | 2238 | |||
180 | 2212 | parser = SubmissionParserUdevUsbCheckFails() | 2239 | parser = SubmissionParserUdevUsbCheckFails() |
181 | 2213 | self.assertFalse(parser.checkConsistentUdevDeviceData( | 2240 | self.assertFalse(parser.checkConsistentUdevDeviceData( |
182 | 2214 | None, None, None)) | 2241 | None, None, None)) |
183 | 2215 | 2242 | ||
186 | 2216 | def _setupConsistencyCheckParser(self): | 2243 | class MockSubmissionParser(SubmissionParser): |
187 | 2217 | """Prepare and return a SubmissionParser instance. | 2244 | """A SubmissionParser variant for testing checkCOnsistentData() |
188 | 2218 | 2245 | ||
189 | 2219 | All "method substitutes" return a valid result. | 2246 | All "method substitutes" return a valid result. |
190 | 2220 | """ | 2247 | """ |
192 | 2221 | test = self | 2248 | |
193 | 2222 | def findDuplicateIDs(self, parsed_data): | 2249 | def findDuplicateIDs(self, parsed_data): |
194 | 2223 | test.assertTrue(isinstance(self, SubmissionParser)) | ||
195 | 2224 | return set() | 2250 | return set() |
196 | 2225 | 2251 | ||
197 | 2226 | def findInvalidIDReferences(self, parsed_data): | 2252 | def findInvalidIDReferences(self, parsed_data): |
198 | 2227 | test.assertTrue(isinstance(self, SubmissionParser)) | ||
199 | 2228 | return set() | 2253 | return set() |
200 | 2229 | 2254 | ||
201 | 2230 | def getUDIDeviceMap(self, devices): | 2255 | def getUDIDeviceMap(self, devices): |
202 | 2231 | test.assertTrue(isinstance(self, SubmissionParser)) | ||
203 | 2232 | return {} | 2256 | return {} |
204 | 2233 | 2257 | ||
205 | 2234 | def getUDIChildren(self, udi_device_map): | 2258 | def getUDIChildren(self, udi_device_map): |
206 | 2235 | test.assertTrue(isinstance(self, SubmissionParser)) | ||
207 | 2236 | return {} | 2259 | return {} |
208 | 2237 | 2260 | ||
211 | 2238 | def checkHALDevicesParentChildConsistency(self, devices): | 2261 | def checkHALDevicesParentChildConsistency(self, udi_children): |
210 | 2239 | test.assertTrue(isinstance(self, SubmissionParser)) | ||
212 | 2240 | return [] | 2262 | return [] |
213 | 2241 | 2263 | ||
215 | 2242 | def checkConsistentUdevDeviceData(self, udev_data, sysfs_data): | 2264 | def checkConsistentUdevDeviceData( |
216 | 2265 | self, udev_data, sysfs_data, dmi_data): | ||
217 | 2243 | return True | 2266 | return True |
218 | 2244 | 2267 | ||
235 | 2245 | parser = SubmissionParser(self.log) | 2268 | validate_mock_class(MockSubmissionParser) |
220 | 2246 | parser.findDuplicateIDs = ( | ||
221 | 2247 | lambda parsed_data: findDuplicateIDs(parser, parsed_data)) | ||
222 | 2248 | parser.findInvalidIDReferences = ( | ||
223 | 2249 | lambda parsed_data: findInvalidIDReferences(parser, parsed_data)) | ||
224 | 2250 | parser.getUDIDeviceMap = ( | ||
225 | 2251 | lambda devices: getUDIDeviceMap(parser, devices)) | ||
226 | 2252 | parser.getUDIChildren = ( | ||
227 | 2253 | lambda udi_device_map: getUDIChildren(parser, udi_device_map)) | ||
228 | 2254 | parser.checkHALDevicesParentChildConsistency = ( | ||
229 | 2255 | lambda udi_children: checkHALDevicesParentChildConsistency( | ||
230 | 2256 | parser, udi_children)) | ||
231 | 2257 | parser.checkConsistentUdevDeviceData = ( | ||
232 | 2258 | lambda udev_data, sysfs_data: checkConsistentUdevDeviceData( | ||
233 | 2259 | parser, udev_data, sysfs_data)) | ||
234 | 2260 | return parser | ||
236 | 2261 | 2269 | ||
237 | 2262 | def assertErrorMessage(self, submission_key, log_message): | 2270 | def assertErrorMessage(self, submission_key, log_message): |
238 | 2263 | """Search for message in the log entries for submission_key. | 2271 | """Search for message in the log entries for submission_key. |
239 | @@ -2309,7 +2317,7 @@ | |||
240 | 2309 | 2317 | ||
241 | 2310 | def testConsistencyCheck(self): | 2318 | def testConsistencyCheck(self): |
242 | 2311 | """Test of SubmissionParser.checkConsistency.""" | 2319 | """Test of SubmissionParser.checkConsistency.""" |
244 | 2312 | parser = self._setupConsistencyCheckParser() | 2320 | parser = self.MockSubmissionParser() |
245 | 2313 | result = parser.checkConsistency({'hardware': | 2321 | result = parser.checkConsistency({'hardware': |
246 | 2314 | {'hal': {'devices': []}}}) | 2322 | {'hal': {'devices': []}}}) |
247 | 2315 | self.assertEqual(result, True, | 2323 | self.assertEqual(result, True, |
248 | @@ -2318,45 +2326,53 @@ | |||
249 | 2318 | 2326 | ||
250 | 2319 | def testConsistencyCheckValidUdevData(self): | 2327 | def testConsistencyCheckValidUdevData(self): |
251 | 2320 | """Test of SubmissionParser.checkConsistency.""" | 2328 | """Test of SubmissionParser.checkConsistency.""" |
253 | 2321 | parser = self._setupConsistencyCheckParser() | 2329 | parser = self.MockSubmissionParser() |
254 | 2322 | self.assertTrue(parser.checkConsistency( | 2330 | self.assertTrue(parser.checkConsistency( |
255 | 2323 | { | 2331 | { |
256 | 2324 | 'hardware': { | 2332 | 'hardware': { |
259 | 2325 | 'udev': [], | 2333 | 'udev': None, |
260 | 2326 | 'sysfs-attributes': [] | 2334 | 'sysfs-attributes': None, |
261 | 2335 | 'dmi': None, | ||
262 | 2327 | } | 2336 | } |
263 | 2328 | } | 2337 | } |
264 | 2329 | )) | 2338 | )) |
265 | 2330 | 2339 | ||
266 | 2331 | def testConsistencyCheck_invalid_udev_data(self): | 2340 | def testConsistencyCheck_invalid_udev_data(self): |
267 | 2332 | """Test of SubmissionParser.checkConsistency.""" | 2341 | """Test of SubmissionParser.checkConsistency.""" |
275 | 2333 | def checkConsistentUdevDeviceData(self, udev_data, sysfs_data): | 2342 | class MockSubmissionParserBadUdevDeviceData( |
276 | 2334 | return False | 2343 | self.MockSubmissionParser): |
277 | 2335 | 2344 | """A parser where checkConsistentUdevDeviceData() fails.""" | |
278 | 2336 | parser = self._setupConsistencyCheckParser() | 2345 | |
279 | 2337 | parser.checkConsistentUdevDeviceData = ( | 2346 | def checkConsistentUdevDeviceData(self, udev_data, sysfs_data, |
280 | 2338 | lambda udev_data, sysfs_data: checkConsistentUdevDeviceData( | 2347 | dmi_data): |
281 | 2339 | parser, udev_data, sysfs_data)) | 2348 | return False |
282 | 2349 | |||
283 | 2350 | validate_mock_class(MockSubmissionParserBadUdevDeviceData) | ||
284 | 2351 | |||
285 | 2352 | parser = MockSubmissionParserBadUdevDeviceData() | ||
286 | 2340 | self.assertFalse(parser.checkConsistency( | 2353 | self.assertFalse(parser.checkConsistency( |
287 | 2341 | { | 2354 | { |
288 | 2342 | 'hardware': { | 2355 | 'hardware': { |
291 | 2343 | 'udev': [{}], | 2356 | 'udev': None, |
292 | 2344 | 'sysfs-attributes': [] | 2357 | 'sysfs-attributes': None, |
293 | 2358 | 'dmi': None, | ||
294 | 2345 | } | 2359 | } |
295 | 2346 | } | 2360 | } |
296 | 2347 | )) | 2361 | )) |
297 | 2348 | 2362 | ||
298 | 2349 | def testConsistencyCheckWithDuplicateIDs(self): | 2363 | def testConsistencyCheckWithDuplicateIDs(self): |
299 | 2350 | """SubmissionParser.checkConsistency detects duplicate IDs.""" | 2364 | """SubmissionParser.checkConsistency detects duplicate IDs.""" |
306 | 2351 | test = self | 2365 | class MockSubmissionParserDuplicateIds( |
307 | 2352 | def findDuplicateIDs(self, parsed_data): | 2366 | self.MockSubmissionParser): |
308 | 2353 | test.assertTrue(isinstance(self, SubmissionParser)) | 2367 | """A parser where findDuplicateIDs() fails.""" |
309 | 2354 | return set([1]) | 2368 | |
310 | 2355 | 2369 | def findDuplicateIDs(self, parsed_data): | |
311 | 2356 | parser = self._setupConsistencyCheckParser() | 2370 | return set([1]) |
312 | 2371 | |||
313 | 2372 | validate_mock_class(MockSubmissionParserDuplicateIds) | ||
314 | 2373 | |||
315 | 2374 | parser = MockSubmissionParserDuplicateIds(self.log) | ||
316 | 2357 | parser.submission_key = 'Consistency check detects duplicate IDs' | 2375 | parser.submission_key = 'Consistency check detects duplicate IDs' |
317 | 2358 | parser.findDuplicateIDs = ( | ||
318 | 2359 | lambda parsed_data: findDuplicateIDs(parser, parsed_data)) | ||
319 | 2360 | result = parser.checkConsistency({'hardware': | 2376 | result = parser.checkConsistency({'hardware': |
320 | 2361 | {'hal': {'devices': []}}}) | 2377 | {'hal': {'devices': []}}}) |
321 | 2362 | self.assertEqual(result, False, | 2378 | self.assertEqual(result, False, |
322 | @@ -2366,15 +2382,16 @@ | |||
323 | 2366 | 2382 | ||
324 | 2367 | def testConsistencyCheckWithInvalidIDReferences(self): | 2383 | def testConsistencyCheckWithInvalidIDReferences(self): |
325 | 2368 | """SubmissionParser.checkConsistency detects invalid ID references.""" | 2384 | """SubmissionParser.checkConsistency detects invalid ID references.""" |
332 | 2369 | test = self | 2385 | class MockSubmissionParserInvalidIDReferences( |
333 | 2370 | def findInvalidIDReferences(self, parsed_data): | 2386 | self.MockSubmissionParser): |
334 | 2371 | test.assertTrue(isinstance(self, SubmissionParser)) | 2387 | """A parser where findInvalidIDReferences() fails.""" |
335 | 2372 | return set([1]) | 2388 | def findInvalidIDReferences(self, parsed_data): |
336 | 2373 | 2389 | return set([1]) | |
337 | 2374 | parser = self._setupConsistencyCheckParser() | 2390 | |
338 | 2391 | validate_mock_class(MockSubmissionParserInvalidIDReferences) | ||
339 | 2392 | |||
340 | 2393 | parser = MockSubmissionParserInvalidIDReferences(self.log) | ||
341 | 2375 | parser.submission_key = 'Consistency check detects invalid ID refs' | 2394 | parser.submission_key = 'Consistency check detects invalid ID refs' |
342 | 2376 | parser.findInvalidIDReferences = ( | ||
343 | 2377 | lambda parsed_data: findInvalidIDReferences(parser, parsed_data)) | ||
344 | 2378 | result = parser.checkConsistency({'hardware': | 2395 | result = parser.checkConsistency({'hardware': |
345 | 2379 | {'hal': {'devices': []}}}) | 2396 | {'hal': {'devices': []}}}) |
346 | 2380 | self.assertEqual(result, False, | 2397 | self.assertEqual(result, False, |
347 | @@ -2384,16 +2401,18 @@ | |||
348 | 2384 | 2401 | ||
349 | 2385 | def testConsistencyCheckWithDuplicateUDI(self): | 2402 | def testConsistencyCheckWithDuplicateUDI(self): |
350 | 2386 | """SubmissionParser.checkConsistency detects duplicate UDIs.""" | 2403 | """SubmissionParser.checkConsistency detects duplicate UDIs.""" |
358 | 2387 | test = self | 2404 | class MockSubmissionParserUDIDeviceMapFails( |
359 | 2388 | def getUDIDeviceMap(self, parsed_data): | 2405 | self.MockSubmissionParser): |
360 | 2389 | test.assertTrue(isinstance(self, SubmissionParser)) | 2406 | """A parser where getUDIDeviceMap() fails.""" |
361 | 2390 | raise ValueError( | 2407 | |
362 | 2391 | 'Duplicate UDI: /org/freedesktop/Hal/devices/computer') | 2408 | def getUDIDeviceMap(self, devices): |
363 | 2392 | 2409 | raise ValueError( | |
364 | 2393 | parser = self._setupConsistencyCheckParser() | 2410 | 'Duplicate UDI: /org/freedesktop/Hal/devices/computer') |
365 | 2411 | |||
366 | 2412 | validate_mock_class(MockSubmissionParserUDIDeviceMapFails) | ||
367 | 2413 | |||
368 | 2414 | parser = MockSubmissionParserUDIDeviceMapFails(self.log) | ||
369 | 2394 | parser.submission_key = 'Consistency check detects invalid ID refs' | 2415 | parser.submission_key = 'Consistency check detects invalid ID refs' |
370 | 2395 | parser.getUDIDeviceMap = ( | ||
371 | 2396 | lambda devices: getUDIDeviceMap(parser, devices)) | ||
372 | 2397 | result = parser.checkConsistency({'hardware': | 2416 | result = parser.checkConsistency({'hardware': |
373 | 2398 | {'hal': {'devices': []}}}) | 2417 | {'hal': {'devices': []}}}) |
374 | 2399 | self.assertEqual(result, False, | 2418 | self.assertEqual(result, False, |
375 | @@ -2404,15 +2423,17 @@ | |||
376 | 2404 | 2423 | ||
377 | 2405 | def testConsistencyCheckChildUDIWithoutParent(self): | 2424 | def testConsistencyCheckChildUDIWithoutParent(self): |
378 | 2406 | """SubmissionParser.checkConsistency detects "orphaned" devices.""" | 2425 | """SubmissionParser.checkConsistency detects "orphaned" devices.""" |
385 | 2407 | test = self | 2426 | class MockSubmissionParserUDIChildrenFails( |
386 | 2408 | def getUDIChildren(self, udi_device_map): | 2427 | self.MockSubmissionParser): |
387 | 2409 | test.assertTrue(isinstance(self, SubmissionParser)) | 2428 | """A parser where getUDIChildren() fails.""" |
388 | 2410 | raise ValueError('Unknown parent UDI /foo in <device id="3">') | 2429 | |
389 | 2411 | 2430 | def getUDIChildren(self, udi_device_map): | |
390 | 2412 | parser = self._setupConsistencyCheckParser() | 2431 | raise ValueError('Unknown parent UDI /foo in <device id="3">') |
391 | 2432 | |||
392 | 2433 | validate_mock_class(MockSubmissionParserUDIChildrenFails) | ||
393 | 2434 | |||
394 | 2435 | parser = MockSubmissionParserUDIChildrenFails(self.log) | ||
395 | 2413 | parser.submission_key = 'Consistency check detects invalid ID refs' | 2436 | parser.submission_key = 'Consistency check detects invalid ID refs' |
396 | 2414 | parser.getUDIChildren = ( | ||
397 | 2415 | lambda udi_device_map: getUDIChildren(parser, udi_device_map)) | ||
398 | 2416 | result = parser.checkConsistency({'hardware': | 2437 | result = parser.checkConsistency({'hardware': |
399 | 2417 | {'hal': {'devices': []}}}) | 2438 | {'hal': {'devices': []}}}) |
400 | 2418 | self.assertEqual(result, False, | 2439 | self.assertEqual(result, False, |
401 | @@ -2423,17 +2444,21 @@ | |||
402 | 2423 | 2444 | ||
403 | 2424 | def testConsistencyCheckCircularParentChildRelation(self): | 2445 | def testConsistencyCheckCircularParentChildRelation(self): |
404 | 2425 | """SubmissionParser.checkConsistency detects "orphaned" devices.""" | 2446 | """SubmissionParser.checkConsistency detects "orphaned" devices.""" |
411 | 2426 | test = self | 2447 | class MockSubmissionParserHALDevicesParentChildConsistency( |
412 | 2427 | def checkHALDevicesParentChildConsistency(self, devices): | 2448 | self.MockSubmissionParser): |
413 | 2428 | test.assertTrue(isinstance(self, SubmissionParser)) | 2449 | """A parser where checkHALDevicesParentChildConsistency() fails. |
414 | 2429 | return ['/foo', '/bar'] | 2450 | """ |
415 | 2430 | 2451 | ||
416 | 2431 | parser = self._setupConsistencyCheckParser() | 2452 | def checkHALDevicesParentChildConsistency(self, udi_children): |
417 | 2453 | return ['/foo', '/bar'] | ||
418 | 2454 | |||
419 | 2455 | validate_mock_class( | ||
420 | 2456 | MockSubmissionParserHALDevicesParentChildConsistency) | ||
421 | 2457 | |||
422 | 2458 | parser = MockSubmissionParserHALDevicesParentChildConsistency( | ||
423 | 2459 | self.log) | ||
424 | 2432 | parser.submission_key = ('Consistency check detects circular ' | 2460 | parser.submission_key = ('Consistency check detects circular ' |
425 | 2433 | 'parent-child relationships') | 2461 | 'parent-child relationships') |
426 | 2434 | parser.checkHALDevicesParentChildConsistency = ( | ||
427 | 2435 | lambda devices: checkHALDevicesParentChildConsistency( | ||
428 | 2436 | parser, devices)) | ||
429 | 2437 | result = parser.checkConsistency({'hardware': | 2462 | result = parser.checkConsistency({'hardware': |
430 | 2438 | {'hal': {'devices': []}}}) | 2463 | {'hal': {'devices': []}}}) |
431 | 2439 | self.assertEqual(result, False, | 2464 | self.assertEqual(result, False, |
432 | 2440 | 2465 | ||
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 | 8 | from datetime import datetime, timedelta | 8 | from datetime import datetime, timedelta |
438 | 9 | from pprint import pformat | 9 | from pprint import pformat |
439 | 10 | import copy | 10 | import copy |
440 | 11 | from inspect import getargspec, getmembers, getmro, isclass, ismethod | ||
441 | 11 | import os | 12 | import os |
442 | 12 | import shutil | 13 | import shutil |
443 | 13 | import subprocess | 14 | import subprocess |
444 | @@ -793,3 +794,69 @@ | |||
445 | 793 | tree.unlock() | 794 | tree.unlock() |
446 | 794 | 795 | ||
447 | 795 | return contents | 796 | return contents |
448 | 797 | |||
449 | 798 | def validate_mock_class(mock_class): | ||
450 | 799 | """Validate method signatures in mock classes derived from real classes. | ||
451 | 800 | |||
452 | 801 | We often use mock classes in tests which are derived from real | ||
453 | 802 | classes. | ||
454 | 803 | |||
455 | 804 | This function ensures that methods redefined in the mock | ||
456 | 805 | class have the same signature as the corresponding methods of | ||
457 | 806 | the base class. | ||
458 | 807 | |||
459 | 808 | >>> class A: | ||
460 | 809 | ... | ||
461 | 810 | ... def method_one(self, a): | ||
462 | 811 | ... pass | ||
463 | 812 | |||
464 | 813 | >>> | ||
465 | 814 | >>> class B(A): | ||
466 | 815 | ... def method_one(self, a): | ||
467 | 816 | ... pass | ||
468 | 817 | >>> validate_mock_class(B) | ||
469 | 818 | |||
470 | 819 | If a class derived from A defines method_one with a different | ||
471 | 820 | signature, we get an AssertionError. | ||
472 | 821 | |||
473 | 822 | >>> class C(A): | ||
474 | 823 | ... def method_one(self, a, b): | ||
475 | 824 | ... pass | ||
476 | 825 | >>> validate_mock_class(C) | ||
477 | 826 | Traceback (most recent call last): | ||
478 | 827 | ... | ||
479 | 828 | AssertionError: Different method signature for method_one:... | ||
480 | 829 | |||
481 | 830 | Even a parameter name must not be modified. | ||
482 | 831 | |||
483 | 832 | >>> class D(A): | ||
484 | 833 | ... def method_one(self, b): | ||
485 | 834 | ... pass | ||
486 | 835 | >>> validate_mock_class(D) | ||
487 | 836 | Traceback (most recent call last): | ||
488 | 837 | ... | ||
489 | 838 | AssertionError: Different method signature for method_one:... | ||
490 | 839 | |||
491 | 840 | If validate_mock_class() for anything but a class, we get an | ||
492 | 841 | AssertionError. | ||
493 | 842 | |||
494 | 843 | >>> validate_mock_class('a string') | ||
495 | 844 | Traceback (most recent call last): | ||
496 | 845 | ... | ||
497 | 846 | AssertionError: validate_mock_class() must be called for a class | ||
498 | 847 | """ | ||
499 | 848 | assert isclass(mock_class), ( | ||
500 | 849 | "validate_mock_class() must be called for a class") | ||
501 | 850 | base_classes = getmro(mock_class) | ||
502 | 851 | for name, obj in getmembers(mock_class): | ||
503 | 852 | if ismethod(obj): | ||
504 | 853 | for base_class in base_classes[1:]: | ||
505 | 854 | if name in base_class.__dict__: | ||
506 | 855 | mock_args = getargspec(obj) | ||
507 | 856 | real_args = getargspec(base_class.__dict__[name]) | ||
508 | 857 | if mock_args != real_args: | ||
509 | 858 | raise AssertionError( | ||
510 | 859 | 'Different method signature for %s: %r %r' % ( | ||
511 | 860 | name, mock_args, real_args)) | ||
512 | 861 | else: | ||
513 | 862 | break | ||
514 | 796 | 863 | ||
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 | 1 | # Copyright 2009 Canonical Ltd. This software is licensed under the | ||
520 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
521 | 3 | |||
522 | 4 | """Run the doc string tests.""" | ||
523 | 5 | |||
524 | 6 | import doctest | ||
525 | 7 | |||
526 | 8 | from zope.testing.doctest import NORMALIZE_WHITESPACE, ELLIPSIS | ||
527 | 9 | |||
528 | 10 | from canonical.launchpad.testing.systemdocs import LayeredDocFileSuite | ||
529 | 11 | from canonical.testing import BaseLayer | ||
530 | 12 | from lp import testing | ||
531 | 13 | |||
532 | 14 | def test_suite(): | ||
533 | 15 | suite = LayeredDocFileSuite( | ||
534 | 16 | layer=BaseLayer) | ||
535 | 17 | suite.addTest(doctest.DocTestSuite( | ||
536 | 18 | testing, optionflags=NORMALIZE_WHITESPACE|ELLIPSIS)) | ||
537 | 19 | return suite | ||
538 | 20 |
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.