diff --git a/tests/audio_engine_rates_test.cpp b/tests/audio_engine_rates_test.cpp index 71451e3d48..7f3fb4bb8e 100644 --- a/tests/audio_engine_rates_test.cpp +++ b/tests/audio_engine_rates_test.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -846,11 +847,14 @@ bool samePcm(const QByteArray& lhs, const QByteArray& rhs) return true; } -void concurrentSourcesAndEffects() +void concurrentSourcesAndEffects(std::optional selectedEffect = std::nullopt) { for (int mainRate : {24000, 48000}) { for (Effect effect : {Effect::None, Effect::Eq, Effect::Nr2, Effect::Rn2, Effect::Nr4, Effect::Dfnr, Effect::Mnr}) { + if (selectedEffect && effect != *selectedEffect) { + continue; + } const Rendered mainOnly = renderSources(mainRate, effect, true, false); if (!mainOnly.enabled) { const bool optional = effect == Effect::Nr4 || effect == Effect::Dfnr @@ -1090,27 +1094,125 @@ void fixedProcessingDomains() "RADE resampler never adopts main producer's 48 kHz rate"); } } + +struct AudioCase { + const char* name; + std::optional effect; +}; + +void printUsage(const char* program) +{ + std::fprintf(stderr, + "usage: %s [--case=rateMatrix|bandwidthAndMono|negotiatedSpeakerOutput|" + "negotiationOpenFailure|negotiationLifetimeAndCoalescing|auxiliaryAcrossNegotiation|" + "transitionsAndRejection|queueBudgetsAndDeviceTransitions|concurrentSourcesAndEffects|" + "effects-bypass|effects-EQ|effects-NR2|effects-RN2|effects-NR4|" + "effects-DFNR|effects-MNR|kiwiDeviceRateMatrix|processingStateReset|" + "legacyReplacementAndKiwiReset|fixedProcessingDomains]\n", program); +} + +std::optional parseCase(int argc, char** argv, bool& valid) +{ + valid = true; + if (argc == 1) { + return std::nullopt; + } + if (argc != 2 || std::strncmp(argv[1], "--case=", 7) != 0) { + valid = false; + return std::nullopt; + } + const char* name = argv[1] + 7; + for (const AudioCase candidate : { + AudioCase{"rateMatrix", std::nullopt}, + AudioCase{"bandwidthAndMono", std::nullopt}, + AudioCase{"negotiatedSpeakerOutput", std::nullopt}, + AudioCase{"negotiationOpenFailure", std::nullopt}, + AudioCase{"negotiationLifetimeAndCoalescing", std::nullopt}, + AudioCase{"auxiliaryAcrossNegotiation", std::nullopt}, + AudioCase{"transitionsAndRejection", std::nullopt}, + AudioCase{"queueBudgetsAndDeviceTransitions", std::nullopt}, + AudioCase{"concurrentSourcesAndEffects", std::nullopt}, + AudioCase{"effects-bypass", Effect::None}, + AudioCase{"effects-EQ", Effect::Eq}, + AudioCase{"effects-NR2", Effect::Nr2}, + AudioCase{"effects-RN2", Effect::Rn2}, + AudioCase{"effects-NR4", Effect::Nr4}, + AudioCase{"effects-DFNR", Effect::Dfnr}, + AudioCase{"effects-MNR", Effect::Mnr}, + AudioCase{"kiwiDeviceRateMatrix", std::nullopt}, + AudioCase{"processingStateReset", std::nullopt}, + AudioCase{"legacyReplacementAndKiwiReset", std::nullopt}, + AudioCase{"fixedProcessingDomains", std::nullopt}, + }) { + if (std::strcmp(name, candidate.name) == 0) { + return candidate; + } + } + valid = false; + return std::nullopt; +} + +template +void runStage(const char* name, Function function) +{ + QElapsedTimer elapsed; + elapsed.start(); + std::fprintf(stderr, "STAGE start %s\n", name); + std::fflush(stderr); + function(); + std::fprintf(stderr, "STAGE finish %s elapsed_ms=%lld\n", name, + static_cast(elapsed.elapsed())); + std::fflush(stderr); +} } // namespace int main(int argc, char** argv) { + bool validCase = false; + const std::optional selectedCase = parseCase(argc, argv, validCase); + if (!validCase) { + printUsage(argv[0]); + return 2; + } TestSettingsProfile settings(QStringLiteral("audio-engine-rates")); qputenv("AETHER_AUTOMATION", "1"); QCoreApplication application(argc, argv); check(settings.isValid(), "isolated settings profile"); - rateMatrix(); - bandwidthAndMono(); - negotiatedSpeakerOutput(); - negotiationOpenFailure(); - negotiationLifetimeAndCoalescing(); - auxiliaryAcrossNegotiation(); - transitionsAndRejection(); - queueBudgetsAndDeviceTransitions(); - concurrentSourcesAndEffects(); - kiwiDeviceRateMatrix(); - processingStateReset(); - legacyReplacementAndKiwiReset(); - fixedProcessingDomains(); + const auto selected = [&selectedCase](const char* name) { + return !selectedCase || std::strcmp(selectedCase->name, name) == 0; + }; + if (selected("rateMatrix")) { runStage("rateMatrix", rateMatrix); } + if (selected("bandwidthAndMono")) { runStage("bandwidthAndMono", bandwidthAndMono); } + if (selected("negotiatedSpeakerOutput")) { + runStage("negotiatedSpeakerOutput", negotiatedSpeakerOutput); + } + if (selected("negotiationOpenFailure")) { + runStage("negotiationOpenFailure", negotiationOpenFailure); + } + if (selected("negotiationLifetimeAndCoalescing")) { + runStage("negotiationLifetimeAndCoalescing", negotiationLifetimeAndCoalescing); + } + if (selected("auxiliaryAcrossNegotiation")) { + runStage("auxiliaryAcrossNegotiation", auxiliaryAcrossNegotiation); + } + if (selected("transitionsAndRejection")) { + runStage("transitionsAndRejection", transitionsAndRejection); + } + if (selected("queueBudgetsAndDeviceTransitions")) { + runStage("queueBudgetsAndDeviceTransitions", queueBudgetsAndDeviceTransitions); + } + if (selected("concurrentSourcesAndEffects") + || (selectedCase && selectedCase->effect)) { + const std::optional effect = selectedCase ? selectedCase->effect : std::nullopt; + runStage(selectedCase ? selectedCase->name : "concurrentSourcesAndEffects", + [effect] { concurrentSourcesAndEffects(effect); }); + } + if (selected("kiwiDeviceRateMatrix")) { runStage("kiwiDeviceRateMatrix", kiwiDeviceRateMatrix); } + if (selected("processingStateReset")) { runStage("processingStateReset", processingStateReset); } + if (selected("legacyReplacementAndKiwiReset")) { + runStage("legacyReplacementAndKiwiReset", legacyReplacementAndKiwiReset); + } + if (selected("fixedProcessingDomains")) { runStage("fixedProcessingDomains", fixedProcessingDomains); } std::printf("AudioEngine rates: %d checks, %d failures\n", checks, failures); return failures ? 1 : 0; } diff --git a/tests/nr_stereo_independence_test.cpp b/tests/nr_stereo_independence_test.cpp index d03e76f709..23abe8b32b 100644 --- a/tests/nr_stereo_independence_test.cpp +++ b/tests/nr_stereo_independence_test.cpp @@ -23,8 +23,11 @@ #include "nr_stereo_independence.h" #include +#include #include +#include +#include using namespace AetherSDR; using NrStereoIndependence::MakeProcess; @@ -37,11 +40,82 @@ int g_failures = 0; void check(bool condition, const QString& name) { std::printf("%s %s\n", condition ? "PASS" : "FAIL", qPrintable(name)); + std::fflush(stdout); if (!condition) { ++g_failures; } } +enum class Method { Nr2, Nr4, Nnr, Dfnr, Bnr }; + +struct Selection { + std::optional method; + std::optional rate; +}; + +void printUsage(const char* program) +{ + std::fprintf(stderr, "usage: %s [--method=nr2|nr4|nnr|dfnr|bnr] [--rate=24000|48000]\n", + program); +} + +std::optional parseMethod(const char* value) +{ + if (std::strcmp(value, "nr2") == 0) { return Method::Nr2; } + if (std::strcmp(value, "nr4") == 0) { return Method::Nr4; } + if (std::strcmp(value, "nnr") == 0) { return Method::Nnr; } + if (std::strcmp(value, "dfnr") == 0) { return Method::Dfnr; } + if (std::strcmp(value, "bnr") == 0) { return Method::Bnr; } + return std::nullopt; +} + +std::optional parseSelection(int argc, char** argv) +{ + Selection selection; + for (int index = 1; index < argc; ++index) { + const char* argument = argv[index]; + if (std::strncmp(argument, "--method=", 9) == 0 && !selection.method) { + selection.method = parseMethod(argument + 9); + if (!selection.method) { return std::nullopt; } + continue; + } + if (std::strncmp(argument, "--rate=", 7) == 0 && !selection.rate) { + if (std::strcmp(argument + 7, "24000") == 0) { + selection.rate = 24000; + } else if (std::strcmp(argument + 7, "48000") == 0) { + selection.rate = 48000; + } else { + return std::nullopt; + } + continue; + } + return std::nullopt; + } + return selection; +} + +bool selected(const Selection& selection, Method method) +{ + return !selection.method || *selection.method == method; +} + +void printStage(const char* state, const char* method, int rate, const QElapsedTimer& elapsed) +{ + std::fprintf(stderr, "STAGE %s method=%s rate=%d elapsed_ms=%lld\n", state, method, rate, + static_cast(elapsed.elapsed())); + std::fflush(stderr); +} + +template +void runMethodStage(const char* method, int rate, Function function) +{ + QElapsedTimer elapsed; + elapsed.start(); + printStage("start", method, rate, elapsed); + function(); + printStage("finish", method, rate, elapsed); +} + void checkMethod(const char* method, int rate, const MakeProcess& make) { const QString label = QStringLiteral("%1 @ %2 Hz: ").arg(QLatin1String(method)).arg(rate); @@ -91,48 +165,94 @@ bool bnrRecycledEffectStartsClean(const QString& pack, int rate) int main(int argc, char** argv) { + const std::optional selection = parseSelection(argc, argv); + if (!selection) { + printUsage(argv[0]); + return 2; + } QCoreApplication app(argc, argv); for (const int rate : {24000, 48000}) { - checkMethod("NR2", rate, [rate]() -> Process { - auto nr2 = std::make_shared(1024, rate, 4); - return [nr2](const QByteArray& pcm) { - QByteArray out(pcm.size(), Qt::Uninitialized); - nr2->processStereo(reinterpret_cast(pcm.constData()), - reinterpret_cast(out.data()), - pcm.size() / (2 * static_cast(sizeof(float)))); - return out; - }; - }); + if (selection->rate && *selection->rate != rate) { + continue; + } + if (selected(*selection, Method::Nr2)) { + runMethodStage("nr2", rate, [rate] { + checkMethod("NR2", rate, [rate]() -> Process { + auto nr2 = std::make_shared(1024, rate, 4); + return [nr2](const QByteArray& pcm) { + QByteArray out(pcm.size(), Qt::Uninitialized); + nr2->processStereo(reinterpret_cast(pcm.constData()), + reinterpret_cast(out.data()), + pcm.size() / (2 * static_cast(sizeof(float)))); + return out; + }; + }); + }); + } #ifdef HAVE_SPECBLEACH - checkMethod("NR4", rate, makeWrapper(rate)); + if (selected(*selection, Method::Nr4)) { + runMethodStage("nr4", rate, [rate] { + checkMethod("NR4", rate, makeWrapper(rate)); + }); + } +#else + if (selection->method == Method::Nr4) { + std::printf("SKIP NR4 @ %d Hz: not built with Specbleach\n", rate); + return g_failures != 0 ? 1 : 77; + } #endif - if (NnrFilter(rate).isValid()) { - checkMethod("NNR", rate, makeWrapper(rate)); - } else { - std::printf("SKIP NNR @ %d Hz: create_nnr() failed\n", rate); + if (selected(*selection, Method::Nnr) && NnrFilter(rate).isValid()) { + runMethodStage("nnr", rate, [rate] { + checkMethod("NNR", rate, makeWrapper(rate)); + }); + } else if (selected(*selection, Method::Nnr)) { + check(false, QStringLiteral("NNR @ %1 Hz: unconditional vendored filter initializes") + .arg(rate)); } #ifdef HAVE_DFNR - if (DeepFilterFilter(rate).isValid()) { - checkMethod("DFNR", rate, makeWrapper(rate)); - } else { + if (selected(*selection, Method::Dfnr) && DeepFilterFilter(rate).isValid()) { + runMethodStage("dfnr", rate, [rate] { + checkMethod("DFNR", rate, makeWrapper(rate)); + }); + } else if (selected(*selection, Method::Dfnr)) { std::printf("SKIP DFNR @ %d Hz: model not found\n", rate); + if (selection->method) { + return g_failures != 0 ? 1 : 77; + } + } +#else + if (selection->method == Method::Dfnr) { + std::printf("SKIP DFNR @ %d Hz: not built with DeepFilterNet\n", rate); + return g_failures != 0 ? 1 : 77; } #endif #ifdef HAVE_NVIDIA_AFX const QString pack = qEnvironmentVariable("AETHER_NVAFX_DIR"); - if (!pack.isEmpty() && NvidiaAfxFilter(pack, rate).isValid()) { - checkMethod("BNR", rate, makeWrapper(pack, rate)); - check(bnrRecycledEffectStartsClean(pack, rate), - QStringLiteral("BNR @ %1 Hz: a filter created while another is alive, " - "after a third was destroyed, matches a fresh one").arg(rate)); - } else { + if (selected(*selection, Method::Bnr) && !pack.isEmpty() + && NvidiaAfxFilter(pack, rate).isValid()) { + runMethodStage("bnr", rate, [&pack, rate] { + checkMethod("BNR", rate, makeWrapper(pack, rate)); + check(bnrRecycledEffectStartsClean(pack, rate), + QStringLiteral("BNR @ %1 Hz: a filter created while another is alive, " + "after a third was destroyed, matches a fresh one").arg(rate)); + }); + } else if (selected(*selection, Method::Bnr)) { + std::printf("SKIP BNR @ %d Hz: set AETHER_NVAFX_DIR to a working AFX pack\n", + rate); + if (selection->method) { + return g_failures != 0 ? 1 : 77; + } + } +#else + if (selection->method == Method::Bnr) { std::printf("SKIP BNR @ %d Hz: set AETHER_NVAFX_DIR to a working AFX pack\n", rate); + return g_failures != 0 ? 1 : 77; } #endif } diff --git a/tests/peripheral_auth_dialog_test.cpp b/tests/peripheral_auth_dialog_test.cpp index 0fdcf53c06..099b565b57 100644 --- a/tests/peripheral_auth_dialog_test.cpp +++ b/tests/peripheral_auth_dialog_test.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -40,6 +41,7 @@ #include #include #include +#include #include #include @@ -1872,61 +1874,8 @@ bool checkStatusPresentation() return sawDisabled; } -int main(int argc, char** argv) +int checkInteraction(QApplication& app) { - TestSettingsProfile profile(QStringLiteral("peripheral-auth-dialog-test")); - if (!profile.isValid()) { - return 1; - } - QApplication app(argc, argv); - AppSettings::instance().load(); - // Remove asks first; the lifecycle checks below confirm it. The confirmation - // itself is covered by checkConnectAutomaticallyToggle. - RadioSetupDialog::setRemovalConfirmationHookForTest( - [](const QString&, const QString&) { return true; }); - if (!checkRemovalWithUnknownOwner() || !checkRemovalKeepsNewerEndpoint() - || !checkRemovalOfUnconfiguredSharedRow() || !checkConnectAutomaticallyToggle() || !checkConnectAutomaticallyGates() - || !checkStatusPresentation()) { - std::fprintf(stderr, "Connect automatically regressed\n"); - return 1; - } - if (!checkSharedCredentialRemoval() || !checkRemovalWithFailedVaultRead() || !checkNeverConfiguredRemovalKeepsDiscovery() || !checkRemovalSignalAndPendingNotice() || !checkRemovalTimeout() || !checkSharedModelRemovalIsolation() - || !checkPendingRemoval() || !checkRemovalOwnerTeardown() - || !checkShackSwitchRetryDuringRemoval() || !checkOneShotShackSwitchDuringRemoval() - || !checkRemovalIgnoresClearedAddress() || !checkRemovalBesideLiveSharedDevice() - || !checkRecoveryShowsRefusingEndpoint()) { - std::fprintf(stderr, "Pending removal lifecycle regressed\n"); - return 1; - } - if (!checkRemovalDuringAuthRead()) { - std::fprintf(stderr, "Removal during a pending auth read created a false failure\n"); - return 1; - } - if (!checkManualRecoveryProvenance()) { - std::fprintf(stderr, "Manual recovery was mistaken for radio discovery\n"); - return 1; - } - if (!checkAgCloseAfterExternalConfiguration()) { - std::fprintf(stderr, "AG external configuration/close regression\n"); - return 1; - } - if (!checkRemovedDiscovery() || !checkExternalAgConfiguration()) { - std::fprintf(stderr, "Removed discovery or external AG configuration regressed\n"); - return 1; - } - if (!checkUnavailableKeychainRemoval() || !checkFieldDescriptions()) { - std::fprintf(stderr, "Keychain-unavailable removal or field descriptions regressed\n"); - return 1; - } - for (bool savedEmptyList : {false, true}) { - for (bool blockedBeforeOpening : {false, true}) { - if (!checkDiscoveredAuthRecovery(savedEmptyList, blockedBeforeOpening)) { - std::fprintf(stderr, "Blocked discovered devices lacked recovery controls (saved=%d, before=%d)\n", - savedEmptyList, blockedBeforeOpening); - return 1; - } - } - } AppSettings::instance().remove(QStringLiteral("Peripherals")); PeripheralSettings::setDeviceInt(QStringLiteral("Vkamp"), QStringLiteral("Variant"), 1); PeripheralSettings::setDeviceInt(QStringLiteral("Lp100a"), QStringLiteral("RangeHighW"), 3000); @@ -2450,3 +2399,164 @@ int main(int argc, char** argv) } return 0; } + +struct Case { + const char* name; +}; + +void printUsage(const char* program) +{ + std::fprintf(stderr, + "usage: %s [--case=automatic|removal|auth-read|manual-recovery|external-ag|" + "discovery|availability|discovered-recovery|interaction]\n", program); +} + +const char* parseCase(int argc, char** argv, bool& valid) +{ + valid = true; + if (argc == 1) { + return nullptr; + } + if (argc != 2 || std::strncmp(argv[1], "--case=", 7) != 0) { + valid = false; + return nullptr; + } + const char* name = argv[1] + 7; + for (const Case candidate : {Case{"automatic"}, Case{"removal"}, Case{"auth-read"}, + Case{"manual-recovery"}, Case{"external-ag"}, + Case{"discovery"}, Case{"availability"}, + Case{"discovered-recovery"}, Case{"interaction"}}) { + if (std::strcmp(name, candidate.name) == 0) { + return candidate.name; + } + } + valid = false; + return nullptr; +} + +template +int runStage(const char* name, Function function) +{ + QElapsedTimer elapsed; + elapsed.start(); + std::fprintf(stderr, "STAGE start %s\n", name); + std::fflush(stderr); + const int result = function(); + std::fprintf(stderr, "STAGE finish %s elapsed_ms=%lld result=%d\n", name, + static_cast(elapsed.elapsed()), result); + std::fflush(stderr); + return result; +} + +int main(int argc, char** argv) +{ + bool validCase = false; + const char* const selectedCase = parseCase(argc, argv, validCase); + if (!validCase) { + printUsage(argv[0]); + return 2; + } + TestSettingsProfile profile(QStringLiteral("peripheral-auth-dialog-test")); + if (!profile.isValid()) { + return 1; + } + QApplication app(argc, argv); + AppSettings::instance().load(); + // Remove asks first; the lifecycle checks below confirm it. The confirmation + // itself is covered by checkConnectAutomaticallyToggle. + RadioSetupDialog::setRemovalConfirmationHookForTest( + [](const QString&, const QString&) { return true; }); + const auto selected = [selectedCase](const char* name) { + return !selectedCase || std::strcmp(selectedCase, name) == 0; + }; + if (selected("automatic") && runStage("automatic", [] { + if (!checkRemovalWithUnknownOwner() || !checkRemovalKeepsNewerEndpoint() + || !checkRemovalOfUnconfiguredSharedRow() || !checkConnectAutomaticallyToggle() + || !checkConnectAutomaticallyGates() || !checkStatusPresentation()) { + std::fprintf(stderr, "Connect automatically regressed\n"); + return 1; + } + return 0; + }) != 0) { + return 1; + } + if (selected("removal") && runStage("removal", [] { + if (!checkSharedCredentialRemoval() || !checkRemovalWithFailedVaultRead() + || !checkNeverConfiguredRemovalKeepsDiscovery() || !checkRemovalSignalAndPendingNotice() + || !checkRemovalTimeout() || !checkSharedModelRemovalIsolation() + || !checkPendingRemoval() || !checkRemovalOwnerTeardown() + || !checkShackSwitchRetryDuringRemoval() || !checkOneShotShackSwitchDuringRemoval() + || !checkRemovalIgnoresClearedAddress() || !checkRemovalBesideLiveSharedDevice() + || !checkRecoveryShowsRefusingEndpoint()) { + std::fprintf(stderr, "Pending removal lifecycle regressed\n"); + return 1; + } + return 0; + }) != 0) { + return 1; + } + if (selected("auth-read") && runStage("auth-read", [] { + if (!checkRemovalDuringAuthRead()) { + std::fprintf(stderr, "Removal during a pending auth read created a false failure\n"); + return 1; + } + return 0; + }) != 0) { + return 1; + } + if (selected("manual-recovery") && runStage("manual-recovery", [] { + if (!checkManualRecoveryProvenance()) { + std::fprintf(stderr, "Manual recovery was mistaken for radio discovery\n"); + return 1; + } + return 0; + }) != 0) { + return 1; + } + if (selected("external-ag") && runStage("external-ag", [] { + if (!checkAgCloseAfterExternalConfiguration()) { + std::fprintf(stderr, "AG external configuration/close regression\n"); + return 1; + } + return 0; + }) != 0) { + return 1; + } + if (selected("discovery") && runStage("discovery", [] { + if (!checkRemovedDiscovery() || !checkExternalAgConfiguration()) { + std::fprintf(stderr, "Removed discovery or external AG configuration regressed\n"); + return 1; + } + return 0; + }) != 0) { + return 1; + } + if (selected("availability") && runStage("availability", [] { + if (!checkUnavailableKeychainRemoval() || !checkFieldDescriptions()) { + std::fprintf(stderr, "Keychain-unavailable removal or field descriptions regressed\n"); + return 1; + } + return 0; + }) != 0) { + return 1; + } + if (selected("discovered-recovery") && runStage("discovered-recovery", [] { + for (bool savedEmptyList : {false, true}) { + for (bool blockedBeforeOpening : {false, true}) { + if (!checkDiscoveredAuthRecovery(savedEmptyList, blockedBeforeOpening)) { + std::fprintf(stderr, + "Blocked discovered devices lacked recovery controls (saved=%d, before=%d)\n", + savedEmptyList, blockedBeforeOpening); + return 1; + } + } + } + return 0; + }) != 0) { + return 1; + } + if (selected("interaction") && runStage("interaction", [&app] { return checkInteraction(app); }) != 0) { + return 1; + } + return 0; +} diff --git a/tests/rtl_spectrum_averaging_test.cpp b/tests/rtl_spectrum_averaging_test.cpp index ecd4a52c21..e5eb28ccdd 100644 --- a/tests/rtl_spectrum_averaging_test.cpp +++ b/tests/rtl_spectrum_averaging_test.cpp @@ -119,30 +119,45 @@ void cadenceAndReceiverIndependence() void noiseStatistics() { + constexpr int kFrameCount = 96; + constexpr int kWarmupFrames = 64; + constexpr int kBinsPerFrame = 64; + constexpr int kFirstBin = 1000; + constexpr int kBinStride = 16; + constexpr int kMeasurements = (kFrameCount - kWarmupFrames) * kBinsPerFrame; Probe raw(0,false), low(5,false), high(100,false); std::mt19937 generator(5782); std::normal_distribution noise(0,.05f); QVector> iq(96000); double sum[3]{},squares[3]{}; - for(int frame=0;frame<500;++frame) { + for(int frame=0;framelast.constData()+1000*sizeof(float),sizeof(db)); - const double power=std::pow(10.,db/10.); - sum[i]+=power;squares[i]+=power*power; + for (int bin = 0; bin < kBinsPerFrame; ++bin) { + float db = 0; + const int binIndex = kFirstBin + bin * kBinStride; + std::memcpy(&db, probes[i]->last.constData() + binIndex * int(sizeof(float)), sizeof(db)); + const double power = std::pow(10., db / 10.); + sum[i] += power; + squares[i] += power * power; + } } } double variance[3]{}; - for(int i=0;i<3;++i) { variance[i]=squares[i]/400-std::pow(sum[i]/400,2); } + for(int i=0;i<3;++i) { + variance[i] = squares[i] / kMeasurements - std::pow(sum[i] / kMeasurements, 2); + } check(variance[1]" + "-DEXPECTED_METHODS=${_weather_radar_loading_cases}" + -P "${CMAKE_CURRENT_SOURCE_DIR}/tests/verify_qttest_registration.cmake") +set_tests_properties(weather_radar_loading_registration_test PROPERTIES + ENVIRONMENT "QT_QPA_PLATFORM=offscreen" TIMEOUT 15) +foreach(_case IN LISTS _weather_radar_loading_cases) + add_test(NAME weather_radar_loading_${_case}_test + COMMAND weather_radar_loading_test ${_case}) + set_tests_properties(weather_radar_loading_${_case}_test PROPERTIES + ENVIRONMENT "QT_QPA_PLATFORM=offscreen" TIMEOUT 30) +endforeach() # Opt-in: the native GPU radar upload and shader alpha-filtering contract. It # needs a real OpenGL 3.2 context, which no CI lane has. Enable on a machine @@ -4692,9 +4760,13 @@ target_include_directories(peripheral_auth_dialog_test PRIVATE tests/fakes src t target_link_libraries(peripheral_auth_dialog_test PRIVATE aetherdesktop_support Qt6::Widgets Qt6::Test) set_target_properties(peripheral_auth_dialog_test PROPERTIES AUTOMOC ON) -add_test(NAME peripheral_auth_dialog_test COMMAND peripheral_auth_dialog_test) -set_tests_properties(peripheral_auth_dialog_test PROPERTIES - ENVIRONMENT "QT_QPA_PLATFORM=offscreen" TIMEOUT 60) +foreach(_case IN ITEMS automatic removal auth-read manual-recovery external-ag + discovery availability discovered-recovery interaction) + add_test(NAME peripheral_auth_dialog_${_case}_test + COMMAND peripheral_auth_dialog_test --case=${_case}) + set_tests_properties(peripheral_auth_dialog_${_case}_test PROPERTIES + ENVIRONMENT "QT_QPA_PLATFORM=offscreen" TIMEOUT 60) +endforeach() # Production Keychain adapter with an in-memory job double; no OS vault or socket. add_executable(peripheral_auth_keychain_test diff --git a/tests/verify_qttest_registration.cmake b/tests/verify_qttest_registration.cmake new file mode 100644 index 0000000000..8709082908 --- /dev/null +++ b/tests/verify_qttest_registration.cmake @@ -0,0 +1,42 @@ +# Compare QtTest's executable method inventory with the split CTest cases. +if(NOT DEFINED TEST_EXECUTABLE OR NOT DEFINED EXPECTED_METHODS) + message(FATAL_ERROR "TEST_EXECUTABLE and EXPECTED_METHODS are required") +endif() +execute_process( + COMMAND "${TEST_EXECUTABLE}" -functions + RESULT_VARIABLE _result + OUTPUT_VARIABLE _output + ERROR_VARIABLE _error + TIMEOUT 10) +if(NOT "${_result}" STREQUAL "0") + message(FATAL_ERROR "QtTest method discovery failed: ${_result}\n${_error}") +endif() +string(REPLACE "\r\n" "\n" _output "${_output}") +string(REPLACE "\n" ";" _lines "${_output}") +set(_actual) +foreach(_line IN LISTS _lines) + string(STRIP "${_line}" _line) + if(_line STREQUAL "") + continue() + endif() + if(NOT _line MATCHES "^([A-Za-z_][A-Za-z0-9_]*)\\(\\)$") + message(FATAL_ERROR "Unexpected QtTest discovery output: ${_line}") + endif() + list(APPEND _actual "${CMAKE_MATCH_1}") +endforeach() +if(NOT _actual) + message(FATAL_ERROR "QtTest discovery returned no methods") +endif() +set(_expected "${EXPECTED_METHODS}") +list(SORT _actual) +list(SORT _expected) +if(NOT "${_actual}" STREQUAL "${_expected}") + set(_missing ${_actual}) + list(REMOVE_ITEM _missing ${_expected}) + set(_unknown ${_expected}) + list(REMOVE_ITEM _unknown ${_actual}) + message(FATAL_ERROR + "QtTest registration mismatch\nUnregistered methods: ${_missing}\n" + "Unknown or duplicate registrations: ${_unknown}\n" + "Expected: ${_expected}\nDiscovered: ${_actual}") +endif()