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
}