Merge lp:~adeuring/launchpad/hwdb-class-udev-device-2 into lp:launchpad

Proposed by Abel Deuring
Status: Merged
Approved by: Barry Warsaw
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~adeuring/launchpad/hwdb-class-udev-device-2
Merge into: lp:launchpad
Diff against target: 456 lines
3 files modified
lib/canonical/launchpad/scripts/hwdbsubmissions.py (+109/-4)
lib/canonical/launchpad/scripts/tests/test_hwdb_submission_parser.py (+150/-7)
lib/canonical/launchpad/scripts/tests/test_hwdb_submission_processing.py (+54/-0)
To merge this branch: bzr merge lp:~adeuring/launchpad/hwdb-class-udev-device-2
Reviewer Review Type Date Requested Status
Barry Warsaw (community) Approve
Review via email: mp+13118@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Abel Deuring (adeuring) wrote :

This branch adds a few USB-related properties to class UdevDevice, which will be used in the script hwdbsubmisison.py to process HWDB submission coming from the client in Karmic.

The branch also adds some sanity checks for USB-related data, to ensure that required properties exist and that their values have the correct format.

tests:

./bin/test --test=test_hwdb_submission_parser/bin/test --test=test_hwdb_submission_processing

= 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
  lib/canonical/launchpad/scripts/tests/test_hwdb_submission_processing.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)

These complaints are not not related to my cheanges; the come from a branch which prepares LP for Python 2.5

Revision history for this message
Barry Warsaw (barry) wrote :
Download full text (19.7 KiB)

Hi Abel,

A rather terse review from me today, since I'm also the CHR. I have some
comments that should be easy to address. r=me, merge-conditional with their
consideration.

 review approve
 status approve

-Barry

=== modified file 'lib/canonical/launchpad/scripts/hwdbsubmissions.py'
--- lib/canonical/launchpad/scripts/hwdbsubmissions.py 2009-10-08 13:41:32 +0000
+++ lib/canonical/launchpad/scripts/hwdbsubmissions.py 2009-10-09 12:10:24 +0000
> @@ -1212,11 +1212,83 @@
> return False
> return True
>
> + USB_DEVICE_PROPERTIES = set(('DEVTYPE', 'PRODUCT', 'TYPE'))
> + usb_product_re = re.compile(
> + '^[0-9a-f]{1,4}/[0-9a-f]{1,4}/[0-9a-f]{1,4}$', re.I)
> + usb_type_re = re.compile('^[0-9]{1,3}/[0-9]{1,3}/[0-9]{1,3}$')

Is there a reason these are class attributes? They probably make more sense
being module globals, with all-caps names.

> +
> + def checkUdevUsbProperties(self, udev_data):
> + """Validation of udev USB devices.
> +
> + USB devices must have the properties DEVTYPE (value
> + 'usb_device' or 'usb_interface'), PRODUCT and TYPE. PRODUCT
> + must be a tuple of three integers in hexadecimal
> + representation, separates by '/'. TYPE must be a a tuple of
> + three integers in decimal representation, separated by '/'.
> + usb_interface nodes must additionally have a property
> + INTERFACE, containing three integers in the same format as
> + TYPE.
> + """
> + for device in udev_data:
> + subsystem = device['E'].get('SUBSYSTEM')
> + if subsystem != 'usb':
> + continue
> + properties = device['E']
> + property_names = set(properties.keys())

Since 'properties' is a dictionary, this is more efficient:

            property_names = set(properties)

