From 5b26b85cc3c299ef1df9a5bff40d527d98f1df33 Mon Sep 17 00:00:00 2001 From: Marco Allegretti Date: Sat, 14 Feb 2026 17:56:09 +0100 Subject: [PATCH] tests: add ProcessScanner stability test Add a new QtTest that spawns marker processes and verifies ProcessScanner::scan() matches by env/cmdline, preferEnvironmentMatches behavior, and cancellation. Register the test with ctest. --- tests/CMakeLists.txt | 24 ++++ tests/processscanner_test.cpp | 202 ++++++++++++++++++++++++++++++++++ 2 files changed, 226 insertions(+) create mode 100644 tests/processscanner_test.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a61a509..2080bd5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -43,3 +43,27 @@ add_test(NAME alakarte_stop_launching_regression_test set_tests_properties(alakarte_stop_launching_regression_test PROPERTIES TIMEOUT 60 ) + +add_executable(alakarte_processscanner_test + processscanner_test.cpp + ../src/gamecenter/processscanner.cpp + ../src/gamecenter/processscanner.h +) + +target_include_directories(alakarte_processscanner_test PRIVATE + ${CMAKE_SOURCE_DIR}/src/gamecenter +) + +target_link_libraries(alakarte_processscanner_test PRIVATE + Qt6::Core + Qt6::Concurrent + Qt6::Test +) + +add_test(NAME alakarte_processscanner_test + COMMAND alakarte_processscanner_test +) + +set_tests_properties(alakarte_processscanner_test PROPERTIES + TIMEOUT 20 +) diff --git a/tests/processscanner_test.cpp b/tests/processscanner_test.cpp new file mode 100644 index 0000000..e41deb1 --- /dev/null +++ b/tests/processscanner_test.cpp @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: 2026 A-La-Karte Contributors + +#include "processscanner.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +class ProcessScannerTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + + void scanFindsEnvAndCmdline(); + void preferEnvironmentMatchesFilters(); + void cancelledScanReturnsEmpty(); + +private: + QString m_shell; + QString m_marker; + QProcess m_envProc; + QProcess m_cmdProc; + + static bool containsPid(const QList &matches, qint64 pid) + { + for (const auto &m : matches) { + if (m.pid == static_cast(pid)) { + return true; + } + } + return false; + } + + static const ProcessScanner::Match *findByPid(const QList &matches, qint64 pid) + { + for (const auto &m : matches) { + if (m.pid == static_cast(pid)) { + return &m; + } + } + return nullptr; + } + + static bool waitForScan(std::function predicate, int timeoutMs) + { + QElapsedTimer t; + t.start(); + while (t.elapsed() < timeoutMs) { + if (predicate()) { + return true; + } + QTest::qWait(50); + } + return false; + } + + void startProcesses(); + void stopProcess(QProcess &p); +}; + +void ProcessScannerTest::initTestCase() +{ + if (!QFileInfo(QStringLiteral("/proc")).isDir()) { + QSKIP("/proc not available"); + } + + m_shell = QStandardPaths::findExecutable(QStringLiteral("sh")); + if (m_shell.isEmpty()) { + QSKIP("sh not found"); + } + + m_marker = QUuid::createUuid().toString(QUuid::WithoutBraces); + startProcesses(); +} + +void ProcessScannerTest::cleanupTestCase() +{ + stopProcess(m_envProc); + stopProcess(m_cmdProc); +} + +void ProcessScannerTest::startProcesses() +{ + const QString script = QStringLiteral("while true; do sleep 60; done # %1").arg(m_marker); + + { + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + env.insert(QStringLiteral("ALAKARTE_PROCESSSCANNER_TEST_MARKER"), m_marker); + m_envProc.setProcessEnvironment(env); + m_envProc.start(m_shell, {QStringLiteral("-c"), script}); + QVERIFY2(m_envProc.waitForStarted(5000), qPrintable(m_envProc.errorString())); + QVERIFY(m_envProc.processId() > 0); + } + + { + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + m_cmdProc.setProcessEnvironment(env); + m_cmdProc.start(m_shell, {QStringLiteral("-c"), script}); + QVERIFY2(m_cmdProc.waitForStarted(5000), qPrintable(m_cmdProc.errorString())); + QVERIFY(m_cmdProc.processId() > 0); + } +} + +void ProcessScannerTest::stopProcess(QProcess &p) +{ + if (p.state() == QProcess::NotRunning) { + return; + } + + p.terminate(); + if (!p.waitForFinished(3000)) { + p.kill(); + p.waitForFinished(3000); + } +} + +void ProcessScannerTest::scanFindsEnvAndCmdline() +{ + QVERIFY(m_envProc.processId() > 0); + QVERIFY(m_cmdProc.processId() > 0); + + ProcessScanner::ScanOptions opts; + opts.envKeys = {QStringLiteral("ALAKARTE_PROCESSSCANNER_TEST_MARKER")}; + opts.envValue = m_marker; + opts.cmdlineSubstring = m_marker; + opts.preferEnvironmentMatches = false; + + QList results; + const bool ok = waitForScan( + [&]() { + results = ProcessScanner::scan(opts); + return containsPid(results, m_envProc.processId()) && containsPid(results, m_cmdProc.processId()); + }, + 5000); + + QVERIFY(ok); + + const auto *envMatch = findByPid(results, m_envProc.processId()); + QVERIFY(envMatch); + QVERIFY(envMatch->envMatched); + + const auto *cmdMatch = findByPid(results, m_cmdProc.processId()); + QVERIFY(cmdMatch); + QVERIFY(!cmdMatch->envMatched); +} + +void ProcessScannerTest::preferEnvironmentMatchesFilters() +{ + QVERIFY(m_envProc.processId() > 0); + QVERIFY(m_cmdProc.processId() > 0); + + ProcessScanner::ScanOptions opts; + opts.envKeys = {QStringLiteral("ALAKARTE_PROCESSSCANNER_TEST_MARKER")}; + opts.envValue = m_marker; + opts.cmdlineSubstring = m_marker; + opts.preferEnvironmentMatches = true; + + QList results; + const bool ok = waitForScan( + [&]() { + results = ProcessScanner::scan(opts); + return containsPid(results, m_envProc.processId()); + }, + 5000); + + QVERIFY(ok); + QVERIFY(containsPid(results, m_envProc.processId())); + QVERIFY(!containsPid(results, m_cmdProc.processId())); + + for (const auto &m : std::as_const(results)) { + QVERIFY(m.envMatched); + } +} + +void ProcessScannerTest::cancelledScanReturnsEmpty() +{ + ProcessScanner::ScanOptions opts; + opts.envKeys = {QStringLiteral("ALAKARTE_PROCESSSCANNER_TEST_MARKER")}; + opts.envValue = m_marker; + opts.cmdlineSubstring = m_marker; + opts.preferEnvironmentMatches = false; + + const std::atomic_bool cancelled(true); + const QList results = ProcessScanner::scan(opts, cancelled); + QVERIFY(results.isEmpty()); +} + +QTEST_MAIN(ProcessScannerTest) + +#include "processscanner_test.moc"