From f215e02bf85f68d3a6106c2a1f4f7f063f819064 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:17:27 +0200 Subject: Adding upstream version 7.0.14-dfsg. Signed-off-by: Daniel Baumann --- .../ValidationKit/utils/audio/vkatCmdSelfTest.cpp | 481 +++++++++++++++++++++ 1 file changed, 481 insertions(+) create mode 100644 src/VBox/ValidationKit/utils/audio/vkatCmdSelfTest.cpp (limited to 'src/VBox/ValidationKit/utils/audio/vkatCmdSelfTest.cpp') diff --git a/src/VBox/ValidationKit/utils/audio/vkatCmdSelfTest.cpp b/src/VBox/ValidationKit/utils/audio/vkatCmdSelfTest.cpp new file mode 100644 index 00000000..3259398d --- /dev/null +++ b/src/VBox/ValidationKit/utils/audio/vkatCmdSelfTest.cpp @@ -0,0 +1,481 @@ +/* $Id: vkatCmdSelfTest.cpp $ */ +/** @file + * Validation Kit Audio Test (VKAT) - Self test. + * + * Self-test which does a complete audio testing framework run without the need + * of a VM or other infrastructure, i.e. all required parts are running locally + * on the same machine. + * + * This self-test does the following: + * - 1. Creates a separate thread for the guest side VKAT and connects to the ATS instance on + * the host side at port 6052 (ATS_TCP_DEF_BIND_PORT_HOST). + * - 2. Uses the Validation Kit audio backend, which in turn creates an ATS instance + * listening at port 6062 (ATS_TCP_DEF_BIND_PORT_VALKIT). + * - 3. Uses the host test environment which creates an ATS instance + * listening at port 6052 (ATS_TCP_DEF_BIND_PORT_HOST). + * - 4. Executes a complete test run locally (e.g. without any guest (VM) involved). + */ + +/* + * Copyright (C) 2021-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 . + * + * 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 +#include +#include +#include +#include +#include + +#include "Audio/AudioHlp.h" +#include "Audio/AudioTest.h" +#include "Audio/AudioTestService.h" +#include "Audio/AudioTestServiceClient.h" + +#include "vkatInternal.h" + + +/********************************************************************************************************************************* +* Internal structures * +*********************************************************************************************************************************/ + +/** + * Structure for keeping a VKAT self test context. + */ +typedef struct SELFTESTCTX +{ + /** Common tag for guest and host side. */ + char szTag[AUDIOTEST_TAG_MAX]; + /** The driver stack in use. */ + AUDIOTESTDRVSTACK DrvStack; + /** Audio driver to use. + * Defaults to the platform's default driver. */ + PCPDMDRVREG pDrvReg; + struct + { + AUDIOTESTENV TstEnv; + /** Where to bind the address of the guest ATS instance to. + * Defaults to localhost (127.0.0.1) if empty. */ + char szAtsAddr[64]; + /** Port of the guest ATS instance. + * Defaults to ATS_ALT_PORT if not set. */ + uint32_t uAtsPort; + } Guest; + struct + { + AUDIOTESTENV TstEnv; + /** Address of the guest ATS instance. + * Defaults to localhost (127.0.0.1) if not set. */ + char szGuestAtsAddr[64]; + /** Port of the guest ATS instance. + * Defaults to ATS_DEFAULT_PORT if not set. */ + uint32_t uGuestAtsPort; + /** Address of the Validation Kit audio driver ATS instance. + * Defaults to localhost (127.0.0.1) if not set. */ + char szValKitAtsAddr[64]; + /** Port of the Validation Kit audio driver ATS instance. + * Defaults to ATS_ALT_PORT if not set. */ + uint32_t uValKitAtsPort; + } Host; +} SELFTESTCTX; +/** Pointer to a VKAT self test context. */ +typedef SELFTESTCTX *PSELFTESTCTX; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ + +/** The global self-text context. */ +static SELFTESTCTX g_Ctx; + + +/********************************************************************************************************************************* +* Driver stack self-test implementation * +*********************************************************************************************************************************/ + +/** + * Performs a (quick) audio driver stack self test. + * + * Local only, no guest/host communication involved. + * + * @returns VBox status code. + */ +int AudioTestDriverStackPerformSelftest(void) +{ + PCPDMDRVREG pDrvReg = AudioTestGetDefaultBackend(); + + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Testing driver stack started\n"); + + AUDIOTESTDRVSTACK DrvStack; + int rc = audioTestDriverStackProbe(&DrvStack, pDrvReg, + true /* fEnabledIn */, true /* fEnabledOut */, false /* fWithDrvAudio */); + RTTEST_CHECK_RC_OK_RET(g_hTest, rc, rc); + + AUDIOTESTIOOPTS IoOpts; + audioTestIoOptsInitDefaults(&IoOpts); + + PPDMAUDIOSTREAM pStream; + PDMAUDIOSTREAMCFG CfgAcq; + rc = audioTestDriverStackStreamCreateOutput(&DrvStack, &IoOpts.Props, + IoOpts.cMsBufferSize, IoOpts.cMsPreBuffer, IoOpts.cMsSchedulingHint, + &pStream, &CfgAcq); + AssertRCReturn(rc, rc); + + rc = audioTestDriverStackStreamEnable(&DrvStack, pStream); + RTTEST_CHECK_RC_OK_RET(g_hTest, rc, rc); + + RTTEST_CHECK_RET(g_hTest, audioTestDriverStackStreamIsOkay(&DrvStack, pStream), VERR_AUDIO_STREAM_NOT_READY); + + uint8_t abBuf[_4K]; + memset(abBuf, 0x42, sizeof(abBuf)); + + uint32_t cbWritten; + rc = audioTestDriverStackStreamPlay(&DrvStack, pStream, abBuf, sizeof(abBuf), &cbWritten); + RTTEST_CHECK_RC_OK_RET(g_hTest, rc, rc); + RTTEST_CHECK_RET(g_hTest, cbWritten == sizeof(abBuf), VERR_AUDIO_STREAM_NOT_READY); + + audioTestDriverStackStreamDrain(&DrvStack, pStream, true /* fSync */); + audioTestDriverStackStreamDestroy(&DrvStack, pStream); + + audioTestDriverStackDelete(&DrvStack); + + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Testing driver stack ended with %Rrc\n", rc); + return rc; +} + + +/********************************************************************************************************************************* +* Self-test implementation * +*********************************************************************************************************************************/ + +/** + * Thread callback for mocking the guest (VM) side of things. + * + * @returns VBox status code. + * @param hThread Thread handle. + * @param pvUser Pointer to user-supplied data. + */ +static DECLCALLBACK(int) audioTestSelftestGuestAtsThread(RTTHREAD hThread, void *pvUser) +{ + RT_NOREF(hThread); + PSELFTESTCTX pCtx = (PSELFTESTCTX)pvUser; + + PAUDIOTESTENV pTstEnvGst = &pCtx->Guest.TstEnv; + + audioTestEnvInit(pTstEnvGst); + + /* Flag the environment for self test mode. */ + pTstEnvGst->fSelftest = true; + + /* Tweak the address the guest ATS is trying to connect to the host if anything else is specified. + * Note: The host also runs on the same host (this self-test is completely self-contained and does not need a VM). */ + if (!pTstEnvGst->TcpOpts.szConnectAddr[0]) + RTStrCopy(pTstEnvGst->TcpOpts.szConnectAddr, sizeof(pTstEnvGst->TcpOpts.szConnectAddr), "127.0.0.1"); + + /* Generate tag for guest side. */ + int rc = RTStrCopy(pTstEnvGst->szTag, sizeof(pTstEnvGst->szTag), pCtx->szTag); + RTTEST_CHECK_RC_OK_RET(g_hTest, rc, rc); + + rc = AudioTestPathCreateTemp(pTstEnvGst->szPathTemp, sizeof(pTstEnvGst->szPathTemp), "selftest-guest"); + RTTEST_CHECK_RC_OK_RET(g_hTest, rc, rc); + + rc = AudioTestPathCreateTemp(pTstEnvGst->szPathOut, sizeof(pTstEnvGst->szPathOut), "selftest-out"); + RTTEST_CHECK_RC_OK_RET(g_hTest, rc, rc); + + pTstEnvGst->enmMode = AUDIOTESTMODE_GUEST; + + rc = audioTestEnvCreate(pTstEnvGst, &pCtx->DrvStack); + if (RT_SUCCESS(rc)) + { + RTThreadUserSignal(hThread); + + rc = audioTestWorker(pTstEnvGst); + RTTEST_CHECK_RC_OK_RET(g_hTest, rc, rc); + + audioTestEnvDestroy(pTstEnvGst); + } + + return rc; +} + +/** + * Main function for performing the self test. + * + * @returns RTEXITCODE + * @param pCtx Self test context to use. + */ +RTEXITCODE audioTestDoSelftest(PSELFTESTCTX pCtx) +{ + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Running self test ...\n"); + + /* Generate a common tag for guest and host side. */ + int rc = AudioTestGenTag(pCtx->szTag, sizeof(pCtx->szTag)); + RTTEST_CHECK_RC_OK_RET(g_hTest, rc, RTEXITCODE_FAILURE); + + PAUDIOTESTENV pTstEnvHst = &pCtx->Host.TstEnv; + + audioTestEnvInit(pTstEnvHst); + + /* Flag the environment for self test mode. */ + pTstEnvHst->fSelftest = true; + + /* One test iteration with a 5s maximum test tone is enough for a (quick) self test. */ + pTstEnvHst->cIterations = 1; + pTstEnvHst->ToneParms.msDuration = RTRandU32Ex(500, RT_MS_5SEC); + + /* Generate tag for host side. */ + rc = RTStrCopy(pTstEnvHst->szTag, sizeof(pTstEnvHst->szTag), pCtx->szTag); + RTTEST_CHECK_RC_OK_RET(g_hTest, rc, RTEXITCODE_FAILURE); + + rc = AudioTestPathCreateTemp(pTstEnvHst->szPathTemp, sizeof(pTstEnvHst->szPathTemp), "selftest-tmp"); + RTTEST_CHECK_RC_OK_RET(g_hTest, rc, RTEXITCODE_FAILURE); + + rc = AudioTestPathCreateTemp(pTstEnvHst->szPathOut, sizeof(pTstEnvHst->szPathOut), "selftest-out"); + RTTEST_CHECK_RC_OK_RET(g_hTest, rc, RTEXITCODE_FAILURE); + + /* + * Step 1. + */ + RTTHREAD hThreadGstAts = NIL_RTTHREAD; + + bool const fStartGuestAts = RTStrNLen(pCtx->Host.szGuestAtsAddr, sizeof(pCtx->Host.szGuestAtsAddr)) == 0; + if (fStartGuestAts) + { + /* Step 1b. */ + rc = RTThreadCreate(&hThreadGstAts, audioTestSelftestGuestAtsThread, pCtx, 0, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, + "VKATGstAts"); + if (RT_SUCCESS(rc)) + rc = RTThreadUserWait(hThreadGstAts, RT_MS_30SEC); + } + + RTThreadSleep(2000); /* Fudge: Wait until guest ATS is up. 2 seconds should be enough (tm). */ + + if (RT_SUCCESS(rc)) + { + /* + * Steps 2 + 3. + */ + pTstEnvHst->enmMode = AUDIOTESTMODE_HOST; + + rc = audioTestEnvCreate(pTstEnvHst, &pCtx->DrvStack); + if (RT_SUCCESS(rc)) + { + /* + * Step 4. + */ + rc = audioTestWorker(pTstEnvHst); + RTTEST_CHECK_RC_OK(g_hTest, rc); + + audioTestEnvDestroy(pTstEnvHst); + } + } + + /* + * Shutting down. + */ + RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Shutting down self test\n"); + + /* If we started the guest ATS ourselves, wait for it to terminate properly. */ + if (fStartGuestAts) + { + int rcThread; + int rc2 = RTThreadWait(hThreadGstAts, RT_MS_30SEC, &rcThread); + if (RT_SUCCESS(rc2)) + rc2 = rcThread; + if (RT_FAILURE(rc2)) + RTTestFailed(g_hTest, "Shutting down guest ATS failed with %Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "Self test failed with %Rrc\n", rc); + + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +/********************************************************************************************************************************* +* Command: selftest * +*********************************************************************************************************************************/ + +/** + * Command line parameters for self-test mode. + */ +static const RTGETOPTDEF s_aCmdSelftestOptions[] = +{ + { "--exclude-all", 'a', RTGETOPT_REQ_NOTHING }, + { "--backend", 'b', RTGETOPT_REQ_STRING }, + { "--with-drv-audio", 'd', RTGETOPT_REQ_NOTHING }, + { "--with-mixer", 'm', RTGETOPT_REQ_NOTHING }, + { "--exclude", 'e', RTGETOPT_REQ_UINT32 }, + { "--include", 'i', RTGETOPT_REQ_UINT32 } +}; + +/** the 'selftest' command option help. */ +static DECLCALLBACK(const char *) audioTestCmdSelftestHelp(PCRTGETOPTDEF pOpt) +{ + switch (pOpt->iShort) + { + case 'a': return "Exclude all tests from the list (useful to enable single tests later with --include)"; + case 'b': return "The audio backend to use"; + case 'd': return "Go via DrvAudio instead of directly interfacing with the backend"; + case 'e': return "Exclude the given test id from the list"; + case 'i': return "Include the given test id in the list"; + case 'm': return "Use the internal mixing engine explicitly"; + default: return NULL; + } +} + +/** + * The 'selftest' command handler. + * + * @returns Program exit code. + * @param pGetState RTGetOpt state. + */ +DECLCALLBACK(RTEXITCODE) audioTestCmdSelftestHandler(PRTGETOPTSTATE pGetState) +{ + RT_ZERO(g_Ctx); + + audioTestEnvInit(&g_Ctx.Guest.TstEnv); + audioTestEnvInit(&g_Ctx.Host.TstEnv); + + AUDIOTESTIOOPTS IoOpts; + audioTestIoOptsInitDefaults(&IoOpts); + + /* Argument processing loop: */ + int rc; + RTGETOPTUNION ValueUnion; + while ((rc = RTGetOpt(pGetState, &ValueUnion)) != 0) + { + switch (rc) + { + case 'a': + for (unsigned i = 0; i < g_cTests; i++) + g_aTests[i].fExcluded = true; + break; + + case 'b': + g_Ctx.pDrvReg = AudioTestFindBackendOpt(ValueUnion.psz); + if (g_Ctx.pDrvReg == NULL) + return RTEXITCODE_SYNTAX; + break; + + case 'd': + IoOpts.fWithDrvAudio = true; + break; + + case 'e': + if (ValueUnion.u32 >= g_cTests) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid test number %u passed to --exclude", ValueUnion.u32); + g_aTests[ValueUnion.u32].fExcluded = true; + break; + + case 'i': + if (ValueUnion.u32 >= g_cTests) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid test number %u passed to --include", ValueUnion.u32); + g_aTests[ValueUnion.u32].fExcluded = false; + break; + + case 'm': + IoOpts.fWithMixer = true; + break; + + AUDIO_TEST_COMMON_OPTION_CASES(ValueUnion, &g_CmdSelfTest); + + default: + return RTGetOptPrintError(rc, &ValueUnion); + } + } + + /* For simplicity both test environments, guest and host, will have the same I/O options. + ** @todo Make this indepedent by a prefix, "--[guest|host]-