[PATCH 5/5] tests/functional: add e2e test for dynamic QMP monitor hotplug

Christian Brauner posted 5 patches 1 week, 1 day ago
Maintainers: "Dr. David Alan Gilbert" <dave@treblig.org>, Markus Armbruster <armbru@redhat.com>, Eric Blake <eblake@redhat.com>, Thomas Huth <th.huth+qemu@posteo.eu>, "Philippe Mathieu-Daudé" <philmd@linaro.org>, "Daniel P. Berrangé" <berrange@redhat.com>, Fabiano Rosas <farosas@suse.de>, Laurent Vivier <lvivier@redhat.com>, Paolo Bonzini <pbonzini@redhat.com>
There is a newer version of this series
[PATCH 5/5] tests/functional: add e2e test for dynamic QMP monitor hotplug
Posted by Christian Brauner 1 week, 1 day ago
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
Re: [PATCH 5/5] tests/functional: add e2e test for dynamic QMP monitor hotplug
Posted by Thomas Huth 6 days ago
 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
Re: [PATCH 5/5] tests/functional: add e2e test for dynamic QMP monitor hotplug
Posted by Christian Brauner 5 days, 4 hours ago
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!