> + existing_usb_properties = property_names.intersection(
> + self.USB_DEVICE_PROPERTIES)
> + if existing_usb_properties != self.USB_DEVICE_PROPERTIES:
> + self._logError(
> + 'USB udev device found without required properties: %r %r'
> + % (self.USB_DEVICE_PROPERTIES.difference(
> + existing_usb_properties),
> + device['P']),

This is somewhat unreadable. You should move the .difference() calculation to
above the self._logError() call and stash it in a local variable, then use the
local variable in the interpolation.

> + self.submission_key)
> + return False
> + if self.usb_product_re.search(properties['PRODUCT']) is None:
> + self._logError(
> + 'USB udev device found with invalid product ID: %r %r'
> + % (properties['PRODUCT'], device['P']),
> + self.submission_key)
> + return False
> + if self.usb_type_re.search(properties['TYPE']) is None:
> + self._logError(
> + 'USB udev device found with invalid type data: %r %r'
> + % (properties['TYPE'], device['P']),
> + ...

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/scripts/hwdbsubmissions.py'
2--- lib/canonical/launchpad/scripts/hwdbsubmissions.py 2009-10-08 13:41:32 +0000
3+++ lib/canonical/launchpad/scripts/hwdbsubmissions.py 2009-10-09 15:45:23 +0000
4@@ -94,6 +94,11 @@
5 'scsi': '%-16s',
6 }
7
8+UDEV_USB_DEVICE_PROPERTIES = set(('DEVTYPE', 'PRODUCT', 'TYPE'))
9+UDEV_USB_PRODUCT_RE = re.compile(
10+ '^[0-9a-f]{1,4}/[0-9a-f]{1,4}/[0-9a-f]{1,4}$', re.I)
11+UDEV_USB_TYPE_RE = re.compile('^[0-9]{1,3}/[0-9]{1,3}/[0-9]{1,3}$')
12+
13 class SubmissionParser(object):
14 """A Parser for the submissions to the hardware database."""
15
16@@ -1165,7 +1170,7 @@
17 """
18 for device in udev_data:
19 properties = device['E']
20- property_names = set(properties.keys())
21+ property_names = set(properties)
22 existing_pci_properties = property_names.intersection(
23 self.PCI_PROPERTIES)
24 subsystem = device['E'].get('SUBSYSTEM')
25@@ -1212,11 +1217,78 @@
26 return False
27 return True
28
29+ def checkUdevUsbProperties(self, udev_data):
30+ """Validation of udev USB devices.
31+
32+ USB devices must have the properties DEVTYPE (value
33+ 'usb_device' or 'usb_interface'), PRODUCT and TYPE. PRODUCT
34+ must be a tuple of three integers in hexadecimal
35+ representation, separates by '/'. TYPE must be a a tuple of
36+ three integers in decimal representation, separated by '/'.
37+ usb_interface nodes must additionally have a property
38+ INTERFACE, containing three integers in the same format as
39+ TYPE.
40+ """
41+ for device in udev_data:
42+ subsystem = device['E'].get('SUBSYSTEM')
43+ if subsystem != 'usb':
44+ continue
45+ properties = device['E']
46+ property_names = set(properties)
47+ existing_usb_properties = property_names.intersection(
48+ UDEV_USB_DEVICE_PROPERTIES)
49+ if existing_usb_properties != UDEV_USB_DEVICE_PROPERTIES:
50+ missing_properties = UDEV_USB_DEVICE_PROPERTIES.difference(
51+ existing_usb_properties)
52+ self._logError(
53+ 'USB udev device found without required properties: %r %r'
54+ % (missing_properties, device['P']),
55+ self.submission_key)
56+ return False
57+ if UDEV_USB_PRODUCT_RE.search(properties['PRODUCT']) is None:
58+ self._logError(
59+ 'USB udev device found with invalid product ID: %r %r'
60+ % (properties['PRODUCT'], device['P']),
61+ self.submission_key)
62+ return False
63+ if UDEV_USB_TYPE_RE.search(properties['TYPE']) is None:
64+ self._logError(
65+ 'USB udev device found with invalid type data: %r %r'
66+ % (properties['TYPE'], device['P']),
67+ self.submission_key)
68+ return False
69+
70+ device_type = properties['DEVTYPE']
71+ if device_type not in ('usb_device', 'usb_interface'):
72+ self._logError(
73+ 'USB udev device found with invalid udev type data: %r %r'
74+ % (device_type, device['P']),
75+ self.submission_key)
76+ return False
77+ if device_type == 'usb_interface':
78+ interface_type = properties.get('INTERFACE')
79+ if interface_type is None:
80+ self._logError(
81+ 'USB interface udev device found without INTERFACE '
82+ 'property: %r'
83+ % device['P'],
84+ self.submission_key)
85+ return False
86+ if UDEV_USB_TYPE_RE.search(interface_type) is None:
87+ self._logError(
88+ 'USB Interface udev device found with invalid '
89+ 'INTERFACE property: %r %r'
90+ % (interface_type, device['P']),
91+ self.submission_key)
92+ return False
93+ return True
94+
95 def checkConsistentUdevDeviceData(self, udev_data):
96 """Consistency checks for udev data."""
97- if not self.checkUdevDictsHavePathKey(udev_data):
98- return False
99- return self.checkUdevPciProperties(udev_data)
100+ return (
101+ self.checkUdevDictsHavePathKey(udev_data) and
102+ self.checkUdevPciProperties(udev_data) and
103+ self.checkUdevUsbProperties(udev_data))
104
105 def checkConsistency(self, parsed_data):
106 """Run consistency checks on the submitted data.
107@@ -2278,6 +2350,39 @@
108 """See `BaseDevice`."""
109 return self.pci_class_info[1]
110
111+ @property
112+ def is_usb(self):
113+ """True, if this is a USB device, else False."""
114+ return self.udev['E'].get('SUBSYSTEM') == 'usb'
115+
116+ @property
117+ def usb_ids(self):
118+ """The vendor ID, product ID, product version for USB devices.
119+
120+ :return: [vendor_id, product_id, version] for USB devices
121+ or [None, None, None] for other devices.
122+ """
123+ if self.is_usb:
124+ # udev represents USB device IDs as strings
125+ # vendor_id/prodct_id/version, where each part is
126+ # as a hexdecimal number.
127+ # SubmissionParser.checkUdevUsbProperties() ensures that
128+ # the string PRODUCT is in the format required below.
129+ product_info = self.udev['E']['PRODUCT'].split('/')
130+ return [int(part, 16) for part in product_info]
131+ else:
132+ return [None, None, None]
133+
134+ @property
135+ def usb_vendor_id(self):
136+ """See `BaseDevice`."""
137+ return self.usb_ids[0]
138+
139+ @property
140+ def usb_product_id(self):
141+ """See `BaseDevice`."""
142+ return self.usb_ids[1]
143+
144
145 class ProcessingLoop(object):
146 """An `ITunableLoop` for processing HWDB submissions."""
147
148=== modified file 'lib/canonical/launchpad/scripts/tests/test_hwdb_submission_parser.py'
149--- lib/canonical/launchpad/scripts/tests/test_hwdb_submission_parser.py 2009-10-08 13:41:32 +0000
150+++ lib/canonical/launchpad/scripts/tests/test_hwdb_submission_parser.py 2009-10-09 15:45:23 +0000
151@@ -112,6 +112,25 @@
152 'PCI_SLOT_NAME': '0000:00:1f.2',
153 }
154 }
155+ self.udev_usb_device = {
156+ 'P': '/devices/pci0000:00/0000:00:1d.1/usb3/3-2',
157+ 'E': {
158+ 'SUBSYSTEM': 'usb',
159+ 'DEVTYPE': 'usb_device',
160+ 'PRODUCT': '46d/a01/1013',
161+ 'TYPE': '0/0/0',
162+ },
163+ }
164+ self.udev_usb_interface = {
165+ 'P': '/devices/pci0000:00/0000:00:1d.1/usb3/3-2/3-2:1.1',
166+ 'E': {
167+ 'SUBSYSTEM': 'usb',
168+ 'DEVTYPE': 'usb_interface',
169+ 'PRODUCT': '46d/a01/1013',
170+ 'TYPE': '0/0/0',
171+ 'INTERFACE': '1/2/0',
172+ },
173+ }
174
175 def getTimestampETreeNode(self, time_string):
176 """Return an Elementtree node for an XML tag with a timestamp."""
177@@ -1788,7 +1807,7 @@
178 parser.submission_key, 'udev node found without a "P" key')
179
180 def testCheckUdevPciProperties(self):
181- """Test of SubmmissionParser.checkUdevPciProperties()."""
182+ """Test of SubmissionParser.checkUdevPciProperties()."""
183 # udev PCI devices must have the properties PCI_CLASS, PCI_ID,
184 # PCI_SUBSYS_ID, PCI_SLOT_NAME; other devices must not have
185 # these properties.
186@@ -1797,7 +1816,7 @@
187 [self.udev_root_device, self.udev_pci_device]))
188
189 def testCheckUdevPciPropertiesNonPciDeviceWithPciProperties(self):
190- """Test of SubmmissionParser.checkUdevPciProperties().
191+ """Test of SubmissionParser.checkUdevPciProperties().
192
193 A non-PCI device having PCI properties makes a submission invalid.
194 """
195@@ -1812,7 +1831,7 @@
196 "'/devices/LNXSYSTM:00'")
197
198 def testCheckUdevPciPropertiesPciDeviceWithoutRequiredProperties(self):
199- """Test of SubmmissionParser.checkUdevPciProperties().
200+ """Test of SubmissionParser.checkUdevPciProperties().
201
202 A PCI device not having a required PCI property makes a submission
203 invalid.
204@@ -1828,7 +1847,7 @@
205 "set(['PCI_CLASS']) '/devices/pci0000:00/0000:00:1f.2'")
206
207 def testCheckUdevPciPropertiesPciDeviceWithNonIntegerPciClass(self):
208- """Test of SubmmissionParser.checkUdevPciProperties().
209+ """Test of SubmissionParser.checkUdevPciProperties().
210
211 A PCI device with a non-integer class value makes a submission
212 invalid.
213@@ -1844,7 +1863,7 @@
214 "'/devices/pci0000:00/0000:00:1f.2'")
215
216 def testCheckUdevPciPropertiesPciDeviceWithInvalidPciClassValue(self):
217- """Test of SubmmissionParser.checkUdevPciProperties().
218+ """Test of SubmissionParser.checkUdevPciProperties().
219
220 A PCI device with invalid class data makes a submission
221 invalid.
222@@ -1860,7 +1879,7 @@
223 "'/devices/pci0000:00/0000:00:1f.2'")
224
225 def testCheckUdevPciPropertiesPciDeviceWithInvalidDeviceID(self):
226- """Test of SubmmissionParser.checkUdevPciProperties().
227+ """Test of SubmissionParser.checkUdevPciProperties().
228
229 A PCI device with an invalid device ID makes a submission
230 invalid.
231@@ -1876,7 +1895,7 @@
232 "'/devices/pci0000:00/0000:00:1f.2'")
233
234 def testCheckUdevPciPropertiesPciDeviceWithInvalidSubsystemID(self):
235- """Test of SubmmissionParser.checkUdevPciProperties().
236+ """Test of SubmissionParser.checkUdevPciProperties().
237
238 A PCI device with an invalid subsystem ID makes a submission
239 invalid.
240@@ -1891,6 +1910,110 @@
241 "Invalid udev PCI device ID: 'not-a-subsystem-id' "
242 "'/devices/pci0000:00/0000:00:1f.2'")
243
244+ def testCheckUdevUsbProperties(self):
245+ """Test of SubmissionParser.checkUdevUsbProperties()."""
246+ parser = SubmissionParser()
247+ self.assertTrue(parser.checkUdevUsbProperties(
248+ [self.udev_root_device, self.udev_usb_device,
249+ self.udev_usb_interface]))
250+
251+ def testCheckUdevUsbProperties_missing_required_property(self):
252+ """Test of SubmissionParser.checkUdevUsbProperties().
253+
254+ A USB device that does not have a required property makes a
255+ submission invalid.
256+ """
257+ del self.udev_usb_device['E']['DEVTYPE']
258+ parser = SubmissionParser(self.log)
259+ parser.submission_key = 'USB device without DEVTYPE property'
260+ self.assertFalse(parser.checkUdevUsbProperties(
261+ [self.udev_root_device, self.udev_usb_device]))
262+ self.assertErrorMessage(
263+ parser.submission_key,
264+ "USB udev device found without required properties: "
265+ "set(['DEVTYPE']) '/devices/pci0000:00/0000:00:1d.1/usb3/3-2'")
266+
267+ def testCheckUdevUsbProperties_with_invalid_product_id(self):
268+ """Test of SubmissionParser.checkUdevUsbProperties().
269+
270+ A USB device with an invalid product ID makes a submission
271+ invalid.
272+ """
273+ self.udev_usb_device['E']['PRODUCT'] = 'not-a-valid-usb-product-id'
274+ parser = SubmissionParser(self.log)
275+ parser.submission_key = 'USB device with invalid product ID'
276+ self.assertFalse(parser.checkUdevUsbProperties(
277+ [self.udev_root_device, self.udev_usb_device]))
278+ self.assertErrorMessage(
279+ parser.submission_key,
280+ "USB udev device found with invalid product ID: "
281+ "'not-a-valid-usb-product-id' "
282+ "'/devices/pci0000:00/0000:00:1d.1/usb3/3-2'")
283+
284+ def testCheckUdevUsbProperties_with_invalid_type_data(self):
285+ """Test of SubmmissionParser.checkUdevUsbProperties().
286+
287+ A USB device with invalid type data makes a submission invalid.
288+ """
289+ self.udev_usb_device['E']['TYPE'] = 'no-type'
290+ parser = SubmissionParser(self.log)
291+ parser.submission_key = 'USB device with invalid type data'
292+ self.assertFalse(parser.checkUdevUsbProperties(
293+ [self.udev_root_device, self.udev_usb_device]))
294+ self.assertErrorMessage(
295+ parser.submission_key,
296+ "USB udev device found with invalid type data: 'no-type' "
297+ "'/devices/pci0000:00/0000:00:1d.1/usb3/3-2'")
298+
299+ def testCheckUdevUsbProperties_with_invalid_devtype(self):
300+ """Test of SubmmissionParser.checkUdevUsbProperties().
301+
302+ A udev USB device must have DEVTYPE set to 'usb_device' or
303+ 'usb_interface'.
304+ """
305+ self.udev_usb_device['E']['DEVTYPE'] = 'nonsense'
306+ parser = SubmissionParser(self.log)
307+ parser.submission_key = 'USB device with invalid DEVTYPE'
308+ self.assertFalse(parser.checkUdevUsbProperties(
309+ [self.udev_root_device, self.udev_usb_device]))
310+ self.assertErrorMessage(
311+ parser.submission_key,
312+ "USB udev device found with invalid udev type data: 'nonsense' "
313+ "'/devices/pci0000:00/0000:00:1d.1/usb3/3-2'")
314+
315+ def testCheckUdevUsbProperties_interface_without_interface_property(self):
316+ """Test of SubmmissionParser.checkUdevUsbProperties().
317+
318+ A udev USB device for a USB interface have the property INTERFACE.
319+ """
320+ del self.udev_usb_interface['E']['INTERFACE']
321+ parser = SubmissionParser(self.log)
322+ parser.submission_key = 'USB interface without INTERFACE property'
323+ self.assertFalse(parser.checkUdevUsbProperties(
324+ [self.udev_root_device, self.udev_usb_interface]))
325+ self.assertErrorMessage(
326+ parser.submission_key,
327+ "USB interface udev device found without INTERFACE property: "
328+ "'/devices/pci0000:00/0000:00:1d.1/usb3/3-2/3-2:1.1'")
329+
330+ def testCheckUdevUsbProperties_interface_invalid_interface_property(self):
331+ """Test of SubmmissionParser.checkUdevUsbProperties().
332+
333+ The INTERFACE proeprty of A udev USB device for a USB interface
334+ must have value in the format main_class/sub_class/version
335+ """
336+ self.udev_usb_interface['E']['INTERFACE'] = 'nonsense'
337+ parser = SubmissionParser(self.log)
338+ parser.submission_key = 'USB interface with invalid INTERFACE data'
339+ self.assertFalse(parser.checkUdevUsbProperties(
340+ [self.udev_root_device, self.udev_usb_interface]))
341+ self.assertErrorMessage(
342+ parser.submission_key,
343+ "USB Interface udev device found with invalid INTERFACE "
344+ "property: 'nonsense' "
345+ "'/devices/pci0000:00/0000:00:1d.1/usb3/3-2/3-2:1.1'")
346+
347+
348 class UdevTestSubmissionParser(SubmissionParser):
349 """A variant of SubmissionParser that shortcuts udev related tests.
350
351@@ -1904,6 +2027,10 @@
352 """See `SubmissionParser`."""
353 return True
354
355+ def checkUdevUsbProperties(self, udev_data):
356+ """See `SubmissionParser`."""
357+ return True
358+
359 def testCheckConsistentUdevDeviceData(self):
360 """Test of SubmissionParser.checkConsistentUdevDeviceData(),"""
361 parser = self.UdevTestSubmissionParser()
362@@ -1941,6 +2068,22 @@
363 parser = SubmissionParserUdevPciCheckFails()
364 self.assertFalse(parser.checkConsistentUdevDeviceData(None))
365
366+ def testCheckConsistentUdevDeviceData_invalid_usb_data(self):
367+ """Test of SubmissionParser.checkConsistentUdevDeviceData(),
368+
369+ Detection of invalid PCI data lets the check fail.
370+ """
371+ class SubmissionParserUdevUsbCheckFails(
372+ self.UdevTestSubmissionParser):
373+ """A SubmissionPaser where checkUdevPciProperties() fails."""
374+
375+ def checkUdevUsbProperties(self, udev_data):
376+ """See `SubmissionParser`."""
377+ return False
378+
379+ parser = SubmissionParserUdevUsbCheckFails()
380+ self.assertFalse(parser.checkConsistentUdevDeviceData(None))
381+
382 def _setupConsistencyCheckParser(self):
383 """Prepare and return a SubmissionParser instance.
384
385
386=== modified file 'lib/canonical/launchpad/scripts/tests/test_hwdb_submission_processing.py'
387--- lib/canonical/launchpad/scripts/tests/test_hwdb_submission_processing.py 2009-10-08 13:41:32 +0000
388+++ lib/canonical/launchpad/scripts/tests/test_hwdb_submission_processing.py 2009-10-09 15:45:23 +0000
389@@ -2617,6 +2617,16 @@
390 }
391 }
392
393+ usb_device_data = {
394+ 'P': '/devices/pci0000:00/0000:00:1d.1/usb3/3-2',
395+ 'E': {
396+ 'SUBSYSTEM': 'usb',
397+ 'DEVTYPE': 'usb_device',
398+ 'PRODUCT': '46d/a01/1013',
399+ 'TYPE': '0/0/0',
400+ },
401+ }
402+
403 def test_device_id(self):
404 """Test of UdevDevice.device_id."""
405 device = UdevDevice(self.pci_device_data, None, None)
406@@ -2668,6 +2678,50 @@
407 None, device.pci_class,
408 'Invalid value of UdevDevice.pci_class for Non-PCI device.')
409
410+ def test_is_usb(self):
411+ """Test of UdevDevice.is_usb"""
412+ device = UdevDevice(self.usb_device_data, None, None)
413+ self.assertTrue(device.is_usb)
414+
415+ device = UdevDevice(self.pci_device_data, None, None)
416+ self.assertFalse(device.is_usb)
417+
418+ def test_usb_ids(self):
419+ """Test of UdevDevice.usb_ids"""
420+ device = UdevDevice(self.usb_device_data, None, None)
421+ self.assertEqual(
422+ [0x46d, 0xa01, 0x1013], device.usb_ids,
423+ 'Invalid value of UdevDevice.usb_ids for USB device.')
424+
425+ device = UdevDevice(self.root_device, None, None)
426+ self.assertEqual(
427+ [None, None, None], device.usb_ids,
428+ 'Invalid value of UdevDevice.usb_ids for Non-USB device.')
429+
430+ def test_usb_vendor_id(self):
431+ """Test of UdevDevice.usb_vendor_id"""
432+ device = UdevDevice(self.usb_device_data, None, None)
433+ self.assertEqual(
434+ 0x46d, device.usb_vendor_id,
435+ 'Invalid value of UdevDevice.usb_vendor_id for USB device.')
436+
437+ device = UdevDevice(self.root_device, None, None)
438+ self.assertEqual(
439+ None, device.usb_vendor_id,
440+ 'Invalid value of UdevDevice.usb_vendor_id for Non-USB device.')
441+
442+ def test_usb_product_id(self):
443+ """Test of UdevDevice.usb_product_id"""
444+ device = UdevDevice(self.usb_device_data, None, None)
445+ self.assertEqual(
446+ 0xa01, device.usb_product_id,
447+ 'Invalid value of UdevDevice.usb_product_id for USB device.')
448+
449+ device = UdevDevice(self.root_device, None, None)
450+ self.assertEqual(
451+ None, device.usb_product_id,
452+ 'Invalid value of UdevDevice.usb_product_id for Non-USB device.')
453+
454
455 class TestHWDBSubmissionTablePopulation(TestCaseHWDB):
456 """Tests of the HWDB popoluation with submitted data."""