[PATCH 19/25] tests/functional: add a device-locator self-check

Emmanuel Blot via qemu development <[email protected]>
Newsgroups gmane.comp.emulators.qemu
Message-ID <[email protected]>
Launch the SanMiguel BMC paused (-S) and resolve its I2C temperature
sensors and SPI flashes by position over QMP, exercising the
DeviceLocator grammar: controller/bus hops, the
address/type/on-bus-index selectors, the leaf type assertion and the
ambiguous/unknown/out-of-range error paths.

The test needs no disk image and completes in about a second, so it
joins the quick arm test set.

Signed-off-by: Emmanuel Blot <[email protected]>
---
 tests/functional/arm/meson.build            |   1 +
 tests/functional/arm/test_device_locator.py | 114 ++++++++++++++++++++++++++++
 2 files changed, 115 insertions(+)

diff --git a/tests/functional/arm/meson.build b/tests/functional/arm/meson.build
index 61b00932db..bb78dcf3e4 100644
--- a/tests/functional/arm/meson.build
+++ b/tests/functional/arm/meson.build
@@ -29,6 +29,7 @@ test_arm_timeouts = {
 }
 
 tests_arm_system_quick = [
+  'device_locator',
   'migration',
 ]
 
diff --git a/tests/functional/arm/test_device_locator.py b/tests/functional/arm/test_device_locator.py
new file mode 100644
index 0000000000..eea88f4fb0
--- /dev/null
+++ b/tests/functional/arm/test_device_locator.py
@@ -0,0 +1,114 @@
+#!/usr/bin/env python3
+#
+# Functional self-check for the pure-Python DeviceLocator helper.
+#
+# It launches the SanMiguel BMC paused (-S) and resolves its I2C sensors and
+# SPI flashes by position over QMP, so it needs no disk image and runs in
+# about a second.
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+import sys
+
+from unittest import skipIf
+
+from qemu_test import QemuSystemTest
+
+# DeviceLocator's type hints require Python 3.10+; import it only when
+# available so this module still loads (and skips) on the 3.9 baseline.
+if sys.version_info >= (3, 10):
+    from qemu_test.locator import DeviceLocator, LocatorError
+
+
+@skipIf(sys.version_info < (3, 10), 'DeviceLocator requires Python 3.10+')
+class DeviceLocatorTest(QemuSystemTest):
+
+    def setUp(self):
+        super().setUp()
+        self.set_machine('sanmiguel-bmc')
+        # Launch paused with defaults enabled so the aspeed board instantiates
+        # its SPI flashes (created only under defaults_enabled()); still no
+        # disk image is needed.
+        self.vm.add_args('-S', '-display', 'none')
+        self.vm.launch()
+        self.locator = DeviceLocator(self.vm.cmd)
+
+    def _address(self, path):
+        return self.vm.cmd('qom-get', path=path, property='address')
+
+    def _cs(self, path):
+        return self.vm.cmd('qom-get', path=path, property='cs')
+
+    def test_resolve_tmp75_sensors(self):
+        # The SanMiguel board wires ti,tmp75 sensors on I2C buses 5, 9 and 10
+        # behind the aspeed.i2c-ast2600 controller.
+        ctrl = '/machine/soc::aspeed.i2c-ast2600[0]'
+        for locator, addr in ((f'{ctrl}~i2c[5]~@0x48', 0x48),
+                              (f'{ctrl}~i2c[9]~@0x4e', 0x4e),
+                              (f'{ctrl}~i2c[10]~@0x48', 0x48)):
+            path = self.locator.resolve(locator, 'tmp75')
+            self.assertEqual(self._address(path), addr)
+
+    def test_device_selectors(self):
+        bus = '/machine/soc::aspeed.i2c-ast2600[0]~i2c[5]'
+        by_addr = self.locator.resolve(f'{bus}~@0x48')
+        # A type selector combined with the address must reach the same device.
+        self.assertEqual(self.locator.resolve(f'{bus}~tmp75@0x48'), by_addr)
+        # The on-bus (BusChild) index selector addresses a device positionally.
+        by_index = self.locator.resolve(f'{bus}~#0')
+        self.assertTrue(by_index.startswith('/machine'))
+        # A bus hop also accepts the raw bus QOM type in place of the keyword.
+        raw = '/machine/soc::aspeed.i2c-ast2600[0]~i2c-bus[5]~@0x48'
+        self.assertEqual(self.locator.resolve(raw), by_addr)
+
+    def test_type_assertion(self):
+        # The leaf type is validated when a type argument is passed.
+        self.assertRaises(LocatorError, self.locator.resolve,
+                          '/machine/soc::aspeed.i2c-ast2600[0]~i2c[5]~@0x48',
+                          'ds1338')
+
+    def test_resolution_errors(self):
+        ctrl = '/machine/soc::aspeed.i2c-ast2600[0]'
+        # No device sits at that address on the bus.
+        self.assertRaises(LocatorError, self.locator.resolve,
+                          f'{ctrl}~i2c[5]~@0x99')
+        # A bare bus hop under /machine/soc spans every i2c bus on both the
+        # i2c and i3c controllers, so it is ambiguous without a controller hop.
+        self.assertRaises(LocatorError, self.locator.resolve,
+                          '/machine/soc::i2c')
+        # Unknown object type in a controller hop.
+        self.assertRaises(LocatorError, self.locator.resolve,
+                          '/machine/soc::nope')
+        # Out-of-range bus index.
+        self.assertRaises(LocatorError, self.locator.resolve,
+                          f'{ctrl}~i2c[99]~@0x48')
+        # A stray or doubled '~' yields an empty hop, which is malformed.
+        self.assertRaises(LocatorError, self.locator.resolve,
+                          f'{ctrl}~~i2c[5]~@0x48')
+
+    def test_resolve_spi_flashes(self):
+        # The FMC controller carries two flashes on chip-selects 0 and 1; the
+        # first SPI controller carries one on chip-select 0.
+        for locator, cs in (
+                ('/machine/soc::aspeed.fmc-ast2600[0]~spi[0]~@0', 0),
+                ('/machine/soc::aspeed.fmc-ast2600[0]~spi[0]~@1', 1),
+                ('/machine/soc::aspeed.spi1-ast2600[0]~spi[0]~@0', 0)):
+            path = self.locator.resolve(locator, 'ssi-peripheral')
+            self.assertEqual(self._cs(path), cs)
+
+    def test_spi_selectors(self):
+        bus = '/machine/soc::aspeed.fmc-ast2600[0]~spi[0]'
+        by_cs = self.locator.resolve(f'{bus}~@0')
+        # A type selector plus the chip-select reaches the same flash.
+        self.assertEqual(
+            self.locator.resolve(f'{bus}~ssi-peripheral@0'), by_cs)
+        # The on-bus index selector resolves positionally.
+        self.assertTrue(
+            self.locator.resolve(f'{bus}~#1').startswith('/machine'))
+        # A wrong-type assertion on an SPI leaf is rejected.
+        self.assertRaises(LocatorError, self.locator.resolve,
+                          f'{bus}~@0', 'tmp75')
+
+
+if __name__ == '__main__':
+    QemuSystemTest.main()

-- 
2.50.1
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.