qemuagenttest hands an fd straight to qemuAgentOpen(). Transport
selection logic was added to qemuConnectAgent(), so to test the vsock
path and related logic, we need to test it instead.

Mock virVsockConnectQuiet() to the scripted unix QGA server, proving the
CID-based vsock branch is taken and a command passes through.

Signed-off-by: Polina Vishneva <[email protected]>
---
 src/util/virvsock.h          |   5 +-
 tests/meson.build            |   1 +
 tests/qemuagenttest.c        |  43 ++++++++++++-
 tests/qemumonitortestutils.c | 119 ++++++++++++++++++++++++++++++++---
 tests/qemumonitortestutils.h |   4 ++
 tests/qemuvsockagentmock.c   |  65 +++++++++++++++++++
 6 files changed, 228 insertions(+), 9 deletions(-)
 create mode 100644 tests/qemuvsockagentmock.c

diff --git a/src/util/virvsock.h b/src/util/virvsock.h
index 8bf08f9228..215bde2d3e 100644
--- a/src/util/virvsock.h
+++ b/src/util/virvsock.h
@@ -18,6 +18,8 @@
 
 #pragma once
 
+#include "internal.h"
+
 #define VIR_VSOCK_GUEST_CID_MIN 3
 #define VIR_VSOCK_CONNECT_TIMEOUT_MS 200
 
@@ -31,4 +33,5 @@ virVsockAcquireGuestCid(int fd,
 
 int
 virVsockConnectQuiet(unsigned int cid,
-                     unsigned int port);
+                     unsigned int port)
+    ATTRIBUTE_MOCKABLE;
diff --git a/tests/meson.build b/tests/meson.build
index 63e0474a29..cd041dd086 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -178,6 +178,7 @@ if conf.has('WITH_QEMU')
     { 'name': 'qemucapsprobemock', 'link_with': [ test_qemu_driver_lib ] },
     { 'name': 'qemucpumock' },
     { 'name': 'qemuhotplugmock', 'link_with': [ test_qemu_driver_lib, 
test_utils_qemu_lib, test_utils_lib ] },
+    { 'name': 'qemuvsockagentmock' },
     { 'name': 'qemuxml2argvmock', 'link_with': [ test_utils_lib ] },
     { 'name': 'virhostidmock' },
   ]
diff --git a/tests/qemuagenttest.c b/tests/qemuagenttest.c
index 74cd317e74..c8e11cfd3c 100644
--- a/tests/qemuagenttest.c
+++ b/tests/qemuagenttest.c
@@ -23,6 +23,7 @@
 #include "testutilsqemu.h"
 #include "qemumonitortestutils.h"
 #include "qemu/qemu_conf.h"
+#include "qemu/qemu_domain.h"
 #include "hypervisor/qemu_agent.h"
 #include "virerror.h"
 
@@ -1394,6 +1395,45 @@ testQemuAgentGetLoadAvg(const void *data)
 }
 
 
