@samitouri / QOSamiQemu / commits / a9e8186be1

tests/qtest: add D-Bus display hotplug test

Add a qtest that verifies display consoles are dynamically added and removed over D-Bus when a bochs-display device is hotplugged and unplugged on a q35 machine. The test plugs device_add a bochs-display, waits for the DEVICE_ADDED QMP event, and checks that the D-Bus VM interface reports a second console. It then device_del it, forces a system reset (q35 removal is ACPI-based and needs guest cooperation qtest cannot provide), waits for DEVICE_DELETED, and checks the console count again. Reviewed-by: Fabiano Rosas <farosas@suse.de> Reviewed-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com> Message-ID: <20260623-b4-ui-v4-34-4656aec3398d@redhat.com>

Marc-André Lureau committed Jun 23, 2026 at 11:44 UTC a9e8186be122a18a94ff181c86282b453597598d
1 file changed +99 -2
tests/qtest/dbus-display-test.c
+99 -2
@@ -7,6 +7,8 @@
7 #include <gio/gio.h>
8 #include <gio/gunixfdlist.h>
9 #include "libqtest.h"
10 +#include "qobject/qdict.h"
11 +#include "qobject/qstring.h"
12 #include "ui/dbus-display1.h"
13
14 static GDBusConnection*
@@ -38,11 +40,11 @@ test_dbus_p2p_from_fd(int fd)
40 }
41
42 static void
41 -test_setup(QTestState **qts, GDBusConnection **conn)
43 +test_setup_args(QTestState **qts, GDBusConnection **conn, const char *args)
44 {
45 int pair[2];
46
45 - *qts = qtest_init("-display dbus,p2p=yes -name dbus-test");
47 + *qts = qtest_init(args);
48
49 g_assert_cmpint(qemu_socketpair(AF_UNIX, SOCK_STREAM, 0, pair), ==, 0);
50
@@ -52,6 +54,12 @@ test_setup(QTestState **qts, GDBusConnection **conn)
54 g_dbus_connection_start_message_processing(*conn);
55 }
56
57 +static void
58 +test_setup(QTestState **qts, GDBusConnection **conn)
59 +{
60 + test_setup_args(qts, conn, "-display dbus,p2p=yes -name dbus-test");
61 +}
62 +
63 static void
64 test_dbus_display_vm(void)
65 {
@@ -360,6 +368,92 @@ test_dbus_display_keyboard(void)
368 qtest_quit(qts);
369 }
370
371 +static gsize
372 +get_console_ids_count(GDBusConnection *conn)
373 +{
374 + g_autoptr(GError) err = NULL;
375 + g_autoptr(QemuDBusDisplay1VMProxy) vm = NULL;
376 + GVariant *console_ids;
377 + gsize n_ids = 0;
378 +
379 + vm = QEMU_DBUS_DISPLAY1_VM_PROXY(
380 + qemu_dbus_display1_vm_proxy_new_sync(
381 + conn,
382 + G_DBUS_PROXY_FLAGS_NONE,
383 + NULL,
384 + DBUS_DISPLAY1_ROOT "/VM",
385 + NULL,
386 + &err));
387 + g_assert_no_error(err);
388 +
389 + console_ids = qemu_dbus_display1_vm_get_console_ids(
390 + QEMU_DBUS_DISPLAY1_VM(vm));
391 + if (console_ids) {
392 + n_ids = g_variant_n_children(console_ids);
393 + }
394 + return n_ids;
395 +}
396 +
397 +static void
398 +wait_device_event(QTestState *qts, const char *event_name, const char *id)
399 +{
400 + QDict *resp, *data;
401 + QString *qstr;
402 +
403 + for (;;) {
404 + resp = qtest_qmp_eventwait_ref(qts, event_name);
405 + data = qdict_get_qdict(resp, "data");
406 + if (!data || !qdict_get(data, "device")) {
407 + qobject_unref(resp);
408 + continue;
409 + }
410 + qstr = qobject_to(QString, qdict_get(data, "device"));
411 + if (!strcmp(qstring_get_str(qstr), id)) {
412 + qobject_unref(resp);
413 + break;
414 + }
415 + qobject_unref(resp);
416 + }
417 +}
418 +
419 +static void
420 +test_dbus_display_hotplug(void)
421 +{
422 + g_autoptr(GDBusConnection) conn = NULL;
423 + QTestState *qts = NULL;
424 + gsize n;
425 +
426 + test_setup_args(&qts, &conn,
427 + "-machine q35"
428 + " -device pcie-root-port,id=rp0"
429 + " -display dbus,p2p=yes"
430 + " -name dbus-test");
431 +
432 + n = get_console_ids_count(conn);
433 + g_assert_cmpuint(n, ==, 1);
434 +
435 + qtest_qmp_device_add(qts, "bochs-display", "bochs0",
436 + "{'bus': 'rp0'}");
437 +
438 + n = get_console_ids_count(conn);
439 + g_assert_cmpuint(n, ==, 2);
440 +
441 + /*
442 + * On q35, PCI device removal is ACPI-based and requires guest
443 + * acknowledgement. Since qtest has no guest OS, issue the delete
444 + * request and force removal via system reset.
445 + */
446 + qtest_qmp_device_del_send(qts, "bochs0");
447 + qtest_system_reset_nowait(qts);
448 + wait_device_event(qts, "DEVICE_DELETED", "bochs0");
449 +
450 + n = get_console_ids_count(conn);
451 + g_assert_cmpuint(n, ==, 1);
452 +
453 + g_clear_object(&conn);
454 + qtest_quit(qts);
455 +}
456 +
457 int
458 main(int argc, char **argv)
459 {
@@ -369,6 +463,9 @@ main(int argc, char **argv)
463 qtest_add_data_func("/dbus-display/console", GINT_TO_POINTER(false), test_dbus_display_console);
464 qtest_add_data_func("/dbus-display/console/map", GINT_TO_POINTER(true), test_dbus_display_console);
465 qtest_add_func("/dbus-display/keyboard", test_dbus_display_keyboard);
466 + if (qtest_has_machine("q35") && qtest_has_device("bochs-display")) {
467 + qtest_add_func("/dbus-display/hotplug", test_dbus_display_hotplug);
468 + }
469
470 return g_test_run();
471 }