From e579bb528c7bfeeb4b509e1cfeea6153af7295ed Mon Sep 17 00:00:00 2001 From: Siddhi Katage Date: Tue, 10 Feb 2026 11:31:35 +0000 Subject: [PATCH 1/3] tests/functional: Add hotplug_scsi test to hotplug virtio-scsi disk Signed-off-by: Siddhi Katage Message-ID: <20260210113135.771697-1-siddhi.katage@oracle.com> [thuth: Add the new .py file to the MAINTAINERS file] Signed-off-by: Thomas Huth --- MAINTAINERS | 1 + tests/functional/x86_64/meson.build | 1 + tests/functional/x86_64/test_hotplug_scsi.py | 86 ++++++++++++++++++++ 3 files changed, 88 insertions(+) create mode 100755 tests/functional/x86_64/test_hotplug_scsi.py diff --git a/MAINTAINERS b/MAINTAINERS index 97dcc78ded..ed65654698 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2338,6 +2338,7 @@ F: tests/qtest/virtio-scsi-test.c F: tests/qtest/fuzz-virtio-scsi-test.c F: tests/qtest/am53c974-test.c F: tests/qtest/fuzz-lsi53c895a-test.c +F: tests/functional/x86_64/test_hotplug_scsi.py T: git https://github.com/bonzini/qemu.git scsi-next SSI diff --git a/tests/functional/x86_64/meson.build b/tests/functional/x86_64/meson.build index 1ed10ad6c2..d237ca156e 100644 --- a/tests/functional/x86_64/meson.build +++ b/tests/functional/x86_64/meson.build @@ -31,6 +31,7 @@ endif tests_x86_64_system_thorough = [ 'acpi_bits', 'hotplug_blk', + 'hotplug_scsi', 'hotplug_cpu', 'intel_iommu', 'kvm_xen', diff --git a/tests/functional/x86_64/test_hotplug_scsi.py b/tests/functional/x86_64/test_hotplug_scsi.py new file mode 100755 index 0000000000..10fd306616 --- /dev/null +++ b/tests/functional/x86_64/test_hotplug_scsi.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +# +# Functional test that hotplugs a virtio scsi disk and checks it on a Linux +# guest +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from qemu_test import LinuxKernelTest, Asset, exec_command_and_wait_for_pattern + +class HotPlugScsi(LinuxKernelTest): + ASSET_KERNEL = Asset( + ('https://archives.fedoraproject.org/pub/archive/fedora/linux/releases' + '/31/Server/x86_64/os/images/pxeboot/vmlinuz'), + 'd4738d03dbbe083ca610d0821d0a8f1488bebbdccef54ce33e3adb35fda00129') + + ASSET_INITRD = Asset( + ('https://archives.fedoraproject.org/pub/archive/fedora/linux/releases' + '/31/Server/x86_64/os/images/pxeboot/initrd.img'), + '277cd6c7adf77c7e63d73bbb2cded8ef9e2d3a2f100000e92ff1f8396513cd8b') + + def blockdev_add(self) -> None: + self.vm.cmd('blockdev-add', **{ + 'driver': 'null-co', + 'size': 1073741824, + 'node-name': 'disk0' + }) + + def add_scsi_controller(self) -> None: + self.vm.cmd('device_add', { + 'driver': 'virtio-scsi-pci', + 'id': 'scsi0', + 'bus': 'pci.1', + 'addr': '1', + }) + + def assert_sda(self) -> None: + exec_command_and_wait_for_pattern(self, 'while ! test -e /sys/block/sda ;' + ' do sleep 0.2 ; done', '# ') + + def assert_no_sda(self) -> None: + exec_command_and_wait_for_pattern(self, 'while test -e /sys/block/sda ;' + ' do sleep 0.2 ; done', '# ') + + def plug(self) -> None: + args = { + 'driver':'scsi-hd', + 'drive':'disk0', + 'bus':'scsi0.0', + 'id':'scsi-disk0', + } + + self.assert_no_sda() + self.vm.cmd('device_add', args) + self.wait_for_console_pattern('[sda] Attached SCSI disk') + self.assert_sda() + + def unplug(self) -> None: + self.vm.cmd('device_del', id='scsi-disk0') + + self.vm.event_wait('DEVICE_DELETED', 1.0, + match={'data': {'device': 'scsi-disk0'}}) + + self.assert_no_sda() + + def test(self) -> None: + self.require_accelerator('kvm') + self.set_machine('q35') + self.vm.add_args('-accel', 'kvm') + self.vm.add_args('-device', 'pcie-pci-bridge,id=pci.1,bus=pcie.0') + self.vm.add_args('-m', '1G') + self.vm.add_args('-append', 'console=ttyS0 rd.rescue') + + self.launch_kernel(self.ASSET_KERNEL.fetch(), + self.ASSET_INITRD.fetch(), + wait_for='Entering emergency mode.') + self.wait_for_console_pattern('# ') + + self.blockdev_add() + self.add_scsi_controller() + self.plug() + self.unplug() + + +if __name__ == '__main__': + LinuxKernelTest.main() + From d1edce955d632df5b06dc9421f43f3fe5356e647 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Le=20Goater?= Date: Wed, 17 Jun 2026 06:27:18 +0200 Subject: [PATCH 2/3] tests/functional/aspeed: unify boot completion detection on 'login:' prompt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The boot completion check in AspeedTest waits for the systemd "Hostname set to" message, which occasionally causes intermittent test timeouts, e.g. on ast2500 SoC machines. The root cause seems to be console output interleaving of both systemd and the getty login process. This results in the expected pattern string being broken up. Unify and simplify all boot completion checks by looking for the generic 'login:' substring in AspeedTest.wait_for_boot_complete(). With the override gone, remove the redundant FacebookAspeedTest class and update the Anacapa, Bletchley, and Catalina tests to inherit directly from AspeedTest. Also drop the now-dead image_hostname parameter from do_test_arm_aspeed_openbmc(). Reported-by: Peter Maydell Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3117 Signed-off-by: Cédric Le Goater Reviewed-by: Thomas Huth Message-ID: <20260617042718.2883655-1-clg@redhat.com> Signed-off-by: Thomas Huth --- tests/functional/arm/test_aspeed_anacapa.py | 6 +++--- tests/functional/arm/test_aspeed_bletchley.py | 6 +++--- tests/functional/arm/test_aspeed_catalina.py | 6 +++--- .../arm/test_aspeed_gb200nvl_bmc.py | 3 +-- tests/functional/aspeed.py | 21 ++++--------------- 5 files changed, 14 insertions(+), 28 deletions(-) diff --git a/tests/functional/arm/test_aspeed_anacapa.py b/tests/functional/arm/test_aspeed_anacapa.py index 27f8bd8b56..809afeaeb8 100644 --- a/tests/functional/arm/test_aspeed_anacapa.py +++ b/tests/functional/arm/test_aspeed_anacapa.py @@ -5,10 +5,10 @@ # SPDX-License-Identifier: GPL-2.0-or-later from qemu_test import Asset -from aspeed import FacebookAspeedTest +from aspeed import AspeedTest -class AnacapaMachine(FacebookAspeedTest): +class AnacapaMachine(AspeedTest): ASSET_ANACAPA_FLASH = Asset( 'https://github.com/legoater/qemu-aspeed-boot/raw/3fa3212827b04be4034d43b5adeef57c27d6ab18/images/anacapa-bmc/openbmc-20260512025228/obmc-phosphor-image-anacapa-20260512025228.static.mtd.xz', @@ -22,4 +22,4 @@ def test_arm_ast2600_anacapa_openbmc(self): soc='AST2600 rev A3') if __name__ == '__main__': - FacebookAspeedTest.main() + AspeedTest.main() diff --git a/tests/functional/arm/test_aspeed_bletchley.py b/tests/functional/arm/test_aspeed_bletchley.py index 3000d0c302..5a60b24b3d 100755 --- a/tests/functional/arm/test_aspeed_bletchley.py +++ b/tests/functional/arm/test_aspeed_bletchley.py @@ -5,10 +5,10 @@ # SPDX-License-Identifier: GPL-2.0-or-later from qemu_test import Asset -from aspeed import FacebookAspeedTest +from aspeed import AspeedTest -class BletchleyMachine(FacebookAspeedTest): +class BletchleyMachine(AspeedTest): ASSET_BLETCHLEY_FLASH = Asset( 'https://github.com/legoater/qemu-aspeed-boot/raw/master/images/bletchley-bmc/openbmc-20250128071329/obmc-phosphor-image-bletchley-20250128071329.static.mtd.xz', @@ -22,4 +22,4 @@ def test_arm_ast2600_bletchley_openbmc(self): soc='AST2600 rev A3') if __name__ == '__main__': - FacebookAspeedTest.main() + AspeedTest.main() diff --git a/tests/functional/arm/test_aspeed_catalina.py b/tests/functional/arm/test_aspeed_catalina.py index 2694e4b005..dc2f24e7b4 100755 --- a/tests/functional/arm/test_aspeed_catalina.py +++ b/tests/functional/arm/test_aspeed_catalina.py @@ -5,10 +5,10 @@ # SPDX-License-Identifier: GPL-2.0-or-later from qemu_test import Asset -from aspeed import FacebookAspeedTest +from aspeed import AspeedTest -class CatalinaMachine(FacebookAspeedTest): +class CatalinaMachine(AspeedTest): ASSET_CATALINA_FLASH = Asset( 'https://github.com/legoater/qemu-aspeed-boot/raw/a866feb5ef81245b4827a214584bf6bcc72939f6/images/catalina-bmc/obmc-phosphor-image-catalina-20250619123021.static.mtd.xz', @@ -22,4 +22,4 @@ def test_arm_ast2600_catalina_openbmc(self): soc='AST2600 rev A3') if __name__ == '__main__': - FacebookAspeedTest.main() + AspeedTest.main() diff --git a/tests/functional/arm/test_aspeed_gb200nvl_bmc.py b/tests/functional/arm/test_aspeed_gb200nvl_bmc.py index e5f2dce0f5..f9e736d047 100755 --- a/tests/functional/arm/test_aspeed_gb200nvl_bmc.py +++ b/tests/functional/arm/test_aspeed_gb200nvl_bmc.py @@ -19,8 +19,7 @@ def test_arm_aspeed_gb200_openbmc(self): self.do_test_arm_aspeed_openbmc('gb200nvl-bmc', image=image_path, uboot='2019.04', cpu_id='0xf00', - soc='AST2600 rev A3', - image_hostname='gb200nvl-obmc') + soc='AST2600 rev A3') if __name__ == '__main__': AspeedTest.main() diff --git a/tests/functional/aspeed.py b/tests/functional/aspeed.py index 88b6590934..076da1036c 100644 --- a/tests/functional/aspeed.py +++ b/tests/functional/aspeed.py @@ -8,14 +8,7 @@ class AspeedTest(LinuxKernelTest): def do_test_arm_aspeed_openbmc(self, machine, image, uboot='2019.04', - cpu_id='0x0', soc='AST2500 rev A1', - image_hostname=None): - # Allow for the image hostname to not end in "-bmc" - if image_hostname is not None: - hostname = image_hostname - else: - hostname = machine.removesuffix('-bmc') - + cpu_id='0x0', soc='AST2500 rev A1'): self.set_machine(machine) self.vm.set_console() self.vm.add_args('-drive', f'file={image},if=mtd,format=raw', @@ -28,10 +21,10 @@ def do_test_arm_aspeed_openbmc(self, machine, image, uboot='2019.04', self.wait_for_console_pattern(f'Booting Linux on physical CPU {cpu_id}') self.wait_for_console_pattern(f'ASPEED {soc}') self.wait_for_console_pattern('/init as init process') - self.wait_for_boot_complete(hostname) + self.wait_for_boot_complete() - def wait_for_boot_complete(self, hostname): - self.wait_for_console_pattern(f'systemd[1]: Hostname set to <{hostname}>.') + def wait_for_boot_complete(self): + self.wait_for_console_pattern('login:') def do_test_arm_aspeed_buildroot_start(self, image, cpu_id, pattern='Aspeed EVB'): self.require_netdev('user') @@ -71,9 +64,3 @@ def generate_otpmem_image(self): with open(path, "wb") as f: f.write(pattern) return path - - -class FacebookAspeedTest(AspeedTest): - - def wait_for_boot_complete(self, hostname): - self.wait_for_console_pattern(f'{hostname} login:') From 0e7aa78b0bee50b076785d4cf4a1b7546b3bed31 Mon Sep 17 00:00:00 2001 From: Ganesh Harshan Date: Thu, 25 Jun 2026 12:53:10 -0400 Subject: [PATCH 3/3] tests/functional: use QMP to query available machines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace parsing of "qemu -M help" in set_machine() with QMP "query-machines". The previous approach relied on parsing human-readable CLI output and substring matching, which is fragile and prone to incorrect matches. It is also sensitive to output format changes. Use QMP instead to retrieve structured machine information, ensuring accurate matching and better maintainability. Cache the result at the class level to avoid repeated QEMU startup overhead. Signed-off-by: Ganesh Harshan Reviewed-by: Daniel P. Berrangé Message-ID: <20260625165310.54113-1-ganeshredcobra@gmail.com> [thuth: Drop problematic self.vm.set_machine() statement] Signed-off-by: Thomas Huth --- tests/functional/qemu_test/testcase.py | 31 +++++++++++++++++++++----- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/tests/functional/qemu_test/testcase.py b/tests/functional/qemu_test/testcase.py index eaec1bea13..4912a47a46 100644 --- a/tests/functional/qemu_test/testcase.py +++ b/tests/functional/qemu_test/testcase.py @@ -314,13 +314,32 @@ def setUp(self): console_log.addHandler(self._console_log_fh) def set_machine(self, machinename): - # TODO: We should use QMP to get the list of available machines - if not self._machinehelp: - self._machinehelp = run( - [self.qemu_bin, '-M', 'help'], - capture_output=True, check=True, encoding='utf8').stdout - if self._machinehelp.find(machinename) < 0: + cls = type(self) + + if not hasattr(cls, "_machines"): + tmp_vm = QEMUMachine(self.qemu_bin) + tmp_vm.set_machine('none') + + try: + tmp_vm.launch() + resp = tmp_vm.qmp('query-machines') + + machines = resp.get('return', []) + cls._machines = [ + m.get('name') for m in machines if 'name' in m + ] + + finally: + try: + tmp_vm.shutdown() + except Exception: + pass + + self._machines = cls._machines + + if machinename not in self._machines: self.skipTest('no support for machine ' + machinename) + self.machine = machinename def require_accelerator(self, accelerator):