+static int
+testQemuAgentVsock(const void *data)
+{
+    virDomainXMLOption *xmlopt = (virDomainXMLOption *)data;
+    g_autoptr(qemuMonitorTest) test = qemuMonitorTestNewAgentVsock(&driver, 
xmlopt);
+    virDomainObj *vm;
+    int rc;
+
+    if (!test)
+        return -1;
+
+    /* The point of this test: qemuConnectAgent() chose vsock and flagged it. 
*/
+    vm = qemuMonitorTestGetDomainObj(test);
+    if (!QEMU_DOMAIN_PRIVATE(vm)->agentIsVsock) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       "agent transport is not vsock");
+        return -1;
+    }
+
+    if (qemuMonitorTestAddAgentSyncResponse(test) < 0)
+        return -1;
+
+    if (qemuMonitorTestAddItem(test, "guest-fsfreeze-thaw",
+                               "{ \"return\" : 5 }") < 0)
+        return -1;
+
+    if ((rc = qemuAgentFSThaw(qemuMonitorTestGetAgent(test))) < 0)
+        return -1;
+
+    if (rc != 5) {
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       "expected 5 thawed filesystems, got %d", rc);
+        return -1;
+    }
+
+    return 0;
+}
+
+
 static int
 mymain(void)
 {
@@ -1431,6 +1471,7 @@ mymain(void)
     DO_TEST(SSHKeys);
     DO_TEST(GetDisks);
     DO_TEST(GetLoadAvg);
+    DO_TEST(Vsock);
 
     DO_TEST(Timeout); /* Timeout should always be called last */
 
@@ -1439,4 +1480,4 @@ mymain(void)
     return (ret == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
 }
 
-VIR_TEST_MAIN(mymain)
+VIR_TEST_MAIN_PRELOAD(mymain, VIR_TEST_MOCK("qemuvsockagent"))
diff --git a/tests/qemumonitortestutils.c b/tests/qemumonitortestutils.c
index e83dd1d9c4..ffdf55a5f9 100644
--- a/tests/qemumonitortestutils.c
+++ b/tests/qemumonitortestutils.c
@@ -30,12 +30,14 @@
 #include "hypervisor/qemu_agent.h"
 #include "qemu/qemu_domain.h"
 #include "qemu/qemu_processpriv.h"
+#include "qemu/qemu_process.h"
 #include "qemu/qemu_monitor.h"
 #include "rpc/virnetsocket.h"
 #include "viralloc.h"
 #include "virlog.h"
 #include "virerror.h"
 #include "vireventthread.h"
+#include "virvsock.h"
 
 #define VIR_FROM_THIS VIR_FROM_NONE
 
@@ -1187,16 +1189,48 @@ qemuMonitorTestNewFromFileFull(const char *fileName,
 }
 
 
+/* Build the bare agent test: the common test object plus the event
+ * thread the agent runs on. @src is filled in by the caller's transport. */
+static qemuMonitorTest *
+qemuMonitorTestNewAgentInit(virDomainXMLOption *xmlopt,
+                            const char *threadName,
+                            virDomainChrSourceDef *src)
+{
+    g_autoptr(qemuMonitorTest) test = NULL;
+
+    if (!(test = qemuMonitorCommonTestNew(xmlopt, NULL, src)))
+        return NULL;
+
+    if (!(test->eventThread = virEventThreadNew(threadName)))
+        return NULL;
+
+    return g_steal_pointer(&test);
+}
+
+
+/* Finish an agent test once test->agent is connected: lock it, run the monitor
+ * init and release @src. On failure the caller's error path frees @test. */
+static int
+qemuMonitorTestAgentStart(qemuMonitorTest *test,
+                          virDomainChrSourceDef *src)
+{
+    virObjectLock(test->agent);
+
+    if (qemuMonitorCommonTestInit(test) < 0)
+        return -1;
+
+    virDomainChrSourceDefClear(src);
+    return 0;
+}
+
+
 qemuMonitorTest *
 qemuMonitorTestNewAgent(virDomainXMLOption *xmlopt)
 {
     g_autoptr(qemuMonitorTest) test = NULL;
     virDomainChrSourceDef src = { 0 };
 
-    if (!(test = qemuMonitorCommonTestNew(xmlopt, NULL, &src)))
-        goto error;
-
-    if (!(test->eventThread = virEventThreadNew("agent-test")))
+    if (!(test = qemuMonitorTestNewAgentInit(xmlopt, "agent-test", &src)))
         goto error;
 
     if (!(test->agent = qemuAgentOpen(test->vm,
@@ -1206,16 +1240,87 @@ qemuMonitorTestNewAgent(virDomainXMLOption *xmlopt)
                                       
QEMU_DOMAIN_PRIVATE(test->vm)->agentTimeout)))
         goto error;
 
-    virObjectLock(test->agent);
-
-    if (qemuMonitorCommonTestInit(test) < 0)
+    if (qemuMonitorTestAgentStart(test, &src) < 0)
         goto error;
 
+    return g_steal_pointer(&test);
+
+ error:
     virDomainChrSourceDefClear(&src);
+    return NULL;
+}
+
+
+/*
+ * Like qemuMonitorTestNewAgent(), but drives the real public 
qemuConnectAgent()
+ * so the transport-selection and vsock connect path actually run. The
+ * qemuvsockagentmock overrides virVsockConnectQuiet() to feed back an fd wired
+ * to @test's scripted unix mock-QGA server, so no real AF_VSOCK is involved.
+ *
+ * @driver must be the same driver @xmlopt was built for, so the domain's
+ * private data auto-wires priv->driver.
+ */
+qemuMonitorTest *
+qemuMonitorTestNewAgentVsock(virQEMUDriver *driver,
+                             virDomainXMLOption *xmlopt)
+{
+    g_autoptr(qemuMonitorTest) test = NULL;
+    virDomainChrSourceDef src = { 0 };
+    qemuDomainObjPrivate *priv;
+
+    if (!(test = qemuMonitorTestNewAgentInit(xmlopt, "agent-vsock-test", 
&src)))
+        goto error;
+
+    /* Make the domain look active and give it a usable guest CID so
+     * qemuConnectAgent() picks the vsock transport. */
+    test->vm->def->id = 1;
+    test->vm->def->name = g_strdup("agent-vsock-test");
+
+    if (!(test->vm->def->vsock = virDomainVsockDefNew(xmlopt)))
+        goto error;
+    test->vm->def->vsock->guest_cid = VIR_VSOCK_GUEST_CID_MIN;
+
+    priv = QEMU_DOMAIN_PRIVATE(test->vm);
+    priv->agentTimeout = 5;
+    /* Alias the event thread without a ref: production only reads it during 
the
+     * connect (the agent takes its own context ref). Severed below before the
+     * private-data teardown could unref the thread qemuMonitorTestFree owns. 
*/
+    priv->eventThread = test->eventThread;
+
+    g_setenv("LIBVIRT_VSOCK_MOCK_PATH", src.data.nix.path, true);
+
+    /* test->vm is already locked (virDomainObjNew() returns it locked), which
+     * is what the connect path needs: it unlocks/relocks the vm internally. */
+
+    /* Drive the real connect. Do NOT ref the vm: qemuAgentOpenFd() takes its
+     * own reference for the agent. */
+    if (qemuConnectAgent(driver, test->vm) < 0 ||
+        !priv->agent || !priv->agentIsVsock) {
+        /* A graceful failure may have armed a reconnect timer holding a vm
+         * ref; drop it (and the thread alias) before tearing down. */
+        qemuDomainCancelAgentVsockReconnect(priv);
+        priv->eventThread = NULL;
+        virObjectUnlock(test->vm);
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       "guest agent did not connect over vsock");
+        goto error;
+    }
+
+    /* Hand the agent to the test object (matches the unix path's ref 
accounting)
+     * and sever the thread alias. */
+    test->agent = g_steal_pointer(&priv->agent);
+    priv->eventThread = NULL;
+
+    virObjectUnlock(test->vm);
+
+    if (qemuMonitorTestAgentStart(test, &src) < 0)
+        goto error;
 
+    g_unsetenv("LIBVIRT_VSOCK_MOCK_PATH");
     return g_steal_pointer(&test);
 
  error:
+    g_unsetenv("LIBVIRT_VSOCK_MOCK_PATH");
     virDomainChrSourceDefClear(&src);
     return NULL;
 }
