// 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"