File indexing completed on 2024-04-21 03:56:47

0001 /*
0002     SPDX-FileCopyrightText: 2020 Alexander Lohnau <alexander.lohnau@gmx.de>
0003     SPDX-License-Identifier: LGPL-2.0-or-later
0004 */
0005 
0006 #ifndef KRUNNER_ABSTRACTRUNNERTEST_H
0007 #define KRUNNER_ABSTRACTRUNNERTEST_H
0008 
0009 #include <KPluginMetaData>
0010 #include <KRunner/AbstractRunner>
0011 #include <KRunner/RunnerManager>
0012 #include <QStandardPaths>
0013 
0014 #include <QSignalSpy>
0015 #include <QTest>
0016 #if KRUNNER_DBUS_RUNNER_TESTING
0017 #include <QDBusConnection>
0018 #include <QDBusConnectionInterface>
0019 #include <QDBusServiceWatcher>
0020 #include <QProcess>
0021 #include <QTimer>
0022 #endif
0023 
0024 namespace KRunner
0025 {
0026 /**
0027  * This class provides a basic structure for a runner test.
0028  * The compile definitions should be configured using the `krunner_configure_test` cmake macro
0029  * @since 5.80
0030  */
0031 class AbstractRunnerTest : public QObject
0032 {
0033 public:
0034     using QObject::QObject;
0035     std::unique_ptr<KRunner::RunnerManager> manager = nullptr;
0036     KRunner::AbstractRunner *runner = nullptr;
0037 
0038     /**
0039      * Load the runner and set the manager and runner properties.
0040      */
0041     void initProperties()
0042     {
0043         qputenv("LC_ALL", "C.utf-8");
0044         manager.reset(new KRunner::RunnerManager());
0045 
0046 #if KRUNNER_DBUS_RUNNER_TESTING
0047         auto md = manager->convertDBusRunnerToJson(QStringLiteral(KRUNNER_TEST_DESKTOP_FILE));
0048         QVERIFY(md.isValid());
0049         manager->loadRunner(md);
0050 #else
0051         const QString pluginId = QFileInfo(QStringLiteral(KRUNNER_TEST_RUNNER_PLUGIN_NAME)).completeBaseName();
0052         auto metaData = KPluginMetaData::findPluginById(QStringLiteral(KRUNNER_TEST_RUNNER_PLUGIN_DIR), pluginId);
0053         QVERIFY2(metaData.isValid(), qPrintable("Could not find plugin " + pluginId + " in folder " + KRUNNER_TEST_RUNNER_PLUGIN_DIR));
0054 
0055         // Set internal variables
0056         manager->loadRunner(metaData);
0057 #endif
0058         QCOMPARE(manager->runners().count(), 1);
0059         runner = manager->runners().constFirst();
0060 
0061         // Just make sure all went well
0062         QVERIFY(runner);
0063     }
0064 
0065     /**
0066      * Launch a query and wait for the RunnerManager to finish
0067      * @param query
0068      * @param runnerName
0069      * @return matches of the current query
0070      */
0071     QList<QueryMatch> launchQuery(const QString &query, const QString &runnerName = QString())
0072     {
0073         QSignalSpy spy(manager.get(), &KRunner::RunnerManager::queryFinished);
0074         manager->launchQuery(query, runnerName);
0075         if (!QTest::qVerify(spy.wait(), "spy.wait()", "RunnerManager did not emit the queryFinished signal", __FILE__, __LINE__)) {
0076             return {};
0077         }
0078         return manager->matches();
0079     }
0080 #if KRUNNER_DBUS_RUNNER_TESTING
0081     /**
0082      * Launch the configured DBus executable with the given arguments and wait for the process to be started.
0083      * @param args
0084      * @param waitForService Wait for this service to be registered, this will default to the service from the metadata
0085      * @return Process that was successfully started
0086      */
0087     QProcess *startDBusRunnerProcess(const QStringList &args = {}, const QString waitForService = QString())
0088     {
0089         qputenv("LC_ALL", "C.utf-8");
0090         QProcess *process = new QProcess();
0091         auto md = manager->convertDBusRunnerToJson(QStringLiteral(KRUNNER_TEST_DESKTOP_FILE));
0092         QString serviceToWatch = waitForService;
0093         if (serviceToWatch.isEmpty()) {
0094             serviceToWatch = md.value(QStringLiteral("X-Plasma-DBusRunner-Service"));
0095         }
0096         QEventLoop loop;
0097         // Wait for the service to show up. Same logic as the dbusrunner
0098         connect(QDBusConnection::sessionBus().interface(),
0099                 &QDBusConnectionInterface::serviceOwnerChanged,
0100                 &loop,
0101                 [&loop, serviceToWatch](const QString &serviceName, const QString &, const QString &newOwner) {
0102                     if (serviceName == serviceToWatch && !newOwner.isEmpty()) {
0103                         loop.quit();
0104                     }
0105                 });
0106 
0107         // Otherwise, we just wait forever without any indication what we are waiting for
0108         QTimer::singleShot(10000, &loop, [&loop, process]() {
0109             loop.quit();
0110 
0111             if (process->state() == QProcess::ProcessState::NotRunning) {
0112                 qWarning() << "stderr of" << KRUNNER_TEST_DBUS_EXECUTABLE << "is:";
0113                 qWarning().noquote() << process->readAllStandardError();
0114             }
0115             Q_ASSERT_X(false, "AbstractRunnerTest::startDBusRunnerProcess", "DBus service was not registered within 10 seconds");
0116         });
0117         process->start(QStringLiteral(KRUNNER_TEST_DBUS_EXECUTABLE), args);
0118         loop.exec();
0119         process->waitForStarted(5);
0120 
0121         Q_ASSERT(process->state() == QProcess::ProcessState::Running);
0122         m_runningProcesses << process;
0123         return process;
0124     }
0125 
0126     /**
0127      * Kill all processes that got started with the startDBusRunnerProcess
0128      */
0129     void killRunningDBusProcesses()
0130     {
0131         for (auto &process : std::as_const(m_runningProcesses)) {
0132             process->kill();
0133             QVERIFY(process->waitForFinished());
0134             if (QTest::currentTestFailed()) {
0135                 qWarning().noquote() << "Output from " << process->program() << ": " << process->readAll();
0136             }
0137         }
0138         qDeleteAll(m_runningProcesses);
0139         m_runningProcesses.clear();
0140     }
0141 
0142 private:
0143     QList<QProcess *> m_runningProcesses;
0144 #endif
0145 };
0146 }
0147 
0148 #endif