diff --git a/tests/qemumonitortestutils.h b/tests/qemumonitortestutils.h
index c87cdf0e6a..1908e91fc0 100644
--- a/tests/qemumonitortestutils.h
+++ b/tests/qemumonitortestutils.h
@@ -99,6 +99,10 @@ qemuMonitorTestNewFromFileFull(const char *fileName,
 qemuMonitorTest *
 qemuMonitorTestNewAgent(virDomainXMLOption *xmlopt);
 
+qemuMonitorTest *
+qemuMonitorTestNewAgentVsock(virQEMUDriver *driver,
+                             virDomainXMLOption *xmlopt);
+
 
 void
 qemuMonitorTestFree(qemuMonitorTest *test);
diff --git a/tests/qemuvsockagentmock.c b/tests/qemuvsockagentmock.c
new file mode 100644
index 0000000000..d1c2760302
--- /dev/null
+++ b/tests/qemuvsockagentmock.c
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2026 Virtuozzo International GmbH
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "internal.h"
+#include "virvsock.h"
+#include "virfile.h"
+#include "virstring.h"
+
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <errno.h>
+#include <stdlib.h>
+
+/*
+ * Override the only genuinely vsock-specific syscall wrapper so the real
+ * qemuConnectAgent()/qemuChannelOpenVsock() dispatch runs under test. The
+ * cid/port are irrelevant here: hand back an fd wired to the scripted mock-QGA
+ * unix server the test set up (mirrors qemuMonitorTestOpenChannel()).
+ */
+int
+virVsockConnectQuiet(unsigned int cid G_GNUC_UNUSED,
+                     unsigned int port G_GNUC_UNUSED)
+{
+    const char *path = getenv("LIBVIRT_VSOCK_MOCK_PATH");
+    struct sockaddr_un addr = { .sun_family = AF_UNIX };
+    int fd;
+
+    if (!path) {
+        errno = ENOENT;
+        return -1;
+    }
+
+    if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
+        return -1;
+
+    if (virStrcpyStatic(addr.sun_path, path) < 0) {
+        VIR_FORCE_CLOSE(fd);
+        errno = ENAMETOOLONG;
+        return -1;
+    }
+
+    if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+        VIR_FORCE_CLOSE(fd);
+        return -1;
+    }
+
+    return fd;
+}
-- 
2.54.0

Reply via email to