summaryrefslogtreecommitdiffstats
path: root/src/VBox/Runtime/testcase/tstRTLocalIpc.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Runtime/testcase/tstRTLocalIpc.cpp')
-rw-r--r--src/VBox/Runtime/testcase/tstRTLocalIpc.cpp970
1 files changed, 970 insertions, 0 deletions
diff --git a/src/VBox/Runtime/testcase/tstRTLocalIpc.cpp b/src/VBox/Runtime/testcase/tstRTLocalIpc.cpp
new file mode 100644
index 00000000..7bbf41c5
--- /dev/null
+++ b/src/VBox/Runtime/testcase/tstRTLocalIpc.cpp
@@ -0,0 +1,970 @@
+/* $Id: tstRTLocalIpc.cpp $ */
+/** @file
+ * IPRT Testcase - RTLocalIpc API.
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/localipc.h>
+
+#include <iprt/asm.h>
+#include <iprt/env.h>
+#include <iprt/err.h>
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/rand.h>
+#include <iprt/string.h>
+#include <iprt/test.h>
+#include <iprt/thread.h>
+#include <iprt/time.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The test instance.*/
+static RTTEST g_hTest;
+
+
+
+static void testBasics(void)
+{
+ RTTestISub("Basics");
+
+ /* Server-side. */
+ RTTESTI_CHECK_RC(RTLocalIpcServerCreate(NULL, NULL, 0), VERR_INVALID_POINTER);
+ RTLOCALIPCSERVER hIpcServer;
+ int rc;
+ RTTESTI_CHECK_RC(rc = RTLocalIpcServerCreate(&hIpcServer, NULL, 0), VERR_INVALID_POINTER);
+ if (RT_SUCCESS(rc)) RTLocalIpcServerDestroy(hIpcServer);
+ RTTESTI_CHECK_RC(rc = RTLocalIpcServerCreate(&hIpcServer, "", 0), VERR_INVALID_NAME);
+ if (RT_SUCCESS(rc)) RTLocalIpcServerDestroy(hIpcServer);
+ RTTESTI_CHECK_RC(rc = RTLocalIpcServerCreate(&hIpcServer, "BasicTest", 1234 /* Invalid flags */), VERR_INVALID_FLAGS);
+ if (RT_SUCCESS(rc)) RTLocalIpcServerDestroy(hIpcServer);
+
+ RTTESTI_CHECK_RC(RTLocalIpcServerCancel(NULL), VERR_INVALID_HANDLE);
+ RTTESTI_CHECK_RC(RTLocalIpcServerDestroy(NULL), VINF_SUCCESS);
+
+ /* Basic server creation / destruction. */
+ RTTESTI_CHECK_RC_RETV(RTLocalIpcServerCreate(&hIpcServer, "BasicTest", 0), VINF_SUCCESS);
+ RTTESTI_CHECK_RC(RTLocalIpcServerCancel(hIpcServer), VINF_SUCCESS);
+ RTTESTI_CHECK_RC(RTLocalIpcServerDestroy(hIpcServer), VINF_OBJECT_DESTROYED);
+
+ /* Client-side (per session). */
+ RTTESTI_CHECK_RC(RTLocalIpcSessionConnect(NULL, NULL, 0), VERR_INVALID_POINTER);
+ RTLOCALIPCSESSION hIpcSession;
+ RTTESTI_CHECK_RC(RTLocalIpcSessionConnect(&hIpcSession, NULL, 0), VERR_INVALID_POINTER);
+ if (RT_SUCCESS(rc)) RTLocalIpcSessionClose(hIpcSession);
+ RTTESTI_CHECK_RC(RTLocalIpcSessionConnect(&hIpcSession, "", 0), VERR_INVALID_NAME);
+ if (RT_SUCCESS(rc)) RTLocalIpcSessionClose(hIpcSession);
+ RTTESTI_CHECK_RC(RTLocalIpcSessionConnect(&hIpcSession, "BasicTest", 1234 /* Invalid flags */), VERR_INVALID_FLAGS);
+ if (RT_SUCCESS(rc)) RTLocalIpcSessionClose(hIpcSession);
+
+ RTTESTI_CHECK_RC(RTLocalIpcSessionCancel(NULL), VERR_INVALID_HANDLE);
+ RTTESTI_CHECK_RC(RTLocalIpcSessionClose(NULL), VINF_SUCCESS);
+
+ /* Basic client creation / destruction. */
+ RTTESTI_CHECK_RC_RETV(rc = RTLocalIpcSessionConnect(&hIpcSession, "BasicTest", 0), VERR_FILE_NOT_FOUND);
+ if (RT_SUCCESS(rc)) RTLocalIpcSessionClose(hIpcSession);
+ //RTTESTI_CHECK_RC(RTLocalIpcServerCancel(hIpcServer), VERR_INVALID_HANDLE); - accessing freed memory, bad idea.
+ //RTTESTI_CHECK_RC(RTLocalIpcServerDestroy(hIpcServer), VERR_INVALID_HANDLE); - accessing freed memory, bad idea.
+}
+
+
+
+/*********************************************************************************************************************************
+* *
+* testSessionConnection - Connecting. *
+* *
+*********************************************************************************************************************************/
+
+static DECLCALLBACK(int) testServerListenThread(RTTHREAD hSelf, void *pvUser)
+{
+ RTLOCALIPCSERVER hIpcServer = (RTLOCALIPCSERVER)pvUser;
+ RTTEST_CHECK_RC_OK_RET(g_hTest, RTTestSetDefault(g_hTest, NULL), rcCheck);
+
+ RTTESTI_CHECK_RC_OK(RTThreadUserSignal(hSelf));
+
+ int rc;
+ for (;;)
+ {
+ RTLOCALIPCSESSION hIpcSession;
+ rc = RTLocalIpcServerListen(hIpcServer, &hIpcSession);
+ if (RT_SUCCESS(rc))
+ {
+ RTThreadSleep(8); /* windows output fudge (purely esthetical) */
+ RTTestIPrintf(RTTESTLVL_INFO, "testServerListenThread: Got new client connection.\n");
+ RTTESTI_CHECK_RC(RTLocalIpcSessionClose(hIpcSession), VINF_OBJECT_DESTROYED);
+ }
+ else
+ {
+ RTTESTI_CHECK_RC(rc, VERR_CANCELLED);
+ break;
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * Used both as a thread procedure and child process worker.
+ */
+static DECLCALLBACK(int) tstRTLocalIpcSessionConnectionChild(RTTHREAD hSelf, void *pvUser)
+{
+ RTLOCALIPCSESSION hClientSession;
+ RT_NOREF_PV(hSelf); RT_NOREF_PV(pvUser);
+
+ RTTEST_CHECK_RC_OK_RET(g_hTest, RTTestSetDefault(g_hTest, NULL), rcCheck);
+
+ RTTEST_CHECK_RC_RET(g_hTest, RTLocalIpcSessionConnect(&hClientSession, "tstRTLocalIpcSessionConnection",0 /* Flags */),
+ VINF_SUCCESS, rcCheck);
+ RTTEST_CHECK_RC_RET(g_hTest, RTLocalIpcSessionClose(hClientSession),
+ VINF_OBJECT_DESTROYED, rcCheck);
+
+ return VINF_SUCCESS;
+}
+
+
+static void testSessionConnection(const char *pszExecPath)
+{
+ RTTestISub(!pszExecPath ? "Connect from thread" : "Connect from child");
+
+ /*
+ * Create the test server.
+ */
+ RTLOCALIPCSERVER hIpcServer;
+ RTTESTI_CHECK_RC_RETV(RTLocalIpcServerCreate(&hIpcServer, "tstRTLocalIpcSessionConnection", 0), VINF_SUCCESS);
+
+ /*
+ * Create worker thread that listens and closes incoming connections until
+ * cancelled.
+ */
+ int rc;
+ RTTHREAD hListenThread;
+ RTTESTI_CHECK_RC_OK(rc = RTThreadCreate(&hListenThread, testServerListenThread, hIpcServer, 0 /* Stack */,
+ RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "listen-1"));
+ if (RT_SUCCESS(rc))
+ {
+ RTThreadUserWait(hListenThread, 32);
+
+ /*
+ * Two variations here: Client connects from thread or a child process.
+ */
+ if (pszExecPath)
+ {
+ RTPROCESS hClientProc;
+ const char *apszArgs[4] = { pszExecPath, "child", "tstRTLocalIpcSessionConnectionChild", NULL };
+ RTTESTI_CHECK_RC_OK(rc = RTProcCreate(pszExecPath, apszArgs, RTENV_DEFAULT, 0 /* fFlags*/, &hClientProc));
+ if (RT_SUCCESS(rc))
+ {
+ RTPROCSTATUS ProcStatus;
+ RTTESTI_CHECK_RC_OK(rc = RTProcWait(hClientProc, RTPROCWAIT_FLAGS_BLOCK, &ProcStatus));
+ if (RT_SUCCESS(rc) && (ProcStatus.enmReason != RTPROCEXITREASON_NORMAL || ProcStatus.iStatus != 0))
+ RTTestIFailed("Chiled exited with enmReason=%d iStatus=%d", ProcStatus.enmReason, ProcStatus.iStatus);
+ }
+ }
+ else
+ {
+ RTTHREAD hClientThread;
+ RTTESTI_CHECK_RC_OK(rc = RTThreadCreate(&hClientThread, tstRTLocalIpcSessionConnectionChild, NULL,
+ 0 /* Stack */, RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "client-1"));
+ if (RT_SUCCESS(rc))
+ {
+ int rcThread;
+ RTTESTI_CHECK_RC_OK(rc = RTThreadWait(hClientThread, RT_MS_1MIN / 2, &rcThread));
+ if (RT_SUCCESS(rc))
+ RTTESTI_CHECK_RC(rcThread, VINF_SUCCESS);
+ }
+ }
+
+
+ /*
+ * Terminate the server thread.
+ */
+ //RTTestIPrintf(RTTESTLVL_INFO, "Child terminated, waiting for server thread ...\n");
+ RTTESTI_CHECK_RC(RTLocalIpcServerCancel(hIpcServer), VINF_SUCCESS);
+ int rcThread;
+ RTTESTI_CHECK_RC(rc = RTThreadWait(hListenThread, 30 * 1000 /* 30s timeout */, &rcThread), VINF_SUCCESS);
+ if (RT_SUCCESS(rc))
+ RTTESTI_CHECK_RC(rcThread, VERR_CANCELLED);
+ }
+
+ RTTESTI_CHECK_RC(RTLocalIpcServerDestroy(hIpcServer), VINF_OBJECT_DESTROYED);
+}
+
+
+
+/*********************************************************************************************************************************
+* *
+* testSessionWait - RTLocalIpcSessionWaitForData. *
+* *
+*********************************************************************************************************************************/
+
+static DECLCALLBACK(int) testSessionWaitThread(RTTHREAD hSelf, void *pvUser)
+{
+ RTLOCALIPCSERVER hIpcServer = (RTLOCALIPCSERVER)pvUser;
+ RTTEST_CHECK_RC_OK_RET(g_hTest, RTTestSetDefault(g_hTest, NULL), rcCheck);
+
+ int rc;
+ for (;;)
+ {
+ RTLOCALIPCSESSION hIpcSession;
+ rc = RTLocalIpcServerListen(hIpcServer, &hIpcSession);
+ if (RT_SUCCESS(rc))
+ {
+ RTTestIPrintf(RTTESTLVL_INFO, "testSessionWaitThread: Got new client connection.\n");
+
+ /* Wait for the client to trigger a disconnect by writing us something. */
+ RTTESTI_CHECK_RC(RTLocalIpcSessionWaitForData(hIpcSession, RT_MS_1MIN), VINF_SUCCESS);
+
+ size_t cbRead;
+ char szCmd[64];
+ RT_ZERO(szCmd);
+ RTTESTI_CHECK_RC(rc = RTLocalIpcSessionReadNB(hIpcSession, szCmd, sizeof(szCmd) - 1, &cbRead), VINF_SUCCESS);
+ if (RT_SUCCESS(rc) && (cbRead != sizeof("disconnect") - 1 || strcmp(szCmd, "disconnect")) )
+ RTTestIFailed("cbRead=%zu, expected %zu; szCmd='%s', expected 'disconnect'\n",
+ cbRead, sizeof("disconnect") - 1, szCmd);
+
+ RTTESTI_CHECK_RC(RTLocalIpcSessionClose(hIpcSession), VINF_OBJECT_DESTROYED);
+ RTTESTI_CHECK_RC_OK(RTThreadUserSignal(hSelf));
+ }
+ else
+ {
+ RTTESTI_CHECK_RC(rc, VERR_CANCELLED);
+ break;
+ }
+ }
+ RTTESTI_CHECK_RC_OK(RTThreadUserSignal(hSelf));
+ return rc;
+}
+
+
+/**
+ * Used both as a thread procedure and child process worker.
+ */
+static DECLCALLBACK(int) tstRTLocalIpcSessionWaitChild(RTTHREAD hSelf, void *pvUser)
+{
+ RTTEST_CHECK_RC_OK_RET(g_hTest, RTTestSetDefault(g_hTest, NULL), rcCheck);
+ RT_NOREF_PV(hSelf); RT_NOREF_PV(pvUser);
+
+ RTLOCALIPCSESSION hClientSession;
+ RTTESTI_CHECK_RC_RET(RTLocalIpcSessionConnect(&hClientSession, "tstRTLocalIpcSessionWait", 0 /*fFlags*/),
+ VINF_SUCCESS, rcCheck);
+
+ /*
+ * The server side won't write anything. It will close the connection
+ * as soon as we write something.
+ */
+ RTTESTI_CHECK_RC(RTLocalIpcSessionWaitForData(hClientSession, 0 /*cMsTimeout*/), VERR_TIMEOUT);
+ RTTESTI_CHECK_RC(RTLocalIpcSessionWaitForData(hClientSession, 8 /*cMsTimeout*/), VERR_TIMEOUT);
+ uint8_t abBuf[4];
+ size_t cbRead = _4M-1;
+ RTTESTI_CHECK_RC(RTLocalIpcSessionReadNB(hClientSession, abBuf, sizeof(abBuf), &cbRead), VINF_TRY_AGAIN);
+ RTTESTI_CHECK(cbRead == 0);
+
+ /* Trigger server disconnect. */
+ int rc;
+ RTTESTI_CHECK_RC(rc = RTLocalIpcSessionWrite(hClientSession, RT_STR_TUPLE("disconnect")), VINF_SUCCESS);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * When we wait now, we should get an broken pipe error as
+ * the server has close its end.
+ */
+ RTTESTI_CHECK_RC(rc = RTLocalIpcSessionWaitForData(hClientSession, RT_MS_1MIN), VERR_BROKEN_PIPE);
+ RTTESTI_CHECK_RC(RTLocalIpcSessionWaitForData(hClientSession, 0), VERR_BROKEN_PIPE);
+ RTTESTI_CHECK_RC(RTLocalIpcSessionWaitForData(hClientSession, RT_MS_1SEC), VERR_BROKEN_PIPE);
+
+ bool fMayPanic = RTAssertSetMayPanic(false);
+ bool fQuiet = RTAssertSetQuiet(true);
+
+ RTTESTI_CHECK_RC(RTLocalIpcSessionWrite(hClientSession, RT_STR_TUPLE("broken")), VERR_BROKEN_PIPE);
+ RTTESTI_CHECK_RC(RTLocalIpcSessionRead(hClientSession, abBuf, sizeof(abBuf), NULL), VERR_BROKEN_PIPE);
+ cbRead = _4M-1;
+ RTTESTI_CHECK_RC(RTLocalIpcSessionRead(hClientSession, abBuf, sizeof(abBuf), &cbRead), VERR_BROKEN_PIPE);
+ cbRead = _1G/2;
+ RTTESTI_CHECK_RC(RTLocalIpcSessionReadNB(hClientSession, abBuf, sizeof(abBuf), &cbRead), VERR_BROKEN_PIPE);
+
+ RTAssertSetMayPanic(fMayPanic);
+ RTAssertSetQuiet(fQuiet);
+ }
+
+ RTTESTI_CHECK_RC(RTLocalIpcSessionClose(hClientSession), VINF_OBJECT_DESTROYED);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @note This is identical to testSessionData with a couple of string and
+ * function pointers replaced.
+ */
+static void testSessionWait(const char *pszExecPath)
+{
+ RTTestISub(!pszExecPath ? "Wait for data in thread" : "Wait for data in child");
+
+ /*
+ * Create the test server.
+ */
+ RTLOCALIPCSERVER hIpcServer;
+ RTTESTI_CHECK_RC_RETV(RTLocalIpcServerCreate(&hIpcServer, "tstRTLocalIpcSessionWait", 0), VINF_SUCCESS);
+
+ /*
+ * Create worker thread that listens and processes incoming connections
+ * until cancelled.
+ */
+ int rc;
+ RTTHREAD hListenThread;
+ RTTESTI_CHECK_RC_OK(rc = RTThreadCreate(&hListenThread, testSessionWaitThread, hIpcServer, 0 /* Stack */,
+ RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "listen-2"));
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Create a client process or thread and connects to the server.
+ * It will perform the wait-for-data test.
+ */
+ RTPROCESS hClientProc = NIL_RTPROCESS;
+ RTTHREAD hClientThread = NIL_RTTHREAD;
+ if (pszExecPath)
+ {
+ const char *apszArgs[4] = { pszExecPath, "child", "tstRTLocalIpcSessionWaitChild", NULL };
+ RTTESTI_CHECK_RC_OK(rc = RTProcCreate(pszExecPath, apszArgs, RTENV_DEFAULT, 0 /* fFlags*/, &hClientProc));
+ }
+ else
+ RTTESTI_CHECK_RC_OK(rc = RTThreadCreate(&hClientThread, tstRTLocalIpcSessionWaitChild, g_hTest, 0 /*cbStack*/,
+ RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "client-2"));
+
+ /*
+ * Wait for the server thread to indicate that it has processed one
+ * connection, then shut it all down.
+ */
+ if (RT_SUCCESS(rc))
+ RTTESTI_CHECK_RC_OK(RTThreadUserWait(hListenThread, RT_MS_1MIN / 2));
+
+ RTTESTI_CHECK_RC(RTLocalIpcServerCancel(hIpcServer), VINF_SUCCESS);
+ int rcThread;
+ RTTESTI_CHECK_RC(rc = RTThreadWait(hListenThread, RT_MS_1MIN / 2, &rcThread), VINF_SUCCESS);
+ if (RT_SUCCESS(rc))
+ RTTESTI_CHECK_RC(rcThread, VERR_CANCELLED);
+
+ RTTESTI_CHECK_RC(RTLocalIpcServerDestroy(hIpcServer), VINF_OBJECT_DESTROYED);
+
+ /*
+ * Check that client ran successfully.
+ */
+ if (pszExecPath)
+ {
+ if (hClientProc != NIL_RTPROCESS)
+ {
+ RTPROCSTATUS ProcStatus;
+ RTTESTI_CHECK_RC_OK(rc = RTProcWait(hClientProc, RTPROCWAIT_FLAGS_BLOCK, &ProcStatus));
+ if (RT_SUCCESS(rc) && (ProcStatus.enmReason != RTPROCEXITREASON_NORMAL || ProcStatus.iStatus != 0))
+ RTTestIFailed("Chiled exited with enmReason=%d iStatus=%d", ProcStatus.enmReason, ProcStatus.iStatus);
+ }
+ }
+ else if (hClientThread != NIL_RTTHREAD)
+ {
+ RTTESTI_CHECK_RC_OK(rc = RTThreadWait(hClientThread, RT_MS_1MIN / 2, &rcThread));
+ if (RT_SUCCESS(rc))
+ RTTESTI_CHECK_RC(rcThread, VINF_SUCCESS);
+ }
+ }
+}
+
+
+
+/*********************************************************************************************************************************
+* *
+* testSessionData - Data transfer integrity. *
+* *
+*********************************************************************************************************************************/
+
+/** The max message size. */
+#define MAX_DATA_MSG_SIZE _1M
+
+static int testSessionDataReadMessages(RTLOCALIPCSESSION hIpcSession, uint32_t cRounds)
+{
+ /*
+ * Message scratch buffer. Search message starts with a uint32_t word
+ * that indicates the message length. The remaining words are set to
+ * the message number.
+ */
+ uint32_t *pau32ScratchBuf = (uint32_t *)RTMemAlloc(MAX_DATA_MSG_SIZE);
+ RTTESTI_CHECK_RET(pau32ScratchBuf != NULL, VERR_NO_MEMORY);
+
+ int rc = VINF_SUCCESS;
+ for (uint32_t iRound = 0; iRound < cRounds && rc == VINF_SUCCESS; iRound++)
+ {
+ /* Read the message length. */
+ uint32_t cbMsg;
+ RTTESTI_CHECK_RC_BREAK(rc = RTLocalIpcSessionRead(hIpcSession, &cbMsg, sizeof(cbMsg), NULL), VINF_SUCCESS);
+ if (cbMsg >= sizeof(cbMsg) && cbMsg <= MAX_DATA_MSG_SIZE)
+ {
+ pau32ScratchBuf[0] = cbMsg;
+
+ /* Read the message body. */
+ uint32_t cbLeft = cbMsg - sizeof(uint32_t);
+ uint8_t *pbCur = (uint8_t *)&pau32ScratchBuf[1];
+ while (cbLeft > 0)
+ {
+ uint32_t cbCur = RTRandU32Ex(1, cbLeft + cbLeft / 4);
+ cbCur = RT_MIN(cbCur, cbLeft);
+ if ((iRound % 3) == 1)
+ {
+ size_t cbRead = _1G;
+ RTTESTI_CHECK_RC_BREAK(rc = RTLocalIpcSessionRead(hIpcSession, pbCur, cbCur, &cbRead), VINF_SUCCESS);
+ RTTESTI_CHECK(cbCur >= cbRead);
+ cbCur = (uint32_t)cbRead;
+ }
+ else
+ RTTESTI_CHECK_RC_BREAK(rc = RTLocalIpcSessionRead(hIpcSession, pbCur, cbCur, NULL), VINF_SUCCESS);
+ pbCur += cbCur;
+ cbLeft -= cbCur;
+ }
+
+ /* Check the message body. */
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t offLast = cbMsg & (sizeof(uint32_t) - 1);
+ if (offLast)
+ memcpy((uint8_t *)pau32ScratchBuf + cbMsg, (uint8_t const *)&iRound + offLast, sizeof(uint32_t) - offLast);
+
+ ASMCompilerBarrier(); /* Guard against theoretical alias issues in the above code. */
+
+ uint32_t cWords = RT_ALIGN_32(cbMsg, sizeof(uint32_t)) / sizeof(uint32_t);
+ for (uint32_t iWord = 1; iWord < cWords; iWord++)
+ if (pau32ScratchBuf[iWord] != iRound)
+ {
+ RTTestIFailed("Message body word #%u mismatch: %#x, expected %#x", iWord, pau32ScratchBuf[iWord], iRound);
+ break;
+ }
+ }
+ }
+ else
+ {
+ RTTestIFailed("cbMsg=%#x is out of range", cbMsg);
+ rc = VERR_OUT_OF_RANGE;
+ }
+ }
+
+ RTMemFree(pau32ScratchBuf);
+ return rc;
+}
+
+
+static int testSessionDataWriteMessages(RTLOCALIPCSESSION hIpcSession, uint32_t cRounds)
+{
+ /*
+ * Message scratch buffer. Search message starts with a uint32_t word
+ * that indicates the message length. The remaining words are set to
+ * the message number.
+ */
+ uint32_t cbScratchBuf = RTRandU32Ex(64, MAX_DATA_MSG_SIZE);
+ cbScratchBuf = RT_ALIGN_32(cbScratchBuf, sizeof(uint32_t));
+
+ uint32_t *pau32ScratchBuf = (uint32_t *)RTMemAlloc(cbScratchBuf);
+ RTTESTI_CHECK_RET(pau32ScratchBuf != NULL, VERR_NO_MEMORY);
+
+ size_t cbSent = 0;
+ int rc = VINF_SUCCESS;
+ for (uint32_t iRound = 0; iRound < cRounds && rc == VINF_SUCCESS; iRound++)
+ {
+ /* Construct the message. */
+ uint32_t cbMsg = RTRandU32Ex(sizeof(uint32_t), cbScratchBuf);
+ uint32_t cWords = RT_ALIGN_32(cbMsg, sizeof(uint32_t)) / sizeof(uint32_t);
+
+ uint32_t iWord = 0;
+ pau32ScratchBuf[iWord++] = cbMsg;
+ while (iWord < cWords)
+ pau32ScratchBuf[iWord++] = iRound;
+
+ /* Send it. */
+ uint32_t cbLeft = cbMsg;
+ uint8_t const *pbCur = (uint8_t *)pau32ScratchBuf;
+ while (cbLeft > 0)
+ {
+ uint32_t cbCur = RT_MIN(iRound + 1, cbLeft);
+ RTTESTI_CHECK_RC_BREAK(rc = RTLocalIpcSessionWrite(hIpcSession, pbCur, cbCur), VINF_SUCCESS);
+ pbCur += cbCur;
+ cbSent += cbCur;
+ cbLeft -= cbCur;
+ }
+ }
+
+ RTTestIPrintf(RTTESTLVL_ALWAYS, "Sent %'zu bytes over %u rounds.\n", cbSent, cRounds);
+ RTMemFree(pau32ScratchBuf);
+ return rc;
+}
+
+
+static DECLCALLBACK(int) testSessionDataThread(RTTHREAD hSelf, void *pvUser)
+{
+ RTLOCALIPCSERVER hIpcServer = (RTLOCALIPCSERVER)pvUser;
+ RTTEST_CHECK_RC_OK_RET(g_hTest, RTTestSetDefault(g_hTest, NULL), rcCheck);
+
+ int rc;
+ for (;;)
+ {
+ RTLOCALIPCSESSION hIpcSession;
+ rc = RTLocalIpcServerListen(hIpcServer, &hIpcSession);
+ if (RT_SUCCESS(rc))
+ {
+ RTTestIPrintf(RTTESTLVL_INFO, "testSessionDataThread: Got new client connection\n");
+
+ /* The server is the initator. First message sets the number of rounds. */
+ uint32_t cRounds = RTRandU32Ex(32, _1K);
+ RTTESTI_CHECK_RC(rc = RTLocalIpcSessionWrite(hIpcSession, &cRounds, sizeof(cRounds)), VINF_SUCCESS);
+ if (RT_SUCCESS(rc))
+ {
+ rc = testSessionDataWriteMessages(hIpcSession, cRounds);
+ if (RT_SUCCESS(rc))
+ rc = testSessionDataReadMessages(hIpcSession, cRounds);
+ }
+
+ RTTESTI_CHECK_RC(RTLocalIpcSessionClose(hIpcSession), VINF_OBJECT_DESTROYED);
+ RTTESTI_CHECK_RC_OK(RTThreadUserSignal(hSelf));
+ }
+ else
+ {
+ RTTESTI_CHECK_RC(rc, VERR_CANCELLED);
+ break;
+ }
+ }
+ RTTESTI_CHECK_RC_OK(RTThreadUserSignal(hSelf));
+ return rc;
+}
+
+
+/**
+ * Used both as a thread procedure and child process worker.
+ */
+static DECLCALLBACK(int) tstRTLocalIpcSessionDataChild(RTTHREAD hSelf, void *pvUser)
+{
+ RTTEST_CHECK_RC_OK_RET(g_hTest, RTTestSetDefault(g_hTest, NULL), rcCheck);
+ RT_NOREF_PV(hSelf); RT_NOREF_PV(pvUser);
+
+ /*
+ * Connect.
+ */
+ RTLOCALIPCSESSION hClientSession;
+ RTTESTI_CHECK_RC_RET(RTLocalIpcSessionConnect(&hClientSession, "tstRTLocalIpcSessionData", 0 /*fFlags*/),
+ VINF_SUCCESS, rcCheck);
+
+ /*
+ * The server first sends us a rounds count.
+ */
+ int rc;
+ uint32_t cRounds = 0;
+ RTTESTI_CHECK_RC(rc = RTLocalIpcSessionRead(hClientSession, &cRounds, sizeof(cRounds), NULL), VINF_SUCCESS);
+ if (RT_SUCCESS(rc))
+ {
+ if (cRounds >= 32 && cRounds <= _1K)
+ {
+ rc = testSessionDataReadMessages(hClientSession, cRounds);
+ if (RT_SUCCESS(rc))
+ rc = testSessionDataWriteMessages(hClientSession, cRounds);
+ }
+ else
+ RTTestIFailed("cRounds=%#x is out of range", cRounds);
+ }
+
+ RTTESTI_CHECK_RC(RTLocalIpcSessionClose(hClientSession), VINF_OBJECT_DESTROYED);
+
+ return rc;
+}
+
+
+/**
+ * @note This is identical to testSessionWait with a couple of strings, function
+ * pointers, and timeouts replaced.
+ */
+static void testSessionData(const char *pszExecPath)
+{
+ RTTestISub(!pszExecPath ? "Data exchange with thread" : "Data exchange with child");
+
+ /*
+ * Create the test server.
+ */
+ RTLOCALIPCSERVER hIpcServer;
+ RTTESTI_CHECK_RC_RETV(RTLocalIpcServerCreate(&hIpcServer, "tstRTLocalIpcSessionData", 0), VINF_SUCCESS);
+
+ /*
+ * Create worker thread that listens and processes incoming connections
+ * until cancelled.
+ */
+ int rc;
+ RTTHREAD hListenThread;
+ RTTESTI_CHECK_RC_OK(rc = RTThreadCreate(&hListenThread, testSessionDataThread, hIpcServer, 0 /* Stack */,
+ RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "listen-3"));
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Create a client thread or process.
+ */
+ RTPROCESS hClientProc = NIL_RTPROCESS;
+ RTTHREAD hClientThread = NIL_RTTHREAD;
+ if (pszExecPath)
+ {
+ const char *apszArgs[4] = { pszExecPath, "child", "tstRTLocalIpcSessionDataChild", NULL };
+ RTTESTI_CHECK_RC_OK(rc = RTProcCreate(pszExecPath, apszArgs, RTENV_DEFAULT, 0 /* fFlags*/, &hClientProc));
+ }
+ else
+ RTTESTI_CHECK_RC_OK(rc = RTThreadCreate(&hClientThread, tstRTLocalIpcSessionDataChild, g_hTest, 0 /*cbStack*/,
+ RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "client-2"));
+
+ /*
+ * Wait for the server thread to indicate that it has processed one
+ * connection, then shut it all down.
+ */
+ if (RT_SUCCESS(rc))
+ RTTESTI_CHECK_RC_OK(RTThreadUserWait(hListenThread, RT_MS_1MIN * 3));
+
+ RTTESTI_CHECK_RC(RTLocalIpcServerCancel(hIpcServer), VINF_SUCCESS);
+ int rcThread;
+ RTTESTI_CHECK_RC(rc = RTThreadWait(hListenThread, RT_MS_1MIN / 2, &rcThread), VINF_SUCCESS);
+ if (RT_SUCCESS(rc))
+ RTTESTI_CHECK_RC(rcThread, VERR_CANCELLED);
+
+ RTTESTI_CHECK_RC(RTLocalIpcServerDestroy(hIpcServer), VINF_OBJECT_DESTROYED);
+
+ /*
+ * Check that client ran successfully.
+ */
+ if (pszExecPath)
+ {
+ if (hClientProc != NIL_RTPROCESS)
+ {
+ RTPROCSTATUS ProcStatus;
+ RTTESTI_CHECK_RC_OK(rc = RTProcWait(hClientProc, RTPROCWAIT_FLAGS_BLOCK, &ProcStatus));
+ if (RT_SUCCESS(rc) && (ProcStatus.enmReason != RTPROCEXITREASON_NORMAL || ProcStatus.iStatus != 0))
+ RTTestIFailed("Chiled exited with enmReason=%d iStatus=%d", ProcStatus.enmReason, ProcStatus.iStatus);
+ }
+ }
+ else if (hClientThread != NIL_RTTHREAD)
+ {
+ RTTESTI_CHECK_RC_OK(rc = RTThreadWait(hClientThread, RT_MS_1MIN / 2, &rcThread));
+ if (RT_SUCCESS(rc))
+ RTTESTI_CHECK_RC(rcThread, VINF_SUCCESS);
+ }
+ }
+}
+
+
+/*********************************************************************************************************************************
+* *
+* testSessionPerf - Performance measurements. *
+* *
+*********************************************************************************************************************************/
+
+#define IPC_PERF_LAST_MSG UINT32_C(0x7fffeeee)
+#define IPC_PERF_MSG_REPLY(uMsg) ((uMsg) | RT_BIT_32(31))
+
+
+static DECLCALLBACK(int) testSessionPerfThread(RTTHREAD hSelf, void *pvUser)
+{
+ RTLOCALIPCSERVER hIpcServer = (RTLOCALIPCSERVER)pvUser;
+ RTTEST_CHECK_RC_OK_RET(g_hTest, RTTestSetDefault(g_hTest, NULL), rcCheck);
+
+ int rc;
+ for (;;)
+ {
+ RTLOCALIPCSESSION hIpcSession;
+ rc = RTLocalIpcServerListen(hIpcServer, &hIpcSession);
+ if (RT_SUCCESS(rc))
+ {
+ RTTestIPrintf(RTTESTLVL_INFO, "testSessionPerfThread: Got new client connection\n");
+
+ /* The server is the initator, so we start sending messages. */
+ uint64_t cNsElapsed = _4G;
+ uint64_t nsStart = RTTimeNanoTS();
+ uint32_t cMessages = 0;
+ for (;; )
+ {
+ uint32_t uMsg = cMessages;
+ RTTESTI_CHECK_RC_BREAK(rc = RTLocalIpcSessionWrite(hIpcSession, &uMsg, sizeof(uMsg)), VINF_SUCCESS);
+ uMsg = UINT32_MAX;
+ RTTESTI_CHECK_RC_BREAK(rc = RTLocalIpcSessionRead(hIpcSession, &uMsg, sizeof(uMsg), NULL), VINF_SUCCESS);
+ if (uMsg == IPC_PERF_MSG_REPLY(cMessages))
+ { /* likely */ }
+ else
+ {
+ RTTestIFailed("uMsg=%#x expected %#x", uMsg, IPC_PERF_MSG_REPLY(cMessages));
+ rc = VERR_OUT_OF_RANGE;
+ break;
+ }
+
+ /* next */
+ cMessages++;
+ if (cMessages & _16K)
+ { /* likely */ }
+ else
+ {
+ cNsElapsed = RTTimeNanoTS() - nsStart;
+ if (cNsElapsed > 2*RT_NS_1SEC_64)
+ {
+ uMsg = IPC_PERF_LAST_MSG;
+ RTTESTI_CHECK_RC_BREAK(rc = RTLocalIpcSessionWrite(hIpcSession, &uMsg, sizeof(uMsg)), VINF_SUCCESS);
+ break;
+ }
+ }
+ }
+ if (RT_SUCCESS(rc))
+ {
+ RTThreadSleep(8); /* windows output fudge (purely esthetical) */
+ RTTestIValue("roundtrip", cNsElapsed / cMessages, RTTESTUNIT_NS_PER_ROUND_TRIP);
+ RTTestIValue("roundtrips", RT_NS_1SEC / (cNsElapsed / cMessages), RTTESTUNIT_OCCURRENCES_PER_SEC);
+ }
+
+ RTTESTI_CHECK_RC(RTLocalIpcSessionClose(hIpcSession), VINF_OBJECT_DESTROYED);
+ RTTESTI_CHECK_RC_OK(RTThreadUserSignal(hSelf));
+ }
+ else
+ {
+ RTTESTI_CHECK_RC(rc, VERR_CANCELLED);
+ break;
+ }
+ }
+ RTTESTI_CHECK_RC_OK(RTThreadUserSignal(hSelf));
+ return rc;
+}
+
+
+/**
+ * Used both as a thread procedure and child process worker.
+ */
+static DECLCALLBACK(int) tstRTLocalIpcSessionPerfChild(RTTHREAD hSelf, void *pvUser)
+{
+ RTTEST_CHECK_RC_OK_RET(g_hTest, RTTestSetDefault(g_hTest, NULL), rcCheck);
+ RT_NOREF_PV(hSelf); RT_NOREF_PV(pvUser);
+
+ /*
+ * Connect.
+ */
+ RTLOCALIPCSESSION hClientSession;
+ RTTESTI_CHECK_RC_RET(RTLocalIpcSessionConnect(&hClientSession, "tstRTLocalIpcSessionPerf", 0 /*fFlags*/),
+ VINF_SUCCESS, rcCheck);
+
+ /*
+ * Process messages. Server does all the timing and stuff.
+ */
+ int rc = VINF_SUCCESS;
+ for (uint32_t cMessages = 0; ; cMessages++)
+ {
+ /* Read the next message from the server. */
+ uint32_t uMsg = UINT32_MAX;
+ RTTESTI_CHECK_RC_BREAK(rc = RTLocalIpcSessionRead(hClientSession, &uMsg, sizeof(uMsg), NULL), VINF_SUCCESS);
+ if (uMsg == cMessages)
+ {
+ uMsg = IPC_PERF_MSG_REPLY(uMsg);
+ RTTESTI_CHECK_RC_BREAK(rc = RTLocalIpcSessionWrite(hClientSession, &uMsg, sizeof(uMsg)), VINF_SUCCESS);
+ }
+ else if (uMsg == IPC_PERF_LAST_MSG)
+ break;
+ else
+ {
+ RTTestIFailed("uMsg=%#x expected %#x", uMsg, cMessages);
+ rc = VERR_OUT_OF_RANGE;
+ break;
+ }
+ }
+
+ RTTESTI_CHECK_RC(RTLocalIpcSessionClose(hClientSession), VINF_OBJECT_DESTROYED);
+ return rc;
+}
+
+
+/**
+ * @note This is identical to testSessionWait with a couple of string and
+ * function pointers replaced.
+ */
+static void testSessionPerf(const char *pszExecPath)
+{
+ RTTestISub(!pszExecPath ? "Thread performance" : "Child performance");
+
+ /*
+ * Create the test server.
+ */
+ RTLOCALIPCSERVER hIpcServer;
+ RTTESTI_CHECK_RC_RETV(RTLocalIpcServerCreate(&hIpcServer, "tstRTLocalIpcSessionPerf", 0), VINF_SUCCESS);
+
+ /*
+ * Create worker thread that listens and processes incoming connections
+ * until cancelled.
+ */
+ int rc;
+ RTTHREAD hListenThread;
+ RTTESTI_CHECK_RC_OK(rc = RTThreadCreate(&hListenThread, testSessionPerfThread, hIpcServer, 0 /* Stack */,
+ RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "listen-3"));
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Create a client thread or process.
+ */
+ RTPROCESS hClientProc = NIL_RTPROCESS;
+ RTTHREAD hClientThread = NIL_RTTHREAD;
+ if (pszExecPath)
+ {
+ const char *apszArgs[4] = { pszExecPath, "child", "tstRTLocalIpcSessionPerfChild", NULL };
+ RTTESTI_CHECK_RC_OK(rc = RTProcCreate(pszExecPath, apszArgs, RTENV_DEFAULT, 0 /* fFlags*/, &hClientProc));
+ }
+ else
+ RTTESTI_CHECK_RC_OK(rc = RTThreadCreate(&hClientThread, tstRTLocalIpcSessionPerfChild, g_hTest, 0 /*cbStack*/,
+ RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "client-2"));
+
+ /*
+ * Wait for the server thread to indicate that it has processed one
+ * connection, then shut it all down.
+ */
+ if (RT_SUCCESS(rc))
+ RTTESTI_CHECK_RC_OK(RTThreadUserWait(hListenThread, RT_MS_1MIN / 2));
+
+ RTTESTI_CHECK_RC(RTLocalIpcServerCancel(hIpcServer), VINF_SUCCESS);
+ int rcThread;
+ RTTESTI_CHECK_RC(rc = RTThreadWait(hListenThread, RT_MS_1MIN / 2, &rcThread), VINF_SUCCESS);
+ if (RT_SUCCESS(rc))
+ RTTESTI_CHECK_RC(rcThread, VERR_CANCELLED);
+
+ RTTESTI_CHECK_RC(RTLocalIpcServerDestroy(hIpcServer), VINF_OBJECT_DESTROYED);
+
+ /*
+ * Check that client ran successfully.
+ */
+ if (pszExecPath)
+ {
+ if (hClientProc != NIL_RTPROCESS)
+ {
+ RTPROCSTATUS ProcStatus;
+ RTTESTI_CHECK_RC_OK(rc = RTProcWait(hClientProc, RTPROCWAIT_FLAGS_BLOCK, &ProcStatus));
+ if (RT_SUCCESS(rc) && (ProcStatus.enmReason != RTPROCEXITREASON_NORMAL || ProcStatus.iStatus != 0))
+ RTTestIFailed("Chiled exited with enmReason=%d iStatus=%d", ProcStatus.enmReason, ProcStatus.iStatus);
+ }
+ }
+ else if (hClientThread != NIL_RTTHREAD)
+ {
+ RTTESTI_CHECK_RC_OK(rc = RTThreadWait(hClientThread, RT_MS_1MIN / 2, &rcThread));
+ if (RT_SUCCESS(rc))
+ RTTESTI_CHECK_RC(rcThread, VINF_SUCCESS);
+ }
+ }
+}
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ /*
+ * Main process.
+ */
+ if (argc == 1)
+ {
+ rc = RTTestCreate("tstRTLocalIpc", &g_hTest);
+ if (RT_FAILURE(rc))
+ return RTEXITCODE_FAILURE;
+ RTTestBanner(g_hTest);
+
+ /* Basics first. */
+ bool fMayPanic = RTAssertSetMayPanic(false);
+ bool fQuiet = RTAssertSetQuiet(true);
+ testBasics();
+ RTAssertSetMayPanic(fMayPanic);
+ RTAssertSetQuiet(fQuiet);
+
+ /* Do real tests if the basics are fine. */
+ char szExecPath[RTPATH_MAX];
+ if (RTProcGetExecutablePath(szExecPath, sizeof(szExecPath)))
+ {
+ if (RTTestErrorCount(g_hTest) == 0)
+ testSessionConnection(NULL);
+ if (RTTestErrorCount(g_hTest) == 0)
+ testSessionConnection(szExecPath);
+
+ if (RTTestErrorCount(g_hTest) == 0)
+ testSessionWait(NULL);
+ if (RTTestErrorCount(g_hTest) == 0)
+ testSessionWait(szExecPath);
+
+ if (RTTestErrorCount(g_hTest) == 0)
+ testSessionData(NULL);
+ if (RTTestErrorCount(g_hTest) == 0)
+ testSessionData(szExecPath);
+
+ if (RTTestErrorCount(g_hTest) == 0)
+ testSessionPerf(NULL);
+ if (RTTestErrorCount(g_hTest) == 0)
+ testSessionPerf(szExecPath);
+ }
+ else
+ RTTestIFailed("RTProcGetExecutablePath failed");
+ }
+ /*
+ * Child process.
+ */
+ else if ( argc == 3
+ && !strcmp(argv[1], "child"))
+ {
+ rc = RTTestCreateChild(argv[2], &g_hTest);
+ if (RT_FAILURE(rc))
+ return RTEXITCODE_FAILURE;
+
+ if (!strcmp(argv[2], "tstRTLocalIpcSessionConnectionChild"))
+ tstRTLocalIpcSessionConnectionChild(RTThreadSelf(), g_hTest);
+ else if (!strcmp(argv[2], "tstRTLocalIpcSessionWaitChild"))
+ tstRTLocalIpcSessionWaitChild(RTThreadSelf(), g_hTest);
+ else if (!strcmp(argv[2], "tstRTLocalIpcSessionDataChild"))
+ tstRTLocalIpcSessionDataChild(RTThreadSelf(), g_hTest);
+ else if (!strcmp(argv[2], "tstRTLocalIpcSessionPerfChild"))
+ tstRTLocalIpcSessionPerfChild(RTThreadSelf(), g_hTest);
+ else
+ RTTestIFailed("Unknown child function '%s'", argv[2]);
+ }
+ /*
+ * Invalid parameters.
+ */
+ else
+ return RTEXITCODE_SYNTAX;
+
+ /*
+ * Summary.
+ */
+ return RTTestSummaryAndDestroy(g_hTest);
+}
+