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

Proposed by Abel Deuring
Status: Merged
Merged at revision: not available
Proposed branch: lp:~adeuring/launchpad/hwdb-class-udev-device-6
Merge into: lp:launchpad
Diff against target: 849 lines
2 files modified
lib/canonical/launchpad/scripts/hwdbsubmissions.py (+61/-20)
lib/canonical/launchpad/scripts/tests/test_hwdb_submission_processing.py (+582/-70)
To merge this branch: bzr merge lp:~adeuring/launchpad/hwdb-class-udev-device-6
Reviewer Review Type Date Requested Status
Michael Nelson (community) code Approve
Review via email: mp+13402@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Abel Deuring (adeuring) wrote :

This branch adds a property scsi_controller to class UdevDevice in the script hwdbsubmissons.py.

Aside from tests of this property, I also added tests for UdevDevice.translateScsiBus(). While this method is defined in class BaseDevice, from which UdevDevice is derived, is accesses the property scsi_controller, so some tests seemed reasonable. (class UdevDevice represents a device mentioned in a submission made by Karmic's HWDB client, while submissions from older Ubuntu versions contain data from HAL; these devices are represnted by class HALDevice.)

Handling of SCSI devices in HWDB submissions is a bit convoluted: Many non-SCSI storage devices, like IDE/SATA disks and CD drives and USB storage devices, support the SCSI command set, and the Linux kernel treats them like real SCSI devices. The data stored about a device includes the bus used to connect the device to a computer, so we want to know the real, physical bus of a device, instead of treating nearly all storage devices as SCSI devices.

We can detect the real bus a of (real or fake) SCSI device by inspecting the type of the (real or fake) SCSI controller. This is done by BaseDevice.translateScsiBus(). The core logic of this method is the same for submissions containing HAL and udev data, so it is located in class BaseDevice. The difference between HAL and udev data is that the controller of a SCSI device in HAL is the grandparent of the SCSI device, while it is the grand-grand-grand-parent in udev, hence the property scsi_controller is differently implemented in HALDevice and UdevDevice.

In order to setup a test of UdevDevice.scsi_controller or UdevDevice.translateScsiBus, we need to create five UdevDevices, so I added a method buildUdevDeviceHierarchy() to class TestUdevDevice.

I also changed the base class of TestUdevDevice from TestCase to testCaseHWDB: The latter defines some infrastructure to assert that expected warnings or error messages really appear.

test: ./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_processing.py

== Pyflakes notices ==

lib/canonical/launchpad/scripts/hwdbsubmissions.py
    22: redefinition of unused 'etree' from line 20

== Pylint notices ==

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

This complaint is not related to my changes.

Revision history for this message
Michael Nelson (michael.nelson) wrote :
Download full text (27.5 KiB)

> This branch adds a property scsi_controller to class UdevDevice in the script
> hwdbsubmissons.py.

Hi Abel. Again, thanks for the details about this branch - it's very helpful to have such a clear explanation when the topic is quite foreign (at least, to me!).

I didn't have anything other than a few questions (see below). Great work!

[snip]

> === modified file 'lib/canonical/launchpad/scripts/hwdbsubmissions.py'
> --- lib/canonical/launchpad/scripts/hwdbsubmissions.py 2009-10-13 21:03:31 +0000
> +++ lib/canonical/launchpad/scripts/hwdbsubmissions.py 2009-10-15 10:53:08 +0000
[snip]
> @@ -2617,6 +2635,29 @@
> """See `BaseDevice`."""
> return self.getVendorOrProductID('product')
>
> + @property
> + def driver_name(self):
> + """See `BaseDevice`."""
> + return self.udev['E'].get('DRIVER')
> +
> + @property
> + def scsi_controller(self):
> + """See `BaseDevice`."""
> + if self.raw_bus != 'scsi_device':
> + return None
> +
> + current_device = self
> + for ancestor_level in range(1, 5):
> + if current_device.parent is None:
> + # While SCSI devices from valid submissions should have a
> + # parent and a grandparent, we can't be sure for bogus or
> + # broken submissions.
> + self.parser._logWarning(
> + 'Found a SCSI device without a sufficient number of '
> + 'ancestors: %s' % self.device_id)
> + return None
> + current_device = current_device.parent
> + return current_device

Nice!

Actually, if you're just presenting a general log warning, I wonder whether
doing something like the following would be more readable?
    try:
        controller = self.parent.parent.parent.parent
    except AttributeError:
        return None

Up to you.

>
> class ProcessingLoop(object):
> """An `ITunableLoop` for processing HWDB submissions."""
>
> === modified file 'lib/canonical/launchpad/scripts/tests/test_hwdb_submission_processing.py'
> --- lib/canonical/launchpad/scripts/tests/test_hwdb_submission_processing.py 2009-10-13 21:02:35 +0000
> +++ lib/canonical/launchpad/scripts/tests/test_hwdb_submission_processing.py 2009-10-15 10:53:08 +0000
> @@ -539,6 +539,214 @@
> 'HAL property info.bus.')
>
>
> + def test_HALDevice_scsi_controller_usb_storage_device(self):
> + """test of HALDevice.scsi_controller.
> +
> + The physical device is a USB storage device.
> + """
> + devices = [
> + # The main node of the USB storage device.
> + {
> + 'id': 1,
> + 'udi': self.UDI_USB_STORAGE,
> + 'properties': {
> + 'info.bus': ('usb_device', 'str'),
> + },
> + },

Just a style question (the answer to which I'm not certain), based on:

https://dev.launchpad.net/PythonStyleGuide#Multiline%20braces

should this be:

        devices = [{
            # The main node of the USB storage device.
            'id': 1,
            'udi': self.UDI_USB_STORAGE,
            '...

review: Approve (code)
Revision history for this message
Abel Deuring (adeuring) wrote :
Download full text (45.5 KiB)

Hi Michael,

thanks for your review!

On 15.10.2009 13:54, Michael Nelson wrote:
> Review: Approve code
>> This branch adds a property scsi_controller to class UdevDevice in the script
>> hwdbsubmissons.py.
>
> Hi Abel. Again, thanks for the details about this branch - it's very helpful to have such a clear explanation when the topic is quite foreign (at least, to me!).
>
> I didn't have anything other than a few questions (see below). Great work!
>
> [snip]
>
>> === modified file 'lib/canonical/launchpad/scripts/hwdbsubmissions.py'
>> --- lib/canonical/launchpad/scripts/hwdbsubmissions.py 2009-10-13 21:03:31 +0000
>> +++ lib/canonical/launchpad/scripts/hwdbsubmissions.py 2009-10-15 10:53:08 +0000
> [snip]
>> @@ -2617,6 +2635,29 @@
>> """See `BaseDevice`."""
>> return self.getVendorOrProductID('product')
>>
>> + @property
>> + def driver_name(self):
>> + """See `BaseDevice`."""
>> + return self.udev['E'].get('DRIVER')
>> +
>> + @property
>> + def scsi_controller(self):
>> + """See `BaseDevice`."""
>> + if self.raw_bus != 'scsi_device':
>> + return None
>> +
>> + current_device = self
>> + for ancestor_level in range(1, 5):
>> + if current_device.parent is None:
>> + # While SCSI devices from valid submissions should have a
>> + # parent and a grandparent, we can't be sure for bogus or
>> + # broken submissions.
>> + self.parser._logWarning(
>> + 'Found a SCSI device without a sufficient number of '
>> + 'ancestors: %s' % self.device_id)
>> + return None
>> + current_device = current_device.parent
>> + return current_device
>
> Nice!
>
> Actually, if you're just presenting a general log warning, I wonder whether
> doing something like the following would be more readable?
> try:
> controller = self.parent.parent.parent.parent
> except AttributeError:
> return None
>
> Up to you.

Right, that's a bit simpler and beter eadable. Changed.

>
>>
>> class ProcessingLoop(object):
>> """An `ITunableLoop` for processing HWDB submissions."""
>>
>> === modified file 'lib/canonical/launchpad/scripts/tests/test_hwdb_submission_processing.py'
>> --- lib/canonical/launchpad/scripts/tests/test_hwdb_submission_processing.py 2009-10-13 21:02:35 +0000
>> +++ lib/canonical/launchpad/scripts/tests/test_hwdb_submission_processing.py 2009-10-15 10:53:08 +0000
>> @@ -539,6 +539,214 @@
>> 'HAL property info.bus.')
>>
>>
>> + def test_HALDevice_scsi_controller_usb_storage_device(self):
>> + """test of HALDevice.scsi_controller.
>> +
>> + The physical device is a USB storage device.
>> + """
>> + devices = [
>> + # The main node of the USB storage device.
>> + {
>> + 'id': 1,
>> + 'udi': self.UDI_USB_STORAGE,
>> + 'properties': {
>> + 'info.bus': ('usb_device', 'str'),
>> + },
>> + },
>
> Just a style question (the answer to whic...

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-13 21:03:31 +0000
3+++ lib/canonical/launchpad/scripts/hwdbsubmissions.py 2009-10-15 13:25:24 +0000
4@@ -87,12 +87,14 @@
5 'pci': '0x%04x',
6 'usb_device': '0x%04x',
7 'scsi': '%-8s',
8+ 'scsi_device': '%-8s',
9 }
10
11 DB_FORMAT_FOR_PRODUCT_ID = {
12 'pci': '0x%04x',
13 'usb_device': '0x%04x',
14 'scsi': '%-16s',
15+ 'scsi_device': '%-16s',
16 }
17
18 UDEV_USB_DEVICE_PROPERTIES = set(('DEVTYPE', 'PRODUCT', 'TYPE'))
19@@ -1609,6 +1611,11 @@
20 """The name of the driver contolling this device. May be None."""
21 raise NotImplementedError
22
23+ @property
24+ def scsi_controller(self):
25+ """Return the SCSI host controller for this device."""
26+ raise NotImplementedError
27+
28 def translateScsiBus(self):
29 """Return the real bus of a device where raw_bus=='scsi'.
30
31@@ -1617,37 +1624,27 @@
32 for more details. This method determines the real bus
33 of a device accessed via the kernel's SCSI subsystem.
34 """
35- # While SCSI devices from valid submissions should have a
36- # parent and a grandparent, we can't be sure for bogus or
37- # broken submissions.
38- parent = self.parent
39- if parent is None:
40- self.parser._logWarning(
41- 'Found SCSI device without a parent: %s.' % self.device_id)
42- return None
43- grandparent = parent.parent
44- if grandparent is None:
45- self.parser._logWarning(
46- 'Found SCSI device without a grandparent: %s.'
47- % self.device_id)
48+ scsi_controller = self.scsi_controller
49+ if scsi_controller is None:
50 return None
51
52- grandparent_bus = grandparent.raw_bus
53- if grandparent_bus == 'pci':
54- if (grandparent.pci_class != PCI_CLASS_STORAGE):
55+ scsi_controller_bus = scsi_controller.raw_bus
56+ if scsi_controller_bus == 'pci':
57+ if (scsi_controller.pci_class != PCI_CLASS_STORAGE):
58 # This is not a storage class PCI device? This
59 # indicates a bug somewhere in HAL or in the hwdb
60 # client, or a fake submission.
61- device_class = grandparent.pci_class
62+ device_class = scsi_controller.pci_class
63 self.parser._logWarning(
64 'A (possibly fake) SCSI device %s is connected to '
65 'PCI device %s that has the PCI device class %s; '
66 'expected class 1 (storage).'
67- % (self.device_id, grandparent.device_id, device_class))
68+ % (self.device_id, scsi_controller.device_id,
69+ device_class))
70 return None
71- pci_subclass = grandparent.pci_subclass
72+ pci_subclass = scsi_controller.pci_subclass
73 return self.pci_storage_subclass_hwbus.get(pci_subclass)
74- elif grandparent_bus == 'usb':
75+ elif scsi_controller_bus == 'usb':
76 # USB storage devices have the following HAL device hierarchy:
77 # - HAL node for the USB device. info.bus == 'usb_device',
78 # device class == 0, device subclass == 0
79@@ -2357,6 +2354,27 @@
80 """See `BaseDevice`."""
81 return self.getVendorOrProductID('product')
82
83+ @property
84+ def scsi_controller(self):
85+ """See `BaseDevice`."""
86+ # While SCSI devices from valid submissions should have a
87+ # parent and a grandparent, we can't be sure for bogus or
88+ # broken submissions.
89+ if self.raw_bus != 'scsi':
90+ return None
91+ parent = self.parent
92+ if parent is None:
93+ self.parser._logWarning(
94+ 'Found SCSI device without a parent: %s.' % self.device_id)
95+ return None
96+ grandparent = parent.parent
97+ if grandparent is None:
98+ self.parser._logWarning(
99+ 'Found SCSI device without a grandparent: %s.'
100+ % self.device_id)
101+ return None
102+ return grandparent
103+
104
105 class UdevDevice(BaseDevice):
106 """The representation of a udev device node."""
107@@ -2617,6 +2635,29 @@
108 """See `BaseDevice`."""
109 return self.getVendorOrProductID('product')
110
111+ @property
112+ def driver_name(self):
113+ """See `BaseDevice`."""
114+ return self.udev['E'].get('DRIVER')
115+
116+ @property
117+ def scsi_controller(self):
118+ """See `BaseDevice`."""
119+ if self.raw_bus != 'scsi_device':
120+ return None
121+
122+ # While SCSI devices from valid submissions should have four
123+ # ancestors, we can't be sure for bogus or broken submissions.
124+ try:
125+ controller = self.parent.parent.parent.parent
126+ except AttributeError:
127+ controller = None
128+ if controller is None:
129+ self.parser._logWarning(
130+ 'Found a SCSI device without a sufficient number of '
131+ 'ancestors: %s' % self.device_id)
132+ return None
133+ return controller
134
135 class ProcessingLoop(object):
136 """An `ITunableLoop` for processing HWDB submissions."""
137
138=== modified file 'lib/canonical/launchpad/scripts/tests/test_hwdb_submission_processing.py'
139--- lib/canonical/launchpad/scripts/tests/test_hwdb_submission_processing.py 2009-10-13 21:02:35 +0000
140+++ lib/canonical/launchpad/scripts/tests/test_hwdb_submission_processing.py 2009-10-15 13:25:24 +0000
141@@ -539,6 +539,214 @@
142 'HAL property info.bus.')
143
144
145+ def test_HALDevice_scsi_controller_usb_storage_device(self):
146+ """test of HALDevice.scsi_controller.
147+
148+ The physical device is a USB storage device.
149+ """
150+ devices = [
151+ # The main node of the USB storage device.
152+ {
153+ 'id': 1,
154+ 'udi': self.UDI_USB_STORAGE,
155+ 'properties': {
156+ 'info.bus': ('usb_device', 'str'),
157+ },
158+ },
159+ # The storage interface of the USB device.
160+ {
161+ 'id': 2,
162+ 'udi': self.UDI_USB_STORAGE_IF0,
163+ 'properties': {
164+ 'info.bus': ('usb', 'str'),
165+ 'info.parent': (self.UDI_USB_STORAGE, 'str'),
166+ },
167+ },
168+ # The fake SCSI host of the storage device. Note that HAL does
169+ # _not_ provide the info.bus property.
170+ {
171+ 'id': 3,
172+ 'udi': self.UDI_USB_STORAGE_SCSI_HOST,
173+ 'properties': {
174+ 'info.parent': (self.UDI_USB_STORAGE_IF0, 'str'),
175+ },
176+ },
177+ # The fake SCSI disk.
178+ {
179+ 'id': 3,
180+ 'udi': self.UDI_USB_STORAGE_SCSI_DEVICE,
181+ 'properties': {
182+ 'info.bus': ('scsi', 'str'),
183+ 'info.parent': (self.UDI_USB_STORAGE_SCSI_HOST, 'str'),
184+ },
185+ },
186+ ]
187+ parsed_data = {
188+ 'hardware': {
189+ 'hal': {
190+ 'devices': devices,
191+ },
192+ },
193+ }
194+
195+ parser = SubmissionParser()
196+ parser.buildDeviceList(parsed_data)
197+
198+ usb_fake_scsi_disk = parser.hal_devices[
199+ self.UDI_USB_STORAGE_SCSI_DEVICE]
200+ usb_main_device = parser.hal_devices[self.UDI_USB_STORAGE_IF0]
201+ self.assertEqual(usb_main_device, usb_fake_scsi_disk.scsi_controller)
202+
203+ def test_HALDevice_scsi_controller_pci_controller(self):
204+ """test of HALDevice.scsi_controller.
205+
206+ Variant for a SCSI device connected to a PCI controller.
207+ """
208+ devices = [
209+ # The PCI host controller.
210+ {
211+ 'id': 1,
212+ 'udi': self.UDI_SATA_CONTROLLER,
213+ 'properties': {
214+ 'info.bus': ('pci', 'str'),
215+ 'pci.device_class': (PCI_CLASS_STORAGE, 'int'),
216+ 'pci.device_subclass': (PCI_SUBCLASS_STORAGE_SATA,
217+ 'int'),
218+ },
219+ },
220+ # The (fake or real) SCSI host of the storage device.
221+ {
222+ 'id': 2,
223+ 'udi': self.UDI_SATA_CONTROLLER_SCSI,
224+ 'properties': {
225+ 'info.parent': (self.UDI_SATA_CONTROLLER, 'str'),
226+ },
227+ },
228+ # The (possibly fake) SCSI disk.
229+ {
230+ 'id': 3,
231+ 'udi': self.UDI_SATA_DISK,
232+ 'properties': {
233+ 'info.bus': ('scsi', 'str'),
234+ 'info.parent': (self.UDI_SATA_CONTROLLER_SCSI, 'str'),
235+ },
236+ },
237+ ]
238+ parsed_data = {
239+ 'hardware': {
240+ 'hal': {
241+ 'devices': devices,
242+ },
243+ },
244+ }
245+
246+ parser = SubmissionParser()
247+ parser.buildDeviceList(parsed_data)
248+
249+ scsi_device = parser.hal_devices[self.UDI_SATA_DISK]
250+ controller = parser.hal_devices[self.UDI_SATA_CONTROLLER]
251+ self.assertEqual(controller, scsi_device.scsi_controller)
252+
253+ def test_HALDevice_scsi_controller_non_scsi_device(self):
254+ """test of HALDevice.scsi_controller.
255+
256+ Variant for non-SCSI devices.
257+ """
258+ devices = [
259+ {
260+ 'id': 1,
261+ 'udi': self.UDI_COMPUTER,
262+ 'properties': {},
263+ },
264+ ]
265+ parsed_data = {
266+ 'hardware': {
267+ 'hal': {
268+ 'devices': devices,
269+ },
270+ },
271+ }
272+
273+ parser = SubmissionParser()
274+ parser.buildDeviceList(parsed_data)
275+
276+ device = parser.hal_devices[self.UDI_COMPUTER]
277+ self.assertEqual(None, device.scsi_controller)
278+
279+ def test_HALDevice_scsi_controller_no_grandparent(self):
280+ """test of HALDevice.scsi_controller.
281+
282+ Variant for a SCSI device without a grandparent device.
283+ """
284+ devices = [
285+ # The (fake or real) SCSI host of the storage device.
286+ {
287+ 'id': 1,
288+ 'udi': self.UDI_SATA_CONTROLLER_SCSI,
289+ 'properties': {},
290+ },
291+ # The (possibly fake) SCSI disk.
292+ {
293+ 'id': 2,
294+ 'udi': self.UDI_SATA_DISK,
295+ 'properties': {
296+ 'info.bus': ('scsi', 'str'),
297+ 'info.parent': (self.UDI_SATA_CONTROLLER_SCSI, 'str'),
298+ },
299+ },
300+ ]
301+ parsed_data = {
302+ 'hardware': {
303+ 'hal': {
304+ 'devices': devices,
305+ },
306+ },
307+ }
308+
309+ parser = SubmissionParser(self.log)
310+ parser.submission_key = 'SCSI device without grandparent device'
311+ parser.buildDeviceList(parsed_data)
312+
313+ scsi_device = parser.hal_devices[self.UDI_SATA_DISK]
314+ self.assertEqual(None, scsi_device.scsi_controller)
315+ self.assertWarningMessage(
316+ parser.submission_key,
317+ "Found SCSI device without a grandparent: %s."
318+ % self.UDI_SATA_DISK)
319+
320+ def test_HALDevice_scsi_controller_no_parent(self):
321+ """test of HALDevice.scsi_controller.
322+
323+ Variant for a SCSI device without a parent device.
324+ """
325+ devices = [
326+ # The (possibly fake) SCSI disk.
327+ {
328+ 'id': 1,
329+ 'udi': self.UDI_SATA_DISK,
330+ 'properties': {
331+ 'info.bus': ('scsi', 'str'),
332+ },
333+ },
334+ ]
335+ parsed_data = {
336+ 'hardware': {
337+ 'hal': {
338+ 'devices': devices,
339+ },
340+ },
341+ }
342+
343+ parser = SubmissionParser(self.log)
344+ parser.submission_key = 'SCSI device without parent device'
345+ parser.buildDeviceList(parsed_data)
346+
347+ scsi_device = parser.hal_devices[self.UDI_SATA_DISK]
348+ self.assertEqual(None, scsi_device.scsi_controller)
349+ self.assertWarningMessage(
350+ parser.submission_key,
351+ "Found SCSI device without a parent: %s." % self.UDI_SATA_DISK)
352+
353 def testHALDeviceGetRealBus(self):
354 """Test of HALDevice.real_bus, generic case.
355
356@@ -2585,65 +2793,241 @@
357 'property not treated as a real device.')
358
359
360-class TestUdevDevice(TestCase):
361+class TestUdevDevice(TestCaseHWDB):
362 """Tests of class UdevDevice."""
363
364- layer = BaseLayer
365-
366- root_device = {
367- 'P': '/devices/LNXSYSTM:00',
368- 'E': {
369- 'UDEV_LOG': '3',
370- 'DEVPATH': '/devices/LNXSYSTM:00',
371- 'MODALIAS': 'acpi:LNXSYSTM:',
372- 'SUBSYSTEM': 'acpi',
373- }
374- }
375-
376- root_device_dmi_data = {
377- '/sys/class/dmi/id/sys_vendor': 'FUJITSU SIEMENS',
378- '/sys/class/dmi/id/product_name': 'LIFEBOOK E8210',
379- }
380-
381- pci_device_data = {
382- 'P': '/devices/pci0000:00/0000:00:1f.2',
383- 'E': {
384- 'PCI_CLASS': '10602',
385- 'PCI_ID': '8086:27C5',
386- 'PCI_SUBSYS_ID': '10CF:1387',
387- 'PCI_SLOT_NAME': '0000:00:1f.2',
388- 'SUBSYSTEM': 'pci',
389- }
390- }
391-
392- usb_device_data = {
393- 'P': '/devices/pci0000:00/0000:00:1d.1/usb3/3-2',
394- 'E': {
395- 'SUBSYSTEM': 'usb',
396- 'DEVTYPE': 'usb_device',
397- 'PRODUCT': '46d/a01/1013',
398- 'TYPE': '0/0/0',
399- },
400- }
401-
402- scsi_device_data = {
403- 'P': '/devices/pci0000:00/0000:00:1f.1/host4/target4:0:0/4:0:0:0',
404- 'E': {
405- 'SUBSYSTEM': 'scsi',
406- 'DEVTYPE': 'scsi_device',
407- },
408- }
409-
410- scsi_device_sysfs_data = {
411- 'vendor': 'MATSHITA',
412- 'model': 'DVD-RAM UJ-841S',
413- 'type': '5',
414- }
415-
416- no_subsystem_device_data = {
417- 'P': '/devices/pnp0/00:00',
418- 'E': {}
419- }
420+ def setUp(self):
421+ """Setup the test environment."""
422+ super(TestUdevDevice, self).setUp()
423+ self.root_device = {
424+ 'P': '/devices/LNXSYSTM:00',
425+ 'E': {
426+ 'UDEV_LOG': '3',
427+ 'DEVPATH': '/devices/LNXSYSTM:00',
428+ 'MODALIAS': 'acpi:LNXSYSTM:',
429+ 'SUBSYSTEM': 'acpi',
430+ }
431+ }
432+
433+ self.root_device_dmi_data = {
434+ '/sys/class/dmi/id/sys_vendor': 'FUJITSU SIEMENS',
435+ '/sys/class/dmi/id/product_name': 'LIFEBOOK E8210',
436+ }
437+
438+ self.pci_device_data = {
439+ 'P': '/devices/pci0000:00/0000:00:1f.2',
440+ 'E': {
441+ 'PCI_CLASS': '10602',
442+ 'PCI_ID': '8086:27C5',
443+ 'PCI_SUBSYS_ID': '10CF:1387',
444+ 'PCI_SLOT_NAME': '0000:00:1f.2',
445+ 'SUBSYSTEM': 'pci',
446+ 'DRIVER': 'ahci',
447+ }
448+ }
449+
450+ self.usb_device_data = {
451+ 'P': '/devices/pci0000:00/0000:00:1d.1/usb3/3-2',
452+ 'E': {
453+ 'SUBSYSTEM': 'usb',
454+ 'DEVTYPE': 'usb_device',
455+ 'PRODUCT': '46d/a01/1013',
456+ 'TYPE': '0/0/0',
457+ 'DRIVER': 'usb',
458+ },
459+ }
460+
461+ self.pci_scsi_controller_data = {
462+ 'P': '/devices/pci0000:00/0000:00:1e.0/0000:08:03.0/0000:09:00.0',
463+ 'E': {
464+ 'DRIVER': 'aic7xxx',
465+ 'PCI_CLASS': '10000',
466+ 'PCI_ID': '9004:6075',
467+ 'PCI_SUBSYS_ID': '9004:7560',
468+ 'SUBSYSTEM': 'pci',
469+ },
470+ }
471+
472+ self.pci_scsi_controller_scsi_side_1 = {
473+ 'P': ('/devices/pci0000:00/0000:00:1e.0/0000:08:03.0/'
474+ '0000:09:00.0/host6'),
475+ 'E': {
476+ 'DEVTYPE': 'scsi_host',
477+ 'SUBSYSTEM': 'scsi',
478+ },
479+ }
480+
481+ self.pci_scsi_controller_scsi_side_2 = {
482+ 'P': ('/devices/pci0000:00/0000:00:1e.0/0000:08:03.0/'
483+ '0000:09:00.0/host6/scsi_host/host6'),
484+ 'E': {
485+ 'SUBSYSTEM': 'scsi_host',
486+ },
487+ }
488+
489+ self.scsi_scanner_target_data = {
490+ 'P': ('/devices/pci0000:00/0000:00:1e.0/0000:08:03.0/'
491+ '0000:09:00.0/host6/target6:0:1'),
492+ 'E': {
493+ 'DEVTYPE': 'scsi_target',
494+ 'SUBSYSTEM': 'scsi'
495+ },
496+ }
497+
498+ self.scsi_scanner_device_data = {
499+ 'P': ('/devices/pci0000:00/0000:00:1e.0/0000:08:03.0/'
500+ '0000:09:00.0/host6/target6:0:1/6:0:1:0'),
501+ 'E': {
502+ 'DEVTYPE': 'scsi_device',
503+ 'SUBSYSTEM': 'scsi',
504+ },
505+ }
506+
507+ self.scsi_scanner_device_sysfs_data = {
508+ 'vendor': 'FUJITSU',
509+ 'model': 'fi-5120Cdj',
510+ 'type': '6',
511+ }
512+
513+ self.scsi_device_hierarchy_data = [
514+ {'udev_data': self.pci_scsi_controller_data},
515+ {'udev_data': self.pci_scsi_controller_scsi_side_1},
516+ {'udev_data': self.pci_scsi_controller_scsi_side_2},
517+ {'udev_data': self.scsi_scanner_target_data},
518+ {
519+ 'udev_data': self.scsi_scanner_device_data,
520+ 'sysfs_data': self.scsi_scanner_device_sysfs_data,
521+ },
522+ ]
523+
524+ self.pci_ide_controller = {
525+ 'P': '/devices/pci0000:00/0000:00:1f.1',
526+ 'E': {
527+ 'DRIVER': 'ata_piix',
528+ 'PCI_CLASS': '1018A',
529+ 'PCI_ID': '8086:27DF',
530+ 'PCI_SUBSYS_ID': '10CF:1385',
531+ 'SUBSYSTEM': 'pci',
532+ },
533+ }
534+
535+ self.pci_ide_controller_scsi_side_1 = {
536+ 'P': '/devices/pci0000:00/0000:00:1f.1/host4',
537+ 'E': {
538+ 'DEVTYPE': 'scsi_host',
539+ 'SUBSYSTEM': 'scsi',
540+ },
541+ }
542+
543+ self.pci_ide_controller_scsi_side_2 = {
544+ 'P': '/devices/pci0000:00/0000:00:1f.1/host4/scsi_host/host4',
545+ 'E': {
546+ 'SUBSYSTEM': 'scsi_host',
547+ },
548+ }
549+
550+ self.ide_device_target_data = {
551+ 'P': '/devices/pci0000:00/0000:00:1f.1/host4/target4:0:0',
552+ 'E': {
553+ 'DEVTYPE': 'scsi_target',
554+ 'SUBSYSTEM': 'scsi',
555+ },
556+ }
557+
558+ self.ide_cdrom_device_data = {
559+ 'P': ('/devices/pci0000:00/0000:00:1f.1/host4/target4:0:0/'
560+ '4:0:0:0'),
561+ 'E': {
562+ 'SUBSYSTEM': 'scsi',
563+ 'DEVTYPE': 'scsi_device',
564+ 'DRIVER': 'sr',
565+ },
566+ }
567+
568+ self.ide_cdrom_device_sysfs_data = {
569+ 'vendor': 'MATSHITA',
570+ 'model': 'DVD-RAM UJ-841S',
571+ 'type': '5',
572+ }
573+
574+ self.ide_device_hierarchy_data = [
575+ {'udev_data': self.pci_ide_controller},
576+ {'udev_data': self.pci_ide_controller_scsi_side_1},
577+ {'udev_data': self.pci_ide_controller_scsi_side_2},
578+ {'udev_data': self.ide_device_target_data},
579+ {
580+ 'udev_data': self.ide_cdrom_device_data,
581+ 'sysfs_data': self.ide_cdrom_device_sysfs_data,
582+ },
583+ ]
584+
585+ self.usb_storage_usb_interface = {
586+ 'P': '/devices/pci0000:00/0000:00:1d.7/usb1/1-1/1-1:1.0',
587+ 'E': {
588+ 'DRIVER': 'usb-storage',
589+ 'PRODUCT': '1307/163/100',
590+ 'TYPE': '0/0/0',
591+ 'INTERFACE': '8/6/80',
592+ 'SUBSYSTEM': 'usb',
593+ },
594+ }
595+
596+ self.usb_storage_scsi_host_1 = {
597+ 'P': '/devices/pci0000:00/0000:00:1d.7/usb1/1-1/1-1:1.0/host7',
598+ 'E': {
599+ 'DEVTYPE': 'scsi_host',
600+ 'SUBSYSTEM': 'scsi',
601+ },
602+ }
603+
604+ self.usb_storage_scsi_host_2 = {
605+ 'P': ('/devices/pci0000:00/0000:00:1d.7/usb1/1-1/1-1:1.0/host7/'
606+ 'scsi_host/host7'),
607+ 'E': {
608+ 'SUBSYSTEM': 'scsi_host',
609+ },
610+ }
611+
612+ self.usb_storage_scsi_target = {
613+ 'P': ('/devices/pci0000:00/0000:00:1d.7/usb1/1-1/1-1:1.0/host7/'
614+ 'target7:0:0'),
615+ 'E': {
616+ 'DEVTYPE': 'scsi_target',
617+ 'SUBSYSTEM': 'scsi',
618+ },
619+ }
620+
621+ self.usb_storage_scsi_device = {
622+ 'P': ('/devices/pci0000:00/0000:00:1d.7/usb1/1-1/1-1:1.0/host7/'
623+ 'target7:0:0/7:0:0:0'),
624+ 'E': {
625+ 'DEVTYPE': 'scsi_device',
626+ 'DRIVER': 'sd',
627+ 'SUBSYSTEM': 'scsi',
628+ },
629+ }
630+
631+ self.usb_storage_scsi_device_sysfs = {
632+ 'vendor': 'Ut163',
633+ 'model': 'USB2FlashStorage',
634+ 'type': '0',
635+ }
636+
637+ self.usb_storage_hierarchy_data = [
638+ {'udev_data': self.usb_storage_usb_interface},
639+ {'udev_data': self.usb_storage_scsi_host_1},
640+ {'udev_data': self.usb_storage_scsi_host_2},
641+ {'udev_data': self.usb_storage_scsi_target},
642+ {
643+ 'udev_data': self.usb_storage_scsi_device,
644+ 'sysfs_data': self.usb_storage_scsi_device_sysfs,
645+ },
646+ ]
647+
648+ self.no_subsystem_device_data = {
649+ 'P': '/devices/pnp0/00:00',
650+ 'E': {}
651+ }
652
653 def test_device_id(self):
654 """Test of UdevDevice.device_id."""
655@@ -2790,7 +3174,8 @@
656 def test_is_scsi_device(self):
657 """Test of UdevDevice.is_scsi_device."""
658 device = UdevDevice(
659- None, self.scsi_device_data, self.scsi_device_sysfs_data)
660+ None, self.scsi_scanner_device_data,
661+ self.scsi_scanner_device_sysfs_data)
662 self.assertTrue(device.is_scsi_device)
663
664 device = UdevDevice(None, self.root_device)
665@@ -2799,16 +3184,18 @@
666 def test_scsi_vendor(self):
667 """Test of UdevDevice.scsi_vendor."""
668 device = UdevDevice(
669- None, self.scsi_device_data, self.scsi_device_sysfs_data, None)
670- self.assertEqual('MATSHITA', device.scsi_vendor)
671+ None, self.scsi_scanner_device_data,
672+ self.scsi_scanner_device_sysfs_data)
673+ self.assertEqual('FUJITSU', device.scsi_vendor)
674 device = UdevDevice(None, self.root_device)
675 self.assertEqual(None, device.scsi_vendor)
676
677 def test_scsi_model(self):
678 """Test of UdevDevice.scsi_model."""
679 device = UdevDevice(
680- None, self.scsi_device_data, self.scsi_device_sysfs_data)
681- self.assertEqual('DVD-RAM UJ-841S', device.scsi_model)
682+ None, self.scsi_scanner_device_data,
683+ self.scsi_scanner_device_sysfs_data)
684+ self.assertEqual('fi-5120Cdj', device.scsi_model)
685
686 device = UdevDevice(None, self.root_device)
687 self.assertEqual(None, device.scsi_model)
688@@ -2847,10 +3234,10 @@
689 self.assertEqual('Unknown', device.getVendorOrProduct('product'))
690
691 device = UdevDevice(
692- None, self.scsi_device_data, self.scsi_device_sysfs_data)
693- self.assertEqual('MATSHITA', device.getVendorOrProduct('vendor'))
694- self.assertEqual(
695- 'DVD-RAM UJ-841S', device.getVendorOrProduct('product'))
696+ None, self.scsi_scanner_device_data,
697+ self.scsi_scanner_device_sysfs_data)
698+ self.assertEqual('FUJITSU', device.getVendorOrProduct('vendor'))
699+ self.assertEqual('fi-5120Cdj', device.getVendorOrProduct('product'))
700
701 device = UdevDevice(None, self.no_subsystem_device_data)
702 self.assertEqual(None, device.getVendorOrProduct('vendor'))
703@@ -2888,10 +3275,10 @@
704 self.assertEqual(0xa01, device.getVendorOrProductID('product'))
705
706 device = UdevDevice(
707- None, self.scsi_device_data, self.scsi_device_sysfs_data)
708- self.assertEqual('MATSHITA', device.getVendorOrProductID('vendor'))
709- self.assertEqual(
710- 'DVD-RAM UJ-841S', device.getVendorOrProductID('product'))
711+ None, self.scsi_scanner_device_data,
712+ self.scsi_scanner_device_sysfs_data)
713+ self.assertEqual('FUJITSU', device.getVendorOrProductID('vendor'))
714+ self.assertEqual('fi-5120Cdj', device.getVendorOrProductID('product'))
715
716 device = UdevDevice(
717 None, self.no_subsystem_device_data)
718@@ -2910,6 +3297,131 @@
719 None, self.root_device, None, self.root_device_dmi_data)
720 self.assertEqual('LIFEBOOK E8210', device.product_id)
721
722+ def test_vendor_id_for_db(self):
723+ """Test of UdevDevice.vendor_id_for_db."""
724+ device = UdevDevice(
725+ None, self.root_device, None, self.root_device_dmi_data)
726+ self.assertEqual('FUJITSU SIEMENS', device.vendor_id_for_db)
727+
728+ device = UdevDevice(None, self.pci_device_data)
729+ self.assertEqual('0x8086', device.vendor_id_for_db)
730+
731+ device = UdevDevice(None, self.usb_device_data)
732+ self.assertEqual('0x046d', device.vendor_id_for_db)
733+
734+ device = UdevDevice(
735+ None, self.scsi_scanner_device_data,
736+ self.scsi_scanner_device_sysfs_data)
737+ self.assertEqual('FUJITSU ', device.vendor_id_for_db)
738+
739+ def test_product_id_for_db(self):
740+ """Test of UdevDevice.product_id_for_db."""
741+ device = UdevDevice(
742+ None, self.root_device, None, self.root_device_dmi_data)
743+ self.assertEqual('LIFEBOOK E8210', device.product_id_for_db)
744+
745+ device = UdevDevice(None, self.pci_device_data)
746+ self.assertEqual('0x27c5', device.product_id_for_db)
747+
748+ device = UdevDevice(None, self.usb_device_data)
749+ self.assertEqual('0x0a01', device.product_id_for_db)
750+
751+ device = UdevDevice(
752+ None, self.scsi_scanner_device_data,
753+ self.scsi_scanner_device_sysfs_data)
754+ self.assertEqual('fi-5120Cdj ', device.product_id_for_db)
755+
756+ def test_driver_name(self):
757+ """Test of UdevDevice.driver_name."""
758+ device = UdevDevice(None, self.pci_device_data)
759+ self.assertEqual('ahci', device.driver_name)
760+
761+ device = UdevDevice(
762+ None, self.root_device, None, self.root_device_dmi_data)
763+ self.assertEqual(None, device.driver_name)
764+
765+ def buildUdevDeviceHierarchy(self, device_data, parser=None):
766+ """Build a UdevDevice hierarchy from device_data.
767+
768+ :param device_data: A sequence of arguments that are passed
769+ to the UdevDevice constructor. Each element must be a
770+ dictionary that can be used as a **kwargs argument.
771+
772+ Element N of the sequence is the parent of element N+1.
773+ :param parser: A SubmissionParser instance to be passed to
774+ the constructor of UdevDevice.
775+ """
776+ parent = None
777+ devices = []
778+ for kwargs in device_data:
779+ device = UdevDevice(parser, **kwargs)
780+ devices.append(device)
781+ if parent is not None:
782+ parent.addChild(device)
783+ parent = device
784+ return devices
785+
786+ def test_scsi_controller(self):
787+ """Test of UdevDevice.scsi_controller for a PCI controller."""
788+ devices = self.buildUdevDeviceHierarchy(
789+ self.scsi_device_hierarchy_data)
790+ controller = devices[0]
791+ scsi_device = devices[-1]
792+ self.assertEqual(controller, scsi_device.scsi_controller)
793+
794+ def test_scsi_controller_insufficient_anchestors(self):
795+ """Test of UdevDevice.scsi_controller for a PCI controller.
796+
797+ If a SCSI device does not have a sufficient number of ancestors,
798+ UdevDevice.scsi_controller returns None.
799+ """
800+ parser = SubmissionParser(self.log)
801+ parser.submission_key = 'UdevDevice.scsi_controller ancestor missing'
802+ devices = self.buildUdevDeviceHierarchy(
803+ self.scsi_device_hierarchy_data[1:], parser)
804+ scsi_device = devices[-1]
805+ self.assertEqual(None, scsi_device.scsi_controller)
806+ self.assertWarningMessage(
807+ parser.submission_key,
808+ 'Found a SCSI device without a sufficient number of ancestors: '
809+ '/devices/pci0000:00/0000:00:1e.0/0000:08:03.0/0000:09:00.0/'
810+ 'host6/target6:0:1/6:0:1:0')
811+
812+ def test_scsi_controller_no_scsi_device(self):
813+ """Test of UdevDevice.scsi_controller for a PCI controller.
814+
815+ For non-SCSI devices, this property is None.
816+ """
817+ device = UdevDevice(None, self.pci_device_data)
818+ self.assertEqual(None, device.scsi_controller)
819+
820+ def test_translateScsiBus_real_scsi_device(self):
821+ """Test of UdevDevice.translateScsiBus() with a real SCSI device."""
822+ devices = self.buildUdevDeviceHierarchy(
823+ self.scsi_device_hierarchy_data)
824+ scsi_device = devices[-1]
825+ self.assertEqual(
826+ HWBus.SCSI, scsi_device.translateScsiBus())
827+
828+ def test_translateScsiBus_ide_device(self):
829+ """Test of UdevDevice.translateScsiBus() with an IDE device."""
830+ devices = self.buildUdevDeviceHierarchy(
831+ self.ide_device_hierarchy_data)
832+ ide_device = devices[-1]
833+ self.assertEqual(HWBus.IDE, ide_device.translateScsiBus())
834+
835+ def test_translateScsiBus_usb_device(self):
836+ """Test of UdevDevice.translateScsiBus() with a USB device."""
837+ devices = self.buildUdevDeviceHierarchy(
838+ self.usb_storage_hierarchy_data)
839+ usb_scsi_device = devices[-1]
840+ self.assertEqual(None, usb_scsi_device.translateScsiBus())
841+
842+ def test_translateScsiBus_non_scsi_device(self):
843+ """Test of UdevDevice.translateScsiBus() for a non-SCSI device."""
844+ device = UdevDevice(None, self.root_device)
845+ self.assertEqual(None, device.translateScsiBus())
846+
847
848 class TestHWDBSubmissionTablePopulation(TestCaseHWDB):
849 """Tests of the HWDB popoluation with submitted data."""