Add a functional test that exercises the full monitor hotplug lifecycle
with a real socket connection:
1. Launch QEMU
2. chardev-add a unix socket chardev
3. monitor-add to attach a QMP monitor to it
4. Connect to the socket, receive the QMP greeting, negotiate
capabilities, and send a query-version command
5. Disconnect, monitor-remove, chardev-remove
6. Repeat the entire cycle a second time to verify cleanup and reuse
This complements the qtest unit tests by verifying that a real QMP
client can connect to a dynamically-added monitor and exchange messages.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
tests/functional/generic/meson.build | 1 +
tests/functional/generic/test_monitor_hotplug.py | 102 +++++++++++++++++++++++
2 files changed, 103 insertions(+)
diff --git a/tests/functional/generic/meson.build b/tests/functional/generic/meson.build
index 09763c5d22..c94105c62e 100644
--- a/tests/functional/generic/meson.build
+++ b/tests/functional/generic/meson.build
@@ -4,6 +4,7 @@ tests_generic_system = [
'empty_cpu_model',
'info_usernet',
'linters',
+ 'monitor_hotplug',
'version',
'vnc',
]
diff --git a/tests/functional/generic/test_monitor_hotplug.py b/tests/functional/generic/test_monitor_hotplug.py
new file mode 100644
index 0000000000..f8f24320c6
--- /dev/null
+++ b/tests/functional/generic/test_monitor_hotplug.py
@@ -0,0 +1,102 @@
+#!/usr/bin/env python3
+#
+# Functional test for dynamic QMP monitor hotplug
+#
+# Copyright (c) 2026 Christian Brauner
+#
+# This work is licensed under the terms of the GNU GPL, version 2 or
+# later. See the COPYING file in the top-level directory.
+
+import os
+import tempfile
+
+from qemu.qmp.legacy import QEMUMonitorProtocol
+
+from qemu_test import QemuSystemTest
+
+
+class MonitorHotplug(QemuSystemTest):
+
+ def setUp(self):
+ super().setUp()
+ # Use /tmp to avoid UNIX socket path length limit (108 bytes).
+ # The scratch_file() path is too deep for socket names.
+ fd, self._sock_path = tempfile.mkstemp(
+ prefix='qemu-mon-', suffix='.sock')
+ os.close(fd)
+ os.unlink(self._sock_path)
+
+ def tearDown(self):
+ try:
+ os.unlink(self._sock_path)
+ except FileNotFoundError:
+ pass
+ super().tearDown()
+
+ def _add_monitor(self):
+ """Create a chardev + monitor and return the socket path."""
+ sock = self._sock_path
+ self.vm.cmd('chardev-add', id='hotplug-chr', backend={
+ 'type': 'socket',
+ 'data': {
+ 'addr': {
+ 'type': 'unix',
+ 'data': {'path': sock}
+ },
+ 'server': True,
+ 'wait': False,
+ }
+ })
+ self.vm.cmd('monitor-add', id='hotplug-mon',
+ chardev='hotplug-chr')
+ return sock
+
+ def _remove_monitor(self):
+ """Remove the monitor + chardev."""
+ self.vm.cmd('monitor-remove', id='hotplug-mon')
+ self.vm.cmd('chardev-remove', id='hotplug-chr')
+
+ def _connect_and_handshake(self, sock_path):
+ """
+ Connect to the dynamic monitor socket, perform the QMP
+ greeting and capability negotiation, send a command, then
+ disconnect.
+ """
+ qmp = QEMUMonitorProtocol(sock_path)
+
+ # connect(negotiate=True) receives the greeting, validates it,
+ # and sends qmp_capabilities automatically.
+ greeting = qmp.connect(negotiate=True)
+ self.assertIn('QMP', greeting)
+ self.assertIn('version', greeting['QMP'])
+ self.assertIn('capabilities', greeting['QMP'])
+
+ # Send a real command to prove the session is fully functional
+ resp = qmp.cmd_obj({'execute': 'query-version'})
+ self.assertIn('return', resp)
+ self.assertIn('qemu', resp['return'])
+
+ qmp.close()
+
+ def test_hotplug_cycle(self):
+ """
+ Hotplug a monitor, do the full QMP handshake, unplug it,
+ then repeat the whole cycle a second time.
+ """
+ self.set_machine('none')
+ self.vm.add_args('-nodefaults')
+ self.vm.launch()
+
+ # First cycle
+ sock = self._add_monitor()
+ self._connect_and_handshake(sock)
+ self._remove_monitor()
+
+ # Second cycle -- same ids, same path, must work
+ sock = self._add_monitor()
+ self._connect_and_handshake(sock)
+ self._remove_monitor()
+
+
+if __name__ == '__main__':
+ QemuSystemTest.main()
--
2.47.3
Hi Christian! Am Thu, 02 Apr 2026 23:19:20 +0200 schrieb Christian Brauner <brauner@kernel.org>: > Add a functional test that exercises the full monitor hotplug lifecycle > with a real socket connection: > > 1. Launch QEMU > 2. chardev-add a unix socket chardev > 3. monitor-add to attach a QMP monitor to it > 4. Connect to the socket, receive the QMP greeting, negotiate > capabilities, and send a query-version command > 5. Disconnect, monitor-remove, chardev-remove > 6. Repeat the entire cycle a second time to verify cleanup and reuse > > This complements the qtest unit tests by verifying that a real QMP > client can connect to a dynamically-added monitor and exchange messages. > > Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org> > --- ... > diff --git a/tests/functional/generic/test_monitor_hotplug.py b/tests/functional/generic/test_monitor_hotplug.py > new file mode 100644 > index 0000000000..f8f24320c6 > --- /dev/null > +++ b/tests/functional/generic/test_monitor_hotplug.py > @@ -0,0 +1,102 @@ > +#!/usr/bin/env python3 > +# > +# Functional test for dynamic QMP monitor hotplug > +# > +# Copyright (c) 2026 Christian Brauner > +# > +# This work is licensed under the terms of the GNU GPL, version 2 or > +# later. See the COPYING file in the top-level directory. Please add a SPDX license identifier for new files - otherwise scripts/checkpatch.pl will complain. (and bonus points for fixing all warnings from pylint ;-)) > +import os > +import tempfile > + > +from qemu.qmp.legacy import QEMUMonitorProtocol > + > +from qemu_test import QemuSystemTest > + > + > +class MonitorHotplug(QemuSystemTest): > + > + def setUp(self): > + super().setUp() > + # Use /tmp to avoid UNIX socket path length limit (108 bytes). > + # The scratch_file() path is too deep for socket names. > + fd, self._sock_path = tempfile.mkstemp( > + prefix='qemu-mon-', suffix='.sock') We've got a socket_dir() function in the test framework to create socket paths ... could you use that one instead? Apart from that, patch looks fine to me. Thomas
On Sun, Apr 05, 2026 at 06:06:50PM +0000, Thomas Huth wrote: > Hi Christian! > > Am Thu, 02 Apr 2026 23:19:20 +0200 > schrieb Christian Brauner <brauner@kernel.org>: > > > Add a functional test that exercises the full monitor hotplug lifecycle > > with a real socket connection: > > > > 1. Launch QEMU > > 2. chardev-add a unix socket chardev > > 3. monitor-add to attach a QMP monitor to it > > 4. Connect to the socket, receive the QMP greeting, negotiate > > capabilities, and send a query-version command > > 5. Disconnect, monitor-remove, chardev-remove > > 6. Repeat the entire cycle a second time to verify cleanup and reuse > > > > This complements the qtest unit tests by verifying that a real QMP > > client can connect to a dynamically-added monitor and exchange messages. > > > > Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org> > > --- > ... > > diff --git a/tests/functional/generic/test_monitor_hotplug.py b/tests/functional/generic/test_monitor_hotplug.py > > new file mode 100644 > > index 0000000000..f8f24320c6 > > --- /dev/null > > +++ b/tests/functional/generic/test_monitor_hotplug.py > > @@ -0,0 +1,102 @@ > > +#!/usr/bin/env python3 > > +# > > +# Functional test for dynamic QMP monitor hotplug > > +# > > +# Copyright (c) 2026 Christian Brauner > > +# > > +# This work is licensed under the terms of the GNU GPL, version 2 or > > +# later. See the COPYING file in the top-level directory. > > Please add a SPDX license identifier for new files - otherwise > scripts/checkpatch.pl will complain. Ok, done that. :) > > (and bonus points for fixing all warnings from pylint ;-)) Done. > > > +import os > > +import tempfile > > + > > +from qemu.qmp.legacy import QEMUMonitorProtocol > > + > > +from qemu_test import QemuSystemTest > > + > > + > > +class MonitorHotplug(QemuSystemTest): > > + > > + def setUp(self): > > + super().setUp() > > + # Use /tmp to avoid UNIX socket path length limit (108 bytes). > > + # The scratch_file() path is too deep for socket names. > > + fd, self._sock_path = tempfile.mkstemp( > > + prefix='qemu-mon-', suffix='.sock') > > We've got a socket_dir() function in the test framework to create socket > paths ... could you use that one instead? Done. > > Apart from that, patch looks fine to me. Thanks!
© 2016 - 2026 Red Hat, Inc.