summaryrefslogtreecommitdiffstats
path: root/src/VBox/Runtime/r3/test.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Runtime/r3/test.cpp')
-rw-r--r--src/VBox/Runtime/r3/test.cpp1662
1 files changed, 1662 insertions, 0 deletions
diff --git a/src/VBox/Runtime/r3/test.cpp b/src/VBox/Runtime/r3/test.cpp
new file mode 100644
index 00000000..a0b90870
--- /dev/null
+++ b/src/VBox/Runtime/r3/test.cpp
@@ -0,0 +1,1662 @@
+/* $Id: test.cpp $ */
+/** @file
+ * IPRT - Testcase Framework.
+ */
+
+/*
+ * Copyright (C) 2009-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/test.h>
+
+#include <iprt/asm.h>
+#include <iprt/critsect.h>
+#include <iprt/env.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/once.h>
+#include <iprt/param.h>
+#include <iprt/pipe.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+
+#include "internal/magics.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Guarded memory allocation record.
+ */
+typedef struct RTTESTGUARDEDMEM
+{
+ /** Pointer to the next record. */
+ struct RTTESTGUARDEDMEM *pNext;
+ /** The address we return to the user. */
+ void *pvUser;
+ /** The base address of the allocation. */
+ void *pvAlloc;
+ /** The size of the allocation. */
+ size_t cbAlloc;
+ /** Guards. */
+ struct
+ {
+ /** The guard address. */
+ void *pv;
+ /** The guard size. */
+ size_t cb;
+ } aGuards[2];
+} RTTESTGUARDEDMEM;
+/** Pointer to an guarded memory allocation. */
+typedef RTTESTGUARDEDMEM *PRTTESTGUARDEDMEM;
+
+/**
+ * Test instance structure.
+ */
+typedef struct RTTESTINT
+{
+ /** Magic. */
+ uint32_t u32Magic;
+ /** The number of errors. */
+ volatile uint32_t cErrors;
+ /** The test name. */
+ const char *pszTest;
+ /** The length of the test name. */
+ size_t cchTest;
+ /** The size of a guard. Multiple of PAGE_SIZE. */
+ uint32_t cbGuard;
+ /** The verbosity level. */
+ RTTESTLVL enmMaxLevel;
+ /** The creation flags. */
+ uint32_t fFlags;
+
+
+ /** Critical section serializing output. */
+ RTCRITSECT OutputLock;
+ /** The output stream. */
+ PRTSTREAM pOutStrm;
+ /** Whether we're currently at a newline. */
+ bool fNewLine;
+
+
+ /** Critical section serializing access to the members following it. */
+ RTCRITSECT Lock;
+
+ /** The list of guarded memory allocations. */
+ PRTTESTGUARDEDMEM pGuardedMem;
+
+ /** The current sub-test. */
+ const char *pszSubTest;
+ /** The length of the sub-test name. */
+ size_t cchSubTest;
+ /** Whether the current subtest should figure as 'SKIPPED'. */
+ bool fSubTestSkipped;
+ /** Whether we've reported the sub-test result or not. */
+ bool fSubTestReported;
+ /** The start error count of the current subtest. */
+ uint32_t cSubTestAtErrors;
+
+ /** The number of sub tests. */
+ uint32_t cSubTests;
+ /** The number of sub tests that failed. */
+ uint32_t cSubTestsFailed;
+
+ /** Error context message. */
+ char *pszErrCtx;
+
+ /** Set if XML output is enabled. */
+ bool fXmlEnabled;
+ /** Set if we omit the top level test in the XML report. */
+ bool fXmlOmitTopTest;
+ /** Set if we've reported the top test (for RTTEST_C_XML_DELAY_TOP_TEST). */
+ bool fXmlTopTestDone;
+ enum {
+ kXmlPos_ValueStart,
+ kXmlPos_Value,
+ kXmlPos_ElementEnd
+ } eXmlState;
+ /** Test pipe for the XML output stream going to the server. */
+ RTPIPE hXmlPipe;
+ /** File where the XML output stream might be directed. */
+ RTFILE hXmlFile;
+ /** The number of XML elements on the stack. */
+ size_t cXmlElements;
+ /** XML element stack. */
+ const char *apszXmlElements[10];
+
+ /** Number of times assertions has been disabled and quieted. */
+ uint32_t volatile cAssertionsDisabledAndQuieted;
+ /** Saved RTAssertSetQuiet return code. */
+ bool fAssertSavedQuiet;
+ /** Saved RTAssertSetMayPanic return code. */
+ bool fAssertSavedMayPanic;
+} RTTESTINT;
+/** Pointer to a test instance. */
+typedef RTTESTINT *PRTTESTINT;
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Validate a test instance. */
+#define RTTEST_VALID_RETURN(pTest) \
+ do { \
+ AssertPtrReturn(pTest, VERR_INVALID_HANDLE); \
+ AssertReturn(pTest->u32Magic == RTTESTINT_MAGIC, VERR_INVALID_HANDLE); \
+ } while (0)
+
+/** Gets and validates a test instance.
+ * If the handle is nil, we will try retrieve it from the test TLS entry.
+ */
+#define RTTEST_GET_VALID_RETURN(pTest) \
+ do { \
+ if (pTest == NIL_RTTEST) \
+ pTest = (PRTTESTINT)RTTlsGet(g_iTestTls); \
+ AssertPtrReturn(pTest, VERR_INVALID_HANDLE); \
+ AssertReturn(pTest->u32Magic == RTTESTINT_MAGIC, VERR_INVALID_MAGIC); \
+ } while (0)
+
+
+/** Gets and validates a test instance.
+ * If the handle is nil, we will try retrieve it from the test TLS entry.
+ */
+#define RTTEST_GET_VALID_RETURN_RC(pTest, rc) \
+ do { \
+ if (pTest == NIL_RTTEST) \
+ pTest = (PRTTESTINT)RTTlsGet(g_iTestTls); \
+ AssertPtrReturn(pTest, (rc)); \
+ AssertReturn(pTest->u32Magic == RTTESTINT_MAGIC, (rc)); \
+ } while (0)
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static void rtTestGuardedFreeOne(PRTTESTGUARDEDMEM pMem);
+static int rtTestPrintf(PRTTESTINT pTest, const char *pszFormat, ...);
+static void rtTestXmlStart(PRTTESTINT pTest, const char *pszTest);
+static void rtTestXmlElemV(PRTTESTINT pTest, const char *pszTag, const char *pszAttrFmt, va_list va);
+static void rtTestXmlElem(PRTTESTINT pTest, const char *pszTag, const char *pszAttrFmt, ...);
+static void rtTestXmlElemStartV(PRTTESTINT pTest, const char *pszTag, const char *pszAttrFmt, va_list va);
+static void rtTestXmlElemStart(PRTTESTINT pTest, const char *pszTag, const char *pszAttrFmt, ...);
+static void rtTestXmlElemEnd(PRTTESTINT pTest, const char *pszTag);
+static void rtTestXmlEnd(PRTTESTINT pTest);
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** For serializing TLS init. */
+static RTONCE g_TestInitOnce = RTONCE_INITIALIZER;
+/** Our TLS entry. */
+static RTTLS g_iTestTls = NIL_RTTLS;
+
+
+
+/**
+ * Init TLS index once.
+ *
+ * @returns IPRT status code.
+ * @param pvUser Ignored.
+ */
+static DECLCALLBACK(int32_t) rtTestInitOnce(void *pvUser)
+{
+ NOREF(pvUser);
+ return RTTlsAllocEx(&g_iTestTls, NULL);
+}
+
+
+RTR3DECL(int) RTTestCreateEx(const char *pszTest, uint32_t fFlags, RTTESTLVL enmMaxLevel,
+ RTHCINTPTR iNativeTestPipe, const char *pszXmlFile, PRTTEST phTest)
+{
+ AssertReturn(!(fFlags & ~RTTEST_C_VALID_MASK), VERR_INVALID_PARAMETER);
+ AssertPtrNull(phTest);
+ AssertPtrNull(pszXmlFile);
+ /* RTTESTLVL_INVALID is valid! */
+ AssertReturn(enmMaxLevel >= RTTESTLVL_INVALID && enmMaxLevel < RTTESTLVL_END, VERR_INVALID_PARAMETER);
+
+ /*
+ * Global init.
+ */
+ int rc = RTOnce(&g_TestInitOnce, rtTestInitOnce, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Create the instance.
+ */
+ PRTTESTINT pTest = (PRTTESTINT)RTMemAllocZ(sizeof(*pTest));
+ if (!pTest)
+ return VERR_NO_MEMORY;
+ pTest->u32Magic = RTTESTINT_MAGIC;
+ pTest->pszTest = RTStrDup(pszTest);
+ pTest->cchTest = strlen(pszTest);
+ pTest->cbGuard = PAGE_SIZE * 7;
+ pTest->enmMaxLevel = enmMaxLevel == RTTESTLVL_INVALID ? RTTESTLVL_INFO : enmMaxLevel;
+ pTest->fFlags = fFlags;
+
+ pTest->pOutStrm = g_pStdOut;
+ pTest->fNewLine = true;
+
+ pTest->pGuardedMem = NULL;
+
+ pTest->pszSubTest = NULL;
+ pTest->cchSubTest = 0;
+ pTest->fSubTestSkipped = false;
+ pTest->fSubTestReported = true;
+ pTest->cSubTestAtErrors = 0;
+ pTest->cSubTests = 0;
+ pTest->cSubTestsFailed = 0;
+
+ pTest->fXmlEnabled = false;
+ pTest->fXmlTopTestDone = false;
+ pTest->eXmlState = RTTESTINT::kXmlPos_ElementEnd;
+ pTest->hXmlPipe = NIL_RTPIPE;
+ pTest->hXmlFile = NIL_RTFILE;
+ pTest->cXmlElements = 0;
+ pTest->cAssertionsDisabledAndQuieted = 0;
+ pTest->fAssertSavedMayPanic = true;
+ pTest->fAssertSavedQuiet = false;
+
+ rc = RTCritSectInit(&pTest->Lock);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCritSectInit(&pTest->OutputLock);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Associate it with our TLS entry unless there is already
+ * an instance there.
+ */
+ if ( !(fFlags & RTTEST_C_NO_TLS)
+ && !RTTlsGet(g_iTestTls))
+ rc = RTTlsSet(g_iTestTls, pTest);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Output level override?
+ */
+ char szEnvVal[RTPATH_MAX];
+ if ((fFlags & RTTEST_C_USE_ENV) && enmMaxLevel == RTTESTLVL_INVALID)
+ {
+ rc = RTEnvGetEx(RTENV_DEFAULT, "IPRT_TEST_MAX_LEVEL", szEnvVal, sizeof(szEnvVal), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ char *pszMaxLevel = RTStrStrip(szEnvVal);
+ if (!strcmp(pszMaxLevel, "all"))
+ pTest->enmMaxLevel = RTTESTLVL_DEBUG;
+ if (!strcmp(pszMaxLevel, "quiet"))
+ pTest->enmMaxLevel = RTTESTLVL_FAILURE;
+ else if (!strcmp(pszMaxLevel, "debug"))
+ pTest->enmMaxLevel = RTTESTLVL_DEBUG;
+ else if (!strcmp(pszMaxLevel, "info"))
+ pTest->enmMaxLevel = RTTESTLVL_INFO;
+ else if (!strcmp(pszMaxLevel, "sub_test"))
+ pTest->enmMaxLevel = RTTESTLVL_SUB_TEST;
+ else if (!strcmp(pszMaxLevel, "failure"))
+ pTest->enmMaxLevel = RTTESTLVL_FAILURE;
+ }
+ else if (rc != VERR_ENV_VAR_NOT_FOUND)
+ RTStrmPrintf(g_pStdErr, "%s: test pipe error: RTEnvGetEx(IPRT_TEST_MAX_LEVEL) -> %Rrc\n", pszTest, rc);
+ }
+
+ /*
+ * Any test driver we are connected or should connect to?
+ */
+ if (!(fFlags & RTTEST_C_NO_XML_REPORTING_PIPE))
+ {
+ if ( (fFlags & RTTEST_C_USE_ENV)
+ && iNativeTestPipe == -1)
+ {
+ rc = RTEnvGetEx(RTENV_DEFAULT, "IPRT_TEST_PIPE", szEnvVal, sizeof(szEnvVal), NULL);
+ if (RT_SUCCESS(rc))
+ {
+#if ARCH_BITS == 64
+ rc = RTStrToInt64Full(szEnvVal, 0, &iNativeTestPipe);
+#else
+ rc = RTStrToInt32Full(szEnvVal, 0, &iNativeTestPipe);
+#endif
+ if (RT_FAILURE(rc))
+ {
+ RTStrmPrintf(g_pStdErr, "%s: test pipe error: RTStrToInt32Full(\"%s\") -> %Rrc\n",
+ pszTest, szEnvVal, rc);
+ iNativeTestPipe = -1;
+ }
+ }
+ else if (rc != VERR_ENV_VAR_NOT_FOUND)
+ RTStrmPrintf(g_pStdErr, "%s: test pipe error: RTEnvGetEx(IPRT_TEST_PIPE) -> %Rrc\n", pszTest, rc);
+ }
+ if (iNativeTestPipe != -1)
+ {
+ rc = RTPipeFromNative(&pTest->hXmlPipe, iNativeTestPipe, RTPIPE_N_WRITE);
+ if (RT_SUCCESS(rc))
+ pTest->fXmlEnabled = true;
+ else
+ {
+ RTStrmPrintf(g_pStdErr, "%s: test pipe error: RTPipeFromNative(,%p,WRITE) -> %Rrc\n",
+ pszTest, iNativeTestPipe, rc);
+ pTest->hXmlPipe = NIL_RTPIPE;
+ }
+ }
+ }
+
+ /*
+ * Any test file we should write the test report to?
+ */
+ if (!(fFlags & RTTEST_C_NO_XML_REPORTING_FILE))
+ {
+ if ((fFlags & RTTEST_C_USE_ENV) && pszXmlFile == NULL)
+ {
+ rc = RTEnvGetEx(RTENV_DEFAULT, "IPRT_TEST_FILE", szEnvVal, sizeof(szEnvVal), NULL);
+ if (RT_SUCCESS(rc))
+ pszXmlFile = szEnvVal;
+ else if (rc != VERR_ENV_VAR_NOT_FOUND)
+ RTStrmPrintf(g_pStdErr, "%s: test file error: RTEnvGetEx(IPRT_TEST_MAX_LEVEL) -> %Rrc\n", pszTest, rc);
+ }
+ if (pszXmlFile && *pszXmlFile)
+ {
+ rc = RTFileOpen(&pTest->hXmlFile, pszXmlFile,
+ RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN_CREATE | RTFILE_O_TRUNCATE);
+ if (RT_SUCCESS(rc))
+ pTest->fXmlEnabled = true;
+ else
+ {
+ RTStrmPrintf(g_pStdErr, "%s: test file error: RTFileOpen(,\"%s\",) -> %Rrc\n",
+ pszTest, pszXmlFile, rc);
+ pTest->hXmlFile = NIL_RTFILE;
+ }
+ }
+ }
+
+ /*
+ * What do we report in the XML stream/file.?
+ */
+ pTest->fXmlOmitTopTest = (fFlags & RTTEST_C_XML_OMIT_TOP_TEST)
+ || ( (fFlags & RTTEST_C_USE_ENV)
+ && RTEnvExistEx(RTENV_DEFAULT, "IPRT_TEST_OMIT_TOP_TEST"));
+
+ /*
+ * Tell the test driver that we're up to.
+ */
+ rtTestXmlStart(pTest, pszTest);
+
+ *phTest = pTest;
+ return VINF_SUCCESS;
+ }
+
+ /* bail out. */
+ RTCritSectDelete(&pTest->OutputLock);
+ }
+ RTCritSectDelete(&pTest->Lock);
+ }
+ pTest->u32Magic = 0;
+ RTStrFree((char *)pTest->pszTest);
+ RTMemFree(pTest);
+ return rc;
+}
+
+
+RTR3DECL(int) RTTestCreate(const char *pszTest, PRTTEST phTest)
+{
+ return RTTestCreateEx(pszTest, RTTEST_C_USE_ENV, RTTESTLVL_INVALID, -1 /*iNativeTestPipe*/, NULL /*pszXmlFile*/, phTest);
+}
+
+
+RTR3DECL(int) RTTestCreateChild(const char *pszTest, PRTTEST phTest)
+{
+ return RTTestCreateEx(pszTest, RTTEST_C_USE_ENV | RTTEST_C_NO_XML_REPORTING,
+ RTTESTLVL_INVALID, -1 /*iNativeTestPipe*/, NULL /*pszXmlFile*/, phTest);
+}
+
+
+RTR3DECL(RTEXITCODE) RTTestInitAndCreate(const char *pszTest, PRTTEST phTest)
+{
+ int rc = RTR3InitExeNoArguments(0);
+ if (RT_FAILURE(rc))
+ {
+ RTStrmPrintf(g_pStdErr, "%s: fatal error: RTR3InitExeNoArguments failed with rc=%Rrc\n", pszTest, rc);
+ return RTEXITCODE_INIT;
+ }
+
+ rc = RTTestCreate(pszTest, phTest);
+ if (RT_FAILURE(rc))
+ {
+ RTStrmPrintf(g_pStdErr, "%s: fatal error: RTTestCreate failed with rc=%Rrc\n", pszTest, rc);
+ return RTEXITCODE_INIT;
+ }
+ return RTEXITCODE_SUCCESS;
+}
+
+
+RTR3DECL(RTEXITCODE) RTTestInitExAndCreate(int cArgs, char ***ppapszArgs, uint32_t fRtInit, const char *pszTest, PRTTEST phTest)
+{
+ int rc;
+ if (cArgs <= 0 && ppapszArgs == NULL)
+ rc = RTR3InitExeNoArguments(fRtInit);
+ else
+ rc = RTR3InitExe(cArgs, ppapszArgs, fRtInit);
+ if (RT_FAILURE(rc))
+ {
+ RTStrmPrintf(g_pStdErr, "%s: fatal error: RTR3InitExe(,,%#x) failed with rc=%Rrc\n", pszTest, fRtInit, rc);
+ return RTEXITCODE_INIT;
+ }
+
+ rc = RTTestCreate(pszTest, phTest);
+ if (RT_FAILURE(rc))
+ {
+ RTStrmPrintf(g_pStdErr, "%s: fatal error: RTTestCreate failed with rc=%Rrc\n", pszTest, rc);
+ return RTEXITCODE_INIT;
+ }
+ return RTEXITCODE_SUCCESS;
+}
+
+
+RTR3DECL(int) RTTestDestroy(RTTEST hTest)
+{
+ /*
+ * Validate
+ */
+ if (hTest == NIL_RTTEST)
+ return VINF_SUCCESS;
+ RTTESTINT *pTest = hTest;
+ RTTEST_VALID_RETURN(pTest);
+
+ /*
+ * Make sure we end with a new line and have finished up the XML.
+ */
+ if (!pTest->fNewLine)
+ rtTestPrintf(pTest, "\n");
+ rtTestXmlEnd(pTest);
+
+ /*
+ * Clean up.
+ */
+ if ((RTTESTINT *)RTTlsGet(g_iTestTls) == pTest)
+ RTTlsSet(g_iTestTls, NULL);
+
+ ASMAtomicWriteU32(&pTest->u32Magic, ~RTTESTINT_MAGIC);
+ RTCritSectDelete(&pTest->Lock);
+ RTCritSectDelete(&pTest->OutputLock);
+
+ /* free guarded memory. */
+ PRTTESTGUARDEDMEM pMem = pTest->pGuardedMem;
+ pTest->pGuardedMem = NULL;
+ while (pMem)
+ {
+ PRTTESTGUARDEDMEM pFree = pMem;
+ pMem = pMem->pNext;
+ rtTestGuardedFreeOne(pFree);
+ }
+
+ RTStrFree((char *)pTest->pszSubTest);
+ pTest->pszSubTest = NULL;
+ RTStrFree((char *)pTest->pszTest);
+ pTest->pszTest = NULL;
+ RTStrFree(pTest->pszErrCtx);
+ pTest->pszErrCtx = NULL;
+ RTMemFree(pTest);
+ return VINF_SUCCESS;
+}
+
+
+RTR3DECL(int) RTTestSetDefault(RTTEST hNewDefaultTest, PRTTEST phOldTest)
+{
+ if (phOldTest)
+ *phOldTest = (RTTEST)RTTlsGet(g_iTestTls);
+ return RTTlsSet(g_iTestTls, hNewDefaultTest);
+}
+
+
+RTR3DECL(int) RTTestChangeName(RTTEST hTest, const char *pszName)
+{
+ PRTTESTINT pTest = hTest;
+ RTTEST_GET_VALID_RETURN(pTest);
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+ AssertReturn(*pszName, VERR_INVALID_PARAMETER);
+
+ size_t cchName = strlen(pszName);
+ AssertReturn(cchName < 128, VERR_INVALID_PARAMETER);
+ char *pszDupName = RTStrDup(pszName);
+ if (!pszDupName)
+ return VERR_NO_STR_MEMORY;
+
+ RTCritSectEnter(&pTest->Lock);
+ RTCritSectEnter(&pTest->OutputLock);
+
+ char *pszOldName = (char *)pTest->pszTest;
+ pTest->pszTest = pszDupName;
+ pTest->cchTest = cchName;
+
+ RTCritSectLeave(&pTest->OutputLock);
+ RTCritSectLeave(&pTest->Lock);
+
+ RTStrFree(pszOldName);
+ return VINF_SUCCESS;
+}
+
+
+RTR3DECL(int) RTTestGuardedAlloc(RTTEST hTest, size_t cb, uint32_t cbAlign, bool fHead, void **ppvUser)
+{
+ PRTTESTINT pTest = hTest;
+ RTTEST_GET_VALID_RETURN(pTest);
+ if (cbAlign == 0)
+ cbAlign = 1;
+ AssertReturn(cbAlign <= PAGE_SIZE, VERR_INVALID_PARAMETER);
+ AssertReturn(cbAlign == (UINT32_C(1) << (ASMBitFirstSetU32(cbAlign) - 1)), VERR_INVALID_PARAMETER);
+
+ /*
+ * Allocate the record and block and initialize them.
+ */
+ int rc = VERR_NO_MEMORY;
+ PRTTESTGUARDEDMEM pMem = (PRTTESTGUARDEDMEM)RTMemAlloc(sizeof(*pMem));
+ if (RT_LIKELY(pMem))
+ {
+ size_t const cbAligned = RT_ALIGN_Z(cb, PAGE_SIZE);
+ pMem->aGuards[0].cb = pMem->aGuards[1].cb = pTest->cbGuard;
+ pMem->cbAlloc = pMem->aGuards[0].cb + pMem->aGuards[1].cb + cbAligned;
+ pMem->pvAlloc = RTMemPageAlloc(pMem->cbAlloc);
+ if (pMem->pvAlloc)
+ {
+ pMem->aGuards[0].pv = pMem->pvAlloc;
+ pMem->pvUser = (uint8_t *)pMem->pvAlloc + pMem->aGuards[0].cb;
+ pMem->aGuards[1].pv = (uint8_t *)pMem->pvUser + cbAligned;
+ if (!fHead)
+ {
+ size_t off = cb & PAGE_OFFSET_MASK;
+ if (off)
+ {
+ off = PAGE_SIZE - RT_ALIGN_Z(off, cbAlign);
+ pMem->pvUser = (uint8_t *)pMem->pvUser + off;
+ }
+ }
+
+ /*
+ * Set up the guards and link the record.
+ */
+ ASMMemFill32(pMem->aGuards[0].pv, pMem->aGuards[0].cb, 0xdeadbeef);
+ ASMMemFill32(pMem->aGuards[1].pv, pMem->aGuards[1].cb, 0xdeadbeef);
+ rc = RTMemProtect(pMem->aGuards[0].pv, pMem->aGuards[0].cb, RTMEM_PROT_NONE);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTMemProtect(pMem->aGuards[1].pv, pMem->aGuards[1].cb, RTMEM_PROT_NONE);
+ if (RT_SUCCESS(rc))
+ {
+ *ppvUser = pMem->pvUser;
+
+ RTCritSectEnter(&pTest->Lock);
+ pMem->pNext = pTest->pGuardedMem;
+ pTest->pGuardedMem = pMem;
+ RTCritSectLeave(&pTest->Lock);
+
+ return VINF_SUCCESS;
+ }
+
+ RTMemProtect(pMem->aGuards[0].pv, pMem->aGuards[0].cb, RTMEM_PROT_WRITE | RTMEM_PROT_READ);
+ }
+
+ RTMemPageFree(pMem->pvAlloc, pMem->cbAlloc);
+ }
+ RTMemFree(pMem);
+ }
+ return rc;
+}
+
+
+RTR3DECL(void *) RTTestGuardedAllocTail(RTTEST hTest, size_t cb)
+{
+ void *pvUser;
+ int rc = RTTestGuardedAlloc(hTest, cb, 1 /* cbAlign */, false /* fHead */, &pvUser);
+ if (RT_SUCCESS(rc))
+ return pvUser;
+ return NULL;
+}
+
+
+RTR3DECL(void *) RTTestGuardedAllocHead(RTTEST hTest, size_t cb)
+{
+ void *pvUser;
+ int rc = RTTestGuardedAlloc(hTest, cb, 1 /* cbAlign */, true /* fHead */, &pvUser);
+ if (RT_SUCCESS(rc))
+ return pvUser;
+ return NULL;
+}
+
+
+/**
+ * Frees one block of guarded memory.
+ *
+ * The caller is responsible for unlinking it.
+ *
+ * @param pMem The memory record.
+ */
+static void rtTestGuardedFreeOne(PRTTESTGUARDEDMEM pMem)
+{
+ int rc;
+ rc = RTMemProtect(pMem->aGuards[0].pv, pMem->aGuards[0].cb, RTMEM_PROT_WRITE | RTMEM_PROT_READ); AssertRC(rc);
+ rc = RTMemProtect(pMem->aGuards[1].pv, pMem->aGuards[1].cb, RTMEM_PROT_WRITE | RTMEM_PROT_READ); AssertRC(rc);
+ RTMemPageFree(pMem->pvAlloc, pMem->cbAlloc);
+ RTMemFree(pMem);
+}
+
+
+RTR3DECL(int) RTTestGuardedFree(RTTEST hTest, void *pv)
+{
+ PRTTESTINT pTest = hTest;
+ RTTEST_GET_VALID_RETURN(pTest);
+ if (!pv)
+ return VINF_SUCCESS;
+
+ /*
+ * Find it.
+ */
+ int rc = VERR_INVALID_POINTER;
+ PRTTESTGUARDEDMEM pPrev = NULL;
+
+ RTCritSectEnter(&pTest->Lock);
+ for (PRTTESTGUARDEDMEM pMem = pTest->pGuardedMem; pMem; pMem = pMem->pNext)
+ {
+ if (pMem->pvUser == pv)
+ {
+ if (pPrev)
+ pPrev->pNext = pMem->pNext;
+ else
+ pTest->pGuardedMem = pMem->pNext;
+ rtTestGuardedFreeOne(pMem);
+ rc = VINF_SUCCESS;
+ break;
+ }
+ pPrev = pMem;
+ }
+ RTCritSectLeave(&pTest->Lock);
+
+ return rc;
+}
+
+
+/**
+ * Outputs the formatted XML.
+ *
+ * @param pTest The test instance.
+ * @param pszFormat The format string.
+ * @param va The format arguments.
+ */
+static void rtTestXmlOutputV(PRTTESTINT pTest, const char *pszFormat, va_list va)
+{
+ if (pTest->fXmlEnabled)
+ {
+ char *pszStr;
+ ssize_t cchStr = RTStrAPrintfV(&pszStr, pszFormat, va);
+ if (pszStr)
+ {
+ if (pTest->hXmlPipe != NIL_RTPIPE)
+ RTPipeWriteBlocking(pTest->hXmlPipe, pszStr, cchStr, NULL);
+ if (pTest->hXmlFile != NIL_RTFILE)
+ RTFileWrite(pTest->hXmlFile, pszStr, cchStr, NULL);
+ RTStrFree(pszStr);
+ }
+ }
+}
+
+
+/**
+ * Outputs the formatted XML.
+ *
+ * @param pTest The test instance.
+ * @param pszFormat The format string.
+ * @param ... The format arguments.
+ */
+static void rtTestXmlOutput(PRTTESTINT pTest, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ rtTestXmlOutputV(pTest, pszFormat, va);
+ va_end(va);
+}
+
+
+/**
+ * Starts the XML stream.
+ *
+ * @param pTest The test instance.
+ * @param pszTest The test name.
+ */
+static void rtTestXmlStart(PRTTESTINT pTest, const char *pszTest)
+{
+ pTest->cXmlElements = 0;
+ if (pTest->fXmlEnabled)
+ {
+ rtTestXmlOutput(pTest, "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
+ pTest->eXmlState = RTTESTINT::kXmlPos_ElementEnd;
+ pTest->fXmlTopTestDone = !(pTest->fFlags & RTTEST_C_XML_DELAY_TOP_TEST) || pTest->fXmlOmitTopTest;
+ if (pTest->fXmlTopTestDone && !pTest->fXmlOmitTopTest)
+ rtTestXmlElemStart(pTest, "Test", "name=%RMas", pszTest);
+ }
+}
+
+
+/**
+ * Emit an XML element that doesn't have any value and instead ends immediately.
+ *
+ * The caller must own the instance lock.
+ *
+ * @param pTest The test instance.
+ * @param pszTag The element tag.
+ * @param pszAttrFmt The element attributes as a format string. Use
+ * NULL if none.
+ * @param va Format string arguments.
+ */
+static void rtTestXmlElemV(PRTTESTINT pTest, const char *pszTag, const char *pszAttrFmt, va_list va)
+{
+ if (pTest->fXmlEnabled)
+ {
+ RTTIMESPEC TimeSpec;
+ RTTIME Time;
+ char szTS[80];
+ RTTimeToString(RTTimeExplode(&Time, RTTimeNow(&TimeSpec)), szTS, sizeof(szTS));
+
+ if (pTest->eXmlState != RTTESTINT::kXmlPos_ElementEnd)
+ rtTestXmlOutput(pTest, "\n");
+
+ if (!pszAttrFmt || !*pszAttrFmt)
+ rtTestXmlOutput(pTest, "%*s<%s timestamp=%RMas/>\n",
+ pTest->cXmlElements * 2, "", pszTag, szTS);
+ else
+ {
+ va_list va2;
+ va_copy(va2, va);
+ rtTestXmlOutput(pTest, "%*s<%s timestamp=%RMas %N/>\n",
+ pTest->cXmlElements * 2, "", pszTag, szTS, pszAttrFmt, &va2);
+ va_end(va2);
+ }
+ pTest->eXmlState = RTTESTINT::kXmlPos_ElementEnd;
+ }
+}
+
+
+/**
+ * Wrapper around rtTestXmlElemV.
+ */
+static void rtTestXmlElem(PRTTESTINT pTest, const char *pszTag, const char *pszAttrFmt, ...)
+{
+ va_list va;
+ va_start(va, pszAttrFmt);
+ rtTestXmlElemV(pTest, pszTag, pszAttrFmt, va);
+ va_end(va);
+}
+
+
+/**
+ * Starts a new XML element.
+ *
+ * The caller must own the instance lock.
+ *
+ * @param pTest The test instance.
+ * @param pszTag The element tag.
+ * @param pszAttrFmt The element attributes as a format string. Use
+ * NULL if none.
+ * @param va Format string arguments.
+ */
+static void rtTestXmlElemStartV(PRTTESTINT pTest, const char *pszTag, const char *pszAttrFmt, va_list va)
+{
+ /* Push it onto the stack. */
+ size_t i = pTest->cXmlElements;
+ AssertReturnVoid(i < RT_ELEMENTS(pTest->apszXmlElements));
+ pTest->apszXmlElements[i] = pszTag;
+ pTest->cXmlElements = i + 1;
+
+ if (pTest->fXmlEnabled)
+ {
+ RTTIMESPEC TimeSpec;
+ RTTIME Time;
+ char szTS[80];
+ RTTimeToString(RTTimeExplode(&Time, RTTimeNow(&TimeSpec)), szTS, sizeof(szTS));
+
+ if (pTest->eXmlState != RTTESTINT::kXmlPos_ElementEnd)
+ rtTestXmlOutput(pTest, "\n");
+
+ if (!pszAttrFmt || !*pszAttrFmt)
+ rtTestXmlOutput(pTest, "%*s<%s timestamp=%RMas>",
+ i * 2, "", pszTag, szTS);
+ else
+ {
+ va_list va2;
+ va_copy(va2, va);
+ rtTestXmlOutput(pTest, "%*s<%s timestamp=%RMas %N>",
+ i * 2, "", pszTag, szTS, pszAttrFmt, &va2);
+ va_end(va2);
+ }
+ pTest->eXmlState = RTTESTINT::kXmlPos_ValueStart;
+ }
+}
+
+
+/**
+ * Wrapper around rtTestXmlElemStartV.
+ */
+static void rtTestXmlElemStart(PRTTESTINT pTest, const char *pszTag, const char *pszAttrFmt, ...)
+{
+ va_list va;
+ va_start(va, pszAttrFmt);
+ rtTestXmlElemStartV(pTest, pszTag, pszAttrFmt, va);
+ va_end(va);
+}
+
+
+/**
+ * Ends the current element.
+ *
+ * The caller must own the instance lock.
+ *
+ * @param pTest The test instance.
+ * @param pszTag The tag we're ending (chiefly for sanity
+ * checking).
+ */
+static void rtTestXmlElemEnd(PRTTESTINT pTest, const char *pszTag)
+{
+ /* pop the element */
+ size_t i = pTest->cXmlElements;
+ AssertReturnVoid(i > 0);
+ i--;
+ AssertReturnVoid(!strcmp(pszTag, pTest->apszXmlElements[i]));
+ pTest->cXmlElements = i;
+
+ /* Do the closing. */
+ if (pTest->fXmlEnabled)
+ {
+ if (pTest->eXmlState == RTTESTINT::kXmlPos_ValueStart)
+ rtTestXmlOutput(pTest, "\n%*s</%s>\n", i * 2, "", pszTag);
+ else if (pTest->eXmlState == RTTESTINT::kXmlPos_ElementEnd)
+ rtTestXmlOutput(pTest, "%*s</%s>\n", i * 2, "", pszTag);
+ else
+ rtTestXmlOutput(pTest, "</%s>\n", pszTag);
+ pTest->eXmlState = RTTESTINT::kXmlPos_ElementEnd;
+ }
+}
+
+
+/**
+ * Ends the XML stream, closing all open elements.
+ *
+ * The caller must own the instance lock.
+ *
+ * @param pTest The test instance.
+ */
+static void rtTestXmlEnd(PRTTESTINT pTest)
+{
+ if (pTest->fXmlEnabled)
+ {
+ /*
+ * Close all the elements and add the final TestEnd one to get a
+ * final timestamp and some certainty that the XML is valid.
+ */
+ size_t i = pTest->cXmlElements;
+ AssertReturnVoid(i > 0 || pTest->fXmlOmitTopTest || !pTest->fXmlTopTestDone);
+ while (i-- > 1)
+ {
+ const char *pszTag = pTest->apszXmlElements[pTest->cXmlElements];
+ if (pTest->eXmlState == RTTESTINT::kXmlPos_ValueStart)
+ rtTestXmlOutput(pTest, "\n%*s</%s>\n", i * 2, "", pszTag);
+ else if (pTest->eXmlState == RTTESTINT::kXmlPos_ElementEnd)
+ rtTestXmlOutput(pTest, "%*s</%s>\n", i * 2, "", pszTag);
+ else
+ rtTestXmlOutput(pTest, "</%s>\n", pszTag);
+ pTest->eXmlState = RTTESTINT::kXmlPos_ElementEnd;
+ }
+
+ if (!pTest->fXmlOmitTopTest && pTest->fXmlTopTestDone)
+ {
+ rtTestXmlElem(pTest, "End", "SubTests=\"%u\" SubTestsFailed=\"%u\" errors=\"%u\"",
+ pTest->cSubTests, pTest->cSubTestsFailed, pTest->cErrors);
+ rtTestXmlOutput(pTest, "</Test>\n");
+ }
+
+ /*
+ * Close the XML outputs.
+ */
+ if (pTest->hXmlPipe != NIL_RTPIPE)
+ {
+ RTPipeClose(pTest->hXmlPipe);
+ pTest->hXmlPipe = NIL_RTPIPE;
+ }
+ if (pTest->hXmlFile != NIL_RTFILE)
+ {
+ RTFileClose(pTest->hXmlFile);
+ pTest->hXmlFile = NIL_RTFILE;
+ }
+ pTest->fXmlEnabled = false;
+ pTest->eXmlState = RTTESTINT::kXmlPos_ElementEnd;
+ }
+ pTest->cXmlElements = 0;
+}
+
+/**
+ * Output callback.
+ *
+ * @returns number of bytes written.
+ * @param pvArg User argument.
+ * @param pachChars Pointer to an array of utf-8 characters.
+ * @param cbChars Number of bytes in the character array pointed to by pachChars.
+ */
+static DECLCALLBACK(size_t) rtTestPrintfOutput(void *pvArg, const char *pachChars, size_t cbChars)
+{
+ size_t cch = 0;
+ PRTTESTINT pTest = (PRTTESTINT)pvArg;
+ if (cbChars)
+ {
+ do
+ {
+ /* insert prefix if at a newline. */
+ if (pTest->fNewLine)
+ {
+ RTStrmWrite(pTest->pOutStrm, pTest->pszTest, pTest->cchTest);
+ RTStrmWrite(pTest->pOutStrm, ": ", 2);
+ cch += 2 + pTest->cchTest;
+ }
+
+ /* look for newline and write the stuff. */
+ const char *pchEnd = (const char *)memchr(pachChars, '\n', cbChars);
+ if (!pchEnd)
+ {
+ pTest->fNewLine = false;
+ RTStrmWrite(pTest->pOutStrm, pachChars, cbChars);
+ cch += cbChars;
+ break;
+ }
+
+ pTest->fNewLine = true;
+ size_t const cchPart = pchEnd - pachChars + 1;
+ RTStrmWrite(pTest->pOutStrm, pachChars, cchPart);
+ cch += cchPart;
+ pachChars += cchPart;
+ cbChars -= cchPart;
+ } while (cbChars);
+ }
+ else
+ RTStrmFlush(pTest->pOutStrm);
+ return cch;
+}
+
+
+/**
+ * Internal output worker.
+ *
+ * Caller takes the lock.
+ *
+ * @returns Number of chars printed.
+ * @param pTest The test instance.
+ * @param pszFormat The message.
+ * @param va The arguments.
+ */
+static int rtTestPrintfV(PRTTESTINT pTest, const char *pszFormat, va_list va)
+{
+ return (int)RTStrFormatV(rtTestPrintfOutput, pTest, NULL, NULL, pszFormat, va);
+}
+
+
+/**
+ * Internal output worker.
+ *
+ * Caller takes the lock.
+ *
+ * @returns Number of chars printed.
+ * @param pTest The test instance.
+ * @param pszFormat The message.
+ * @param ... The arguments.
+ */
+static int rtTestPrintf(PRTTESTINT pTest, const char *pszFormat, ...)
+{
+ va_list va;
+
+ va_start(va, pszFormat);
+ int cch = rtTestPrintfV(pTest, pszFormat, va);
+ va_end(va);
+
+ return cch;
+}
+
+
+RTR3DECL(int) RTTestPrintfNlV(RTTEST hTest, RTTESTLVL enmLevel, const char *pszFormat, va_list va)
+{
+ PRTTESTINT pTest = hTest;
+ RTTEST_GET_VALID_RETURN_RC(pTest, -1);
+
+ RTCritSectEnter(&pTest->OutputLock);
+
+ int cch = 0;
+ if (enmLevel <= pTest->enmMaxLevel)
+ {
+ if (!pTest->fNewLine)
+ cch += rtTestPrintf(pTest, "\n");
+ cch += rtTestPrintfV(pTest, pszFormat, va);
+ }
+
+ RTCritSectLeave(&pTest->OutputLock);
+
+ return cch;
+}
+
+
+RTR3DECL(int) RTTestPrintfNl(RTTEST hTest, RTTESTLVL enmLevel, const char *pszFormat, ...)
+{
+ va_list va;
+
+ va_start(va, pszFormat);
+ int cch = RTTestPrintfNlV(hTest, enmLevel, pszFormat, va);
+ va_end(va);
+
+ return cch;
+}
+
+
+RTR3DECL(int) RTTestPrintfV(RTTEST hTest, RTTESTLVL enmLevel, const char *pszFormat, va_list va)
+{
+ PRTTESTINT pTest = hTest;
+ RTTEST_GET_VALID_RETURN_RC(pTest, -1);
+
+ RTCritSectEnter(&pTest->OutputLock);
+ int cch = 0;
+ if (enmLevel <= pTest->enmMaxLevel)
+ cch += rtTestPrintfV(pTest, pszFormat, va);
+ RTCritSectLeave(&pTest->OutputLock);
+
+ return cch;
+}
+
+
+RTR3DECL(int) RTTestPrintf(RTTEST hTest, RTTESTLVL enmLevel, const char *pszFormat, ...)
+{
+ va_list va;
+
+ va_start(va, pszFormat);
+ int cch = RTTestPrintfV(hTest, enmLevel, pszFormat, va);
+ va_end(va);
+
+ return cch;
+}
+
+
+RTR3DECL(int) RTTestBanner(RTTEST hTest)
+{
+ return RTTestPrintfNl(hTest, RTTESTLVL_ALWAYS, "TESTING...\n");
+}
+
+
+/**
+ * Prints the result of a sub-test if necessary.
+ *
+ * @returns Number of chars printed.
+ * @param pTest The test instance.
+ * @remarks Caller own the test Lock.
+ */
+static int rtTestSubTestReport(PRTTESTINT pTest)
+{
+ int cch = 0;
+ if ( !pTest->fSubTestReported
+ && pTest->pszSubTest)
+ {
+ pTest->fSubTestReported = true;
+ uint32_t cErrors = ASMAtomicUoReadU32(&pTest->cErrors) - pTest->cSubTestAtErrors;
+ if (!cErrors)
+ {
+ if (!pTest->fSubTestSkipped)
+ {
+ rtTestXmlElem(pTest, "Passed", NULL);
+ rtTestXmlElemEnd(pTest, "Test");
+ cch += RTTestPrintfNl(pTest, RTTESTLVL_SUB_TEST, "%-60s: PASSED\n", pTest->pszSubTest);
+ }
+ else
+ {
+ rtTestXmlElem(pTest, "Skipped", NULL);
+ rtTestXmlElemEnd(pTest, "Test");
+ cch += RTTestPrintfNl(pTest, RTTESTLVL_SUB_TEST, "%-60s: SKIPPED\n", pTest->pszSubTest);
+ }
+ }
+ else
+ {
+ pTest->cSubTestsFailed++;
+ rtTestXmlElem(pTest, "Failed", "errors=\"%u\"", cErrors);
+ rtTestXmlElemEnd(pTest, "Test");
+ cch += RTTestPrintfNl(pTest, RTTESTLVL_SUB_TEST, "%-60s: FAILED (%u errors)\n",
+ pTest->pszSubTest, cErrors);
+ }
+ }
+ return cch;
+}
+
+
+/**
+ * RTTestSub and RTTestSubDone worker that cleans up the current (if any)
+ * sub test.
+ *
+ * @returns Number of chars printed.
+ * @param pTest The test instance.
+ * @remarks Caller own the test Lock.
+ */
+static int rtTestSubCleanup(PRTTESTINT pTest)
+{
+ int cch = 0;
+ if (pTest->pszSubTest)
+ {
+ cch += rtTestSubTestReport(pTest);
+
+ RTStrFree((char *)pTest->pszSubTest);
+ pTest->pszSubTest = NULL;
+ pTest->fSubTestReported = true;
+ }
+ RTStrFree(pTest->pszErrCtx);
+ pTest->pszErrCtx = NULL;
+ return cch;
+}
+
+
+RTR3DECL(RTEXITCODE) RTTestSummaryAndDestroy(RTTEST hTest)
+{
+ PRTTESTINT pTest = hTest;
+ RTTEST_GET_VALID_RETURN_RC(pTest, RTEXITCODE_FAILURE);
+
+ RTCritSectEnter(&pTest->Lock);
+ rtTestSubTestReport(pTest);
+ RTCritSectLeave(&pTest->Lock);
+
+ RTEXITCODE enmExitCode;
+ if (!pTest->cErrors)
+ {
+ RTTestPrintfNl(hTest, RTTESTLVL_ALWAYS, "SUCCESS\n");
+ enmExitCode = RTEXITCODE_SUCCESS;
+ }
+ else
+ {
+ RTTestPrintfNl(hTest, RTTESTLVL_ALWAYS, "FAILURE - %u errors\n", pTest->cErrors);
+ enmExitCode = RTEXITCODE_FAILURE;
+ }
+
+ RTTestDestroy(pTest);
+ return enmExitCode;
+}
+
+
+RTR3DECL(RTEXITCODE) RTTestSkipAndDestroyV(RTTEST hTest, const char *pszReasonFmt, va_list va)
+{
+ PRTTESTINT pTest = hTest;
+ RTTEST_GET_VALID_RETURN_RC(pTest, RTEXITCODE_SKIPPED);
+
+ RTCritSectEnter(&pTest->Lock);
+ rtTestSubTestReport(pTest);
+ RTCritSectLeave(&pTest->Lock);
+
+ RTEXITCODE enmExitCode;
+ if (!pTest->cErrors)
+ {
+ if (pszReasonFmt)
+ RTTestPrintfNlV(hTest, RTTESTLVL_FAILURE, pszReasonFmt, va);
+ RTTestPrintfNl(hTest, RTTESTLVL_ALWAYS, "SKIPPED\n");
+ enmExitCode = RTEXITCODE_SKIPPED;
+ }
+ else
+ {
+ RTTestPrintfNl(hTest, RTTESTLVL_ALWAYS, "FAILURE - %u errors\n", pTest->cErrors);
+ enmExitCode = RTEXITCODE_FAILURE;
+ }
+
+ RTTestDestroy(pTest);
+ return enmExitCode;
+}
+
+
+RTR3DECL(RTEXITCODE) RTTestSkipAndDestroy(RTTEST hTest, const char *pszReasonFmt, ...)
+{
+ va_list va;
+ va_start(va, pszReasonFmt);
+ RTEXITCODE enmExitCode = RTTestSkipAndDestroyV(hTest, pszReasonFmt, va);
+ va_end(va);
+ return enmExitCode;
+}
+
+
+RTR3DECL(int) RTTestSub(RTTEST hTest, const char *pszSubTest)
+{
+ PRTTESTINT pTest = hTest;
+ RTTEST_GET_VALID_RETURN_RC(pTest, -1);
+
+ RTCritSectEnter(&pTest->Lock);
+
+ /* Cleanup, reporting if necessary previous sub test. */
+ rtTestSubCleanup(pTest);
+
+ /* Start new sub test. */
+ pTest->cSubTests++;
+ pTest->cSubTestAtErrors = ASMAtomicUoReadU32(&pTest->cErrors);
+ pTest->pszSubTest = RTStrDup(pszSubTest);
+ pTest->cchSubTest = strlen(pszSubTest);
+ AssertMsg(pTest->cchSubTest < 64 /* See g_kcchMaxTestResultName in testmanager/config.py. */,
+ ("cchSubTest=%u: '%s'\n", pTest->cchSubTest, pTest->pszSubTest));
+ pTest->fSubTestSkipped = false;
+ pTest->fSubTestReported = false;
+
+ int cch = 0;
+ if (pTest->enmMaxLevel >= RTTESTLVL_DEBUG)
+ cch = RTTestPrintfNl(hTest, RTTESTLVL_DEBUG, "debug: Starting sub-test '%s'\n", pszSubTest);
+
+ if (!pTest->fXmlTopTestDone)
+ {
+ pTest->fXmlTopTestDone = true;
+ rtTestXmlElemStart(pTest, "Test", "name=%RMas", pTest->pszTest);
+ }
+
+ rtTestXmlElemStart(pTest, "Test", "name=%RMas", pszSubTest);
+
+ RTCritSectLeave(&pTest->Lock);
+
+ return cch;
+}
+
+
+RTR3DECL(int) RTTestSubF(RTTEST hTest, const char *pszSubTestFmt, ...)
+{
+ va_list va;
+ va_start(va, pszSubTestFmt);
+ int cch = RTTestSubV(hTest, pszSubTestFmt, va);
+ va_end(va);
+ return cch;
+}
+
+
+RTR3DECL(int) RTTestSubV(RTTEST hTest, const char *pszSubTestFmt, va_list va)
+{
+ char *pszSubTest;
+ RTStrAPrintfV(&pszSubTest, pszSubTestFmt, va);
+ if (pszSubTest)
+ {
+ int cch = RTTestSub(hTest, pszSubTest);
+ RTStrFree(pszSubTest);
+ return cch;
+ }
+ return 0;
+}
+
+
+RTR3DECL(int) RTTestSubDone(RTTEST hTest)
+{
+ PRTTESTINT pTest = hTest;
+ RTTEST_GET_VALID_RETURN_RC(pTest, VERR_INVALID_HANDLE);
+
+ RTCritSectEnter(&pTest->Lock);
+ int cch = rtTestSubCleanup(pTest);
+ RTCritSectLeave(&pTest->Lock);
+
+ return cch;
+}
+
+
+RTR3DECL(int) RTTestPassedV(RTTEST hTest, const char *pszFormat, va_list va)
+{
+ PRTTESTINT pTest = hTest;
+ AssertPtr(pszFormat);
+ RTTEST_GET_VALID_RETURN_RC(pTest, VERR_INVALID_HANDLE);
+
+ int cch = 0;
+ if (pTest->enmMaxLevel >= RTTESTLVL_INFO)
+ {
+ va_list va2;
+ va_copy(va2, va);
+
+ RTCritSectEnter(&pTest->OutputLock);
+ cch += rtTestPrintf(pTest, "%N\n", pszFormat, &va2);
+ RTCritSectLeave(&pTest->OutputLock);
+
+ va_end(va2);
+ }
+
+ return cch;
+}
+
+
+RTR3DECL(int) RTTestPassed(RTTEST hTest, const char *pszFormat, ...)
+{
+ va_list va;
+
+ va_start(va, pszFormat);
+ int cch = RTTestPassedV(hTest, pszFormat, va);
+ va_end(va);
+
+ return cch;
+}
+
+
+RTR3DECL(int) RTTestSkippedV(RTTEST hTest, const char *pszFormat, va_list va)
+{
+ PRTTESTINT pTest = hTest;
+ AssertPtrNull(pszFormat);
+ RTTEST_GET_VALID_RETURN_RC(pTest, VERR_INVALID_HANDLE);
+
+ pTest->fSubTestSkipped = true;
+
+ int cch = 0;
+ if (pszFormat && *pszFormat && pTest->enmMaxLevel >= RTTESTLVL_INFO)
+ {
+ va_list va2;
+ va_copy(va2, va);
+
+ RTCritSectEnter(&pTest->OutputLock);
+ cch += rtTestPrintf(pTest, "%N\n", pszFormat, &va2);
+ RTCritSectLeave(&pTest->OutputLock);
+
+ va_end(va2);
+ }
+
+ return cch;
+}
+
+
+RTR3DECL(int) RTTestSkipped(RTTEST hTest, const char *pszFormat, ...)
+{
+ va_list va;
+
+ va_start(va, pszFormat);
+ int cch = RTTestSkippedV(hTest, pszFormat, va);
+ va_end(va);
+
+ return cch;
+}
+
+
+
+/**
+ * Gets the unit name.
+ *
+ * @returns Unit name.
+ * @param enmUnit The unit.
+ */
+static const char *rtTestUnitName(RTTESTUNIT enmUnit)
+{
+ switch (enmUnit)
+ {
+ case RTTESTUNIT_PCT: return "%";
+ case RTTESTUNIT_BYTES: return "bytes";
+ case RTTESTUNIT_BYTES_PER_SEC: return "bytes/s";
+ case RTTESTUNIT_KILOBYTES: return "KB";
+ case RTTESTUNIT_KILOBYTES_PER_SEC: return "KB/s";
+ case RTTESTUNIT_MEGABYTES: return "MB";
+ case RTTESTUNIT_MEGABYTES_PER_SEC: return "MB/s";
+ case RTTESTUNIT_PACKETS: return "packets";
+ case RTTESTUNIT_PACKETS_PER_SEC: return "packets/s";
+ case RTTESTUNIT_FRAMES: return "frames";
+ case RTTESTUNIT_FRAMES_PER_SEC: return "frames/s";
+ case RTTESTUNIT_OCCURRENCES: return "occurrences";
+ case RTTESTUNIT_OCCURRENCES_PER_SEC: return "occurrences/s";
+ case RTTESTUNIT_ROUND_TRIP: return "roundtrips";
+ case RTTESTUNIT_CALLS: return "calls";
+ case RTTESTUNIT_CALLS_PER_SEC: return "calls/s";
+ case RTTESTUNIT_SECS: return "s";
+ case RTTESTUNIT_MS: return "ms";
+ case RTTESTUNIT_NS: return "ns";
+ case RTTESTUNIT_NS_PER_CALL: return "ns/call";
+ case RTTESTUNIT_NS_PER_FRAME: return "ns/frame";
+ case RTTESTUNIT_NS_PER_OCCURRENCE: return "ns/occurrence";
+ case RTTESTUNIT_NS_PER_PACKET: return "ns/packet";
+ case RTTESTUNIT_NS_PER_ROUND_TRIP: return "ns/roundtrip";
+ case RTTESTUNIT_INSTRS: return "ins";
+ case RTTESTUNIT_INSTRS_PER_SEC: return "ins/sec";
+ case RTTESTUNIT_NONE: return "";
+ case RTTESTUNIT_PP1K: return "pp1k";
+ case RTTESTUNIT_PP10K: return "pp10k";
+ case RTTESTUNIT_PPM: return "ppm";
+ case RTTESTUNIT_PPB: return "ppb";
+ case RTTESTUNIT_TICKS: return "ticks";
+ case RTTESTUNIT_TICKS_PER_CALL: return "ticks/call";
+ case RTTESTUNIT_TICKS_PER_OCCURENCE: return "ticks/occ";
+ case RTTESTUNIT_PAGES: return "pages";
+ case RTTESTUNIT_PAGES_PER_SEC: return "pages/s";
+ case RTTESTUNIT_TICKS_PER_PAGE: return "ticks/page";
+ case RTTESTUNIT_NS_PER_PAGE: return "ns/page";
+ case RTTESTUNIT_PS: return "ps";
+ case RTTESTUNIT_PS_PER_CALL: return "ps/call";
+ case RTTESTUNIT_PS_PER_FRAME: return "ps/frame";
+ case RTTESTUNIT_PS_PER_OCCURRENCE: return "ps/occurrence";
+ case RTTESTUNIT_PS_PER_PACKET: return "ps/packet";
+ case RTTESTUNIT_PS_PER_ROUND_TRIP: return "ps/roundtrip";
+ case RTTESTUNIT_PS_PER_PAGE: return "ps/page";
+
+ /* No default so gcc helps us keep this up to date. */
+ case RTTESTUNIT_INVALID:
+ case RTTESTUNIT_END:
+ break;
+ }
+ AssertMsgFailed(("%d\n", enmUnit));
+ return "unknown";
+}
+
+
+RTR3DECL(int) RTTestValue(RTTEST hTest, const char *pszName, uint64_t u64Value, RTTESTUNIT enmUnit)
+{
+ PRTTESTINT pTest = hTest;
+ RTTEST_GET_VALID_RETURN(pTest);
+
+ Assert(strlen(pszName) < 56 /* See g_kcchMaxTestValueName in testmanager/config.py. */);
+
+ const char *pszUnit = rtTestUnitName(enmUnit);
+
+ RTCritSectEnter(&pTest->Lock);
+ rtTestXmlElem(pTest, "Value", "name=%RMas unit=%RMas value=\"%llu\"", pszName, pszUnit, u64Value);
+ RTCritSectLeave(&pTest->Lock);
+
+ RTCritSectEnter(&pTest->OutputLock);
+ rtTestPrintf(pTest, " %-58s: %'16llu %s\n", pszName, u64Value, pszUnit);
+ RTCritSectLeave(&pTest->OutputLock);
+
+ return VINF_SUCCESS;
+}
+
+
+RTR3DECL(int) RTTestValueF(RTTEST hTest, uint64_t u64Value, RTTESTUNIT enmUnit, const char *pszNameFmt, ...)
+{
+ va_list va;
+ va_start(va, pszNameFmt);
+ int rc = RTTestValueV(hTest, u64Value, enmUnit, pszNameFmt, va);
+ va_end(va);
+ return rc;
+}
+
+
+RTR3DECL(int) RTTestValueV(RTTEST hTest, uint64_t u64Value, RTTESTUNIT enmUnit, const char *pszNameFmt, va_list va)
+{
+ char *pszName;
+ RTStrAPrintfV(&pszName, pszNameFmt, va);
+ if (!pszName)
+ return VERR_NO_MEMORY;
+ int rc = RTTestValue(hTest, pszName, u64Value, enmUnit);
+ RTStrFree(pszName);
+ return rc;
+}
+
+
+RTR3DECL(int) RTTestErrorInc(RTTEST hTest)
+{
+ PRTTESTINT pTest = hTest;
+ RTTEST_GET_VALID_RETURN(pTest);
+
+ ASMAtomicIncU32(&pTest->cErrors);
+
+ return VINF_SUCCESS;
+}
+
+
+RTR3DECL(uint32_t) RTTestErrorCount(RTTEST hTest)
+{
+ PRTTESTINT pTest = hTest;
+ RTTEST_GET_VALID_RETURN_RC(pTest, UINT32_MAX);
+
+ return ASMAtomicReadU32(&pTest->cErrors);
+}
+
+
+RTR3DECL(uint32_t) RTTestSubErrorCount(RTTEST hTest)
+{
+ PRTTESTINT pTest = hTest;
+ RTTEST_GET_VALID_RETURN_RC(pTest, UINT32_MAX);
+
+ return ASMAtomicReadU32(&pTest->cErrors) - pTest->cSubTestAtErrors;
+}
+
+
+RTR3DECL(int) RTTestFailedV(RTTEST hTest, const char *pszFormat, va_list va)
+{
+ PRTTESTINT pTest = hTest;
+ RTTEST_GET_VALID_RETURN_RC(pTest, VERR_INVALID_HANDLE);
+
+ RTTestErrorInc(pTest);
+
+ int cch = 0;
+ if (pTest->enmMaxLevel >= RTTESTLVL_FAILURE)
+ {
+ va_list va2;
+ va_copy(va2, va);
+
+ const char *pszEnd = strchr(pszFormat, '\0');
+ bool fHasNewLine = pszFormat != pszEnd
+ && pszEnd[-1] == '\n';
+
+ RTCritSectEnter(&pTest->OutputLock);
+ cch += rtTestPrintf(pTest, fHasNewLine ? "%N" : "%N\n", pszFormat, &va2);
+ if (pTest->pszErrCtx)
+ {
+ cch += rtTestPrintf(pTest, "context: %s\n", pTest->pszErrCtx);
+ RTStrFree(pTest->pszErrCtx);
+ pTest->pszErrCtx = NULL;
+ }
+ RTCritSectLeave(&pTest->OutputLock);
+
+ va_end(va2);
+ }
+
+ return cch;
+}
+
+
+RTR3DECL(int) RTTestFailed(RTTEST hTest, const char *pszFormat, ...)
+{
+ va_list va;
+
+ va_start(va, pszFormat);
+ int cch = RTTestFailedV(hTest, pszFormat, va);
+ va_end(va);
+
+ return cch;
+}
+
+
+RTR3DECL(int) RTTestFailureDetailsV(RTTEST hTest, const char *pszFormat, va_list va)
+{
+ return RTTestPrintfV(hTest, RTTESTLVL_FAILURE, pszFormat, va);
+}
+
+
+RTR3DECL(int) RTTestFailureDetails(RTTEST hTest, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ int cch = RTTestFailureDetailsV(hTest, pszFormat, va);
+ va_end(va);
+ return cch;
+}
+
+
+RTR3DECL(int) RTTestErrContextV(RTTEST hTest, const char *pszFormat, va_list va)
+{
+ PRTTESTINT pTest = hTest;
+ RTTEST_GET_VALID_RETURN(pTest);
+
+ RTStrFree(pTest->pszErrCtx);
+ pTest->pszErrCtx = NULL;
+
+ if (pszFormat && *pszFormat)
+ {
+ pTest->pszErrCtx = RTStrAPrintf2V(pszFormat, va);
+ AssertReturn(pTest->pszErrCtx, VERR_NO_STR_MEMORY);
+ RTStrStripR(pTest->pszErrCtx);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+RTR3DECL(int) RTTestErrContext(RTTEST hTest, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ int rc = RTTestErrContextV(hTest, pszFormat, va);
+ va_end(va);
+ return rc;
+}
+
+
+RTR3DECL(int) RTTestDisableAssertions(RTTEST hTest)
+{
+ PRTTESTINT pTest = hTest;
+ RTTEST_GET_VALID_RETURN(pTest);
+
+ uint32_t cTimes = ASMAtomicIncU32(&pTest->cAssertionsDisabledAndQuieted);
+ if (cTimes >= 2 && cTimes <= 8)
+ return VINF_SUCCESS;
+ if (cTimes > 8)
+ {
+ RTAssertSetMayPanic(pTest->fAssertSavedMayPanic);
+ RTAssertSetQuiet(pTest->fAssertSavedQuiet);
+ Assert(cTimes <= 8);
+ }
+ pTest->fAssertSavedMayPanic = RTAssertSetMayPanic(false);
+ pTest->fAssertSavedQuiet = RTAssertSetQuiet(true);
+ return VINF_SUCCESS;
+}
+
+
+RTR3DECL(int) RTTestRestoreAssertions(RTTEST hTest)
+{
+ PRTTESTINT pTest = hTest;
+ RTTEST_GET_VALID_RETURN(pTest);
+
+ uint32_t cTimes = ASMAtomicDecU32(&pTest->cAssertionsDisabledAndQuieted);
+ if (cTimes == 0)
+ {
+ RTAssertSetMayPanic(pTest->fAssertSavedMayPanic);
+ RTAssertSetQuiet(pTest->fAssertSavedQuiet);
+ }
+ else
+ AssertStmt(cTimes < UINT32_MAX / 2, ASMAtomicIncU32(&pTest->cAssertionsDisabledAndQuieted));
+ return VINF_SUCCESS;
+}
+