File indexing completed on 2024-05-19 16:41:38
0001 /* 0002 SPDX-FileCopyrightText: 2016-2021 Harald Sitter <sitter@kde.org> 0003 SPDX-FileCopyrightText: 2022 Alexander Lohnau <alexander.lohnau@gmx.de> 0004 0005 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0006 */ 0007 0008 #include <QDebug> 0009 #include <QDir> 0010 #include <QFile> 0011 #include <QObject> 0012 #include <QStandardPaths> 0013 #include <QTest> 0014 #include <QThread> 0015 0016 #include <KSycoca> 0017 0018 #include "../servicerunner.h" 0019 0020 #include <clocale> 0021 #include <optional> 0022 #include <sys/types.h> 0023 #include <unistd.h> 0024 0025 class ServiceRunnerTest : public QObject 0026 { 0027 Q_OBJECT 0028 private Q_SLOTS: 0029 void initTestCase(); 0030 void cleanupTestCase(); 0031 0032 void testExcutableExactMatch(); 0033 void testKonsoleVsYakuakeComment(); 0034 void testSystemSettings(); 0035 void testSystemSettings2(); 0036 void testCategories(); 0037 void testJumpListActions(); 0038 void testINotifyUsage(); 0039 void testSpecialArgs(); 0040 void testEnv(); 0041 }; 0042 0043 void ServiceRunnerTest::initTestCase() 0044 { 0045 QStandardPaths::setTestModeEnabled(true); 0046 0047 auto appsPath = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation); 0048 QDir(appsPath).removeRecursively(); 0049 QVERIFY(QDir().mkpath(appsPath)); 0050 auto fixtureDir = QDir(QFINDTESTDATA("fixtures")); 0051 const auto infoList = fixtureDir.entryInfoList(QDir::Files); 0052 for (const auto &fileInfo : infoList) { 0053 auto source = fileInfo.absoluteFilePath(); 0054 auto target = appsPath + QDir::separator() + fileInfo.fileName(); 0055 QVERIFY2(QFile::copy(fileInfo.absoluteFilePath(), target), qPrintable(QStringLiteral("can't copy %1 => %2").arg(source, target))); 0056 } 0057 0058 setlocale(LC_ALL, "C.utf8"); 0059 0060 KSycoca::self()->ensureCacheValid(); 0061 0062 // Make sure noDisplay behaves consistently WRT OnlyShowIn etc. 0063 QVERIFY(setenv("XDG_CURRENT_DESKTOP", "KDE", 1) == 0); 0064 // NOTE: noDisplay also includes X-KDE-OnlyShowOnQtPlatforms which is a bit harder to fake 0065 // and not currently under testing anyway. 0066 } 0067 0068 void ServiceRunnerTest::cleanupTestCase() 0069 { 0070 } 0071 0072 void ServiceRunnerTest::testExcutableExactMatch() 0073 { 0074 ServiceRunner runner(this, KPluginMetaData(), QVariantList()); 0075 Plasma::RunnerContext context; 0076 context.setQuery(QStringLiteral("Virtual Machine Manager ServiceRunnerTest")); // virt-manager.desktop 0077 0078 runner.match(context); 0079 auto matches = context.matches(); 0080 QVERIFY(std::any_of(matches.cbegin(), matches.cend(), [](const Plasma::QueryMatch &match) { 0081 return match.text() == QLatin1String("Virtual Machine Manager ServiceRunnerTest") && match.relevance() == 1; 0082 })); 0083 } 0084 0085 void ServiceRunnerTest::testKonsoleVsYakuakeComment() 0086 { 0087 // Yakuake has konsole mentioned in comment, should be rated lower. 0088 ServiceRunner runner(this, KPluginMetaData(), QVariantList()); 0089 Plasma::RunnerContext context; 0090 context.setQuery(QStringLiteral("kons")); 0091 0092 runner.match(context); 0093 0094 bool konsoleFound = false; 0095 bool yakuakeFound = false; 0096 const auto matches = context.matches(); 0097 for (const auto &match : matches) { 0098 qDebug() << "matched" << match.text(); 0099 if (!match.text().contains(QLatin1String("ServiceRunnerTest"))) { 0100 continue; 0101 } 0102 0103 if (match.text() == QLatin1String("Konsole ServiceRunnerTest")) { 0104 QCOMPARE(match.relevance(), 0.99); 0105 konsoleFound = true; 0106 } else if (match.text() == QLatin1String("Yakuake ServiceRunnerTest")) { 0107 // Rates lower because it doesn't have it in the name. 0108 QCOMPARE(match.relevance(), 0.59); 0109 yakuakeFound = true; 0110 } 0111 } 0112 QVERIFY(konsoleFound); 0113 QVERIFY(yakuakeFound); 0114 } 0115 0116 void ServiceRunnerTest::testSystemSettings() 0117 { 0118 // In 5.9.0 'System Settings' suddenly didn't come back as a match for 'settings' anymore. 0119 // System Settings has a noKDE version and a KDE version, if the noKDE version is encountered 0120 // first it will be added to the seen cache, however disqualification of already seen items 0121 // may then also disqualify the KDE version of system settings on account of having already 0122 // seen it. This test makes sure we find the right version. 0123 ServiceRunner runner(this, KPluginMetaData(), QVariantList()); 0124 Plasma::RunnerContext context; 0125 context.setQuery(QStringLiteral("settings")); 0126 0127 runner.match(context); 0128 0129 bool systemSettingsFound = false; 0130 bool foreignSystemSettingsFound = false; 0131 const auto matches = context.matches(); 0132 for (const auto &match : matches) { 0133 qDebug() << "matched" << match.text(); 0134 if (match.text() == QLatin1String("System Settings ServiceRunnerTest")) { 0135 systemSettingsFound = true; 0136 } 0137 if (match.text() == QLatin1String("KDE System Settings ServiceRunnerTest")) { 0138 foreignSystemSettingsFound = true; 0139 } 0140 } 0141 QVERIFY(systemSettingsFound); 0142 QVERIFY(!foreignSystemSettingsFound); 0143 } 0144 0145 void ServiceRunnerTest::testSystemSettings2() 0146 { 0147 ServiceRunner runner(this, KPluginMetaData(), QVariantList()); 0148 Plasma::RunnerContext context; 0149 context.setQuery(QStringLiteral("sy")); 0150 0151 runner.match(context); 0152 0153 bool systemSettingsFound = false; 0154 bool foreignSystemSettingsFound = false; 0155 const auto matches = context.matches(); 0156 for (const auto &match : matches) { 0157 qDebug() << "matched" << match.text(); 0158 if (match.text() == QLatin1String("System Settings ServiceRunnerTest")) { 0159 systemSettingsFound = true; 0160 } 0161 if (match.text() == QLatin1String("KDE System Settings ServiceRunnerTest")) { 0162 foreignSystemSettingsFound = true; 0163 } 0164 } 0165 QVERIFY(systemSettingsFound); 0166 QVERIFY(!foreignSystemSettingsFound); 0167 } 0168 0169 void ServiceRunnerTest::testCategories() 0170 { 0171 ServiceRunner runner(this, KPluginMetaData(), QVariantList()); 0172 Plasma::RunnerContext context; 0173 0174 context.setQuery(QStringLiteral("System")); 0175 runner.match(context); 0176 auto matches = context.matches(); 0177 QVERIFY(std::any_of(matches.cbegin(), matches.cend(), [](const Plasma::QueryMatch &match) { 0178 return match.text() == QLatin1String("Konsole ServiceRunnerTest") && match.relevance() == 0.64; 0179 })); 0180 0181 // Multiple categories, this should still match, but now as relevant 0182 context.setQuery(QStringLiteral("System KDE TerminalEmulator")); 0183 runner.match(context); 0184 matches = context.matches(); 0185 QVERIFY(std::any_of(matches.cbegin(), matches.cend(), [](const Plasma::QueryMatch &match) { 0186 return match.text() == QLatin1String("Konsole ServiceRunnerTest") && match.relevance() == 0.44; 0187 })); 0188 0189 // Multiple categories but at least one doesn't match 0190 context.setQuery(QStringLiteral("System KDE Office")); 0191 runner.match(context); 0192 matches = context.matches(); 0193 QVERIFY(std::none_of(matches.cbegin(), matches.cend(), [](const Plasma::QueryMatch &match) { 0194 return match.text() == QLatin1String("Konsole ServiceRunnerTest"); 0195 })); 0196 0197 // Query too short to match any category 0198 context.setQuery(QStringLiteral("Dumm")); 0199 runner.match(context); 0200 matches = context.matches(); 0201 QVERIFY(matches.isEmpty()); 0202 } 0203 0204 void ServiceRunnerTest::testJumpListActions() 0205 { 0206 ServiceRunner runner(this, KPluginMetaData(), QVariantList()); 0207 Plasma::RunnerContext context; 0208 0209 context.setQuery(QStringLiteral("open a new window")); // org.kde.konsole.desktop 0210 runner.match(context); 0211 auto matches = context.matches(); 0212 QVERIFY(std::any_of(matches.cbegin(), matches.cend(), [](const Plasma::QueryMatch &match) { 0213 return match.text() == QLatin1String("Open a New Window - Konsole ServiceRunnerTest") && match.relevance() == 0.65; 0214 })); 0215 0216 context.setQuery(QStringLiteral("new window")); 0217 runner.match(context); 0218 matches = context.matches(); 0219 QVERIFY(std::any_of(matches.cbegin(), matches.cend(), [](const Plasma::QueryMatch &match) { 0220 return match.text() == QLatin1String("Open a New Window - Konsole ServiceRunnerTest") && match.relevance() == 0.5; 0221 })); 0222 0223 context.setQuery(QStringLiteral("new windows")); 0224 runner.match(context); 0225 matches = context.matches(); 0226 QVERIFY(std::none_of(matches.cbegin(), matches.cend(), [](const Plasma::QueryMatch &match) { 0227 return match.text() == QLatin1String("Open a New Window - Konsole ServiceRunnerTest"); 0228 })); 0229 } 0230 0231 void ServiceRunnerTest::testINotifyUsage() 0232 { 0233 auto inotifyCount = []() -> uint { 0234 uint count = 0; 0235 const QDir procDir(QStringLiteral("/proc/%1/fd").arg(getpid())); 0236 for (const auto &fileInfo : procDir.entryInfoList()) { 0237 if (fileInfo.symLinkTarget().endsWith(QStringLiteral("anon_inode:inotify"))) { 0238 ++count; 0239 } 0240 } 0241 return count; 0242 }; 0243 0244 const uint originalCount = inotifyCount(); 0245 0246 // We'll run this in a new thread so KDirWatch would be led to create a new thread-local watch instance. 0247 // The expectation here is that this KDW instance is not persistently claiming an inotify instance. 0248 bool inotifyCountCool = false; 0249 auto thread = QThread::create([&] { 0250 ServiceRunner runner(nullptr, KPluginMetaData(), QVariantList()); 0251 Plasma::RunnerContext context; 0252 context.setQuery(QStringLiteral("settings")); 0253 0254 runner.match(context); 0255 0256 QCOMPARE(inotifyCount(), originalCount); 0257 inotifyCountCool = true; 0258 }); 0259 thread->start(); 0260 thread->wait(); 0261 thread->deleteLater(); 0262 0263 QVERIFY(inotifyCountCool); 0264 } 0265 0266 void ServiceRunnerTest::testSpecialArgs() 0267 { 0268 ServiceRunner runner(this, KPluginMetaData(), QVariantList()); 0269 Plasma::RunnerContext context; 0270 0271 context.setQuery(QStringLiteral("kpat")); 0272 runner.match(context); 0273 auto matches = context.matches(); 0274 QVERIFY(std::any_of(matches.cbegin(), matches.cend(), [](const Plasma::QueryMatch &match) { 0275 // Should have no -qwindowtitle at the end. Because we use DesktopExecParser, we have a "true" as an exec which is available on all systems 0276 return match.id().endsWith(QLatin1String("/bin/true")); 0277 })); 0278 } 0279 0280 void ServiceRunnerTest::testEnv() 0281 { 0282 ServiceRunner runner(this, KPluginMetaData(), QVariantList()); 0283 Plasma::RunnerContext context; 0284 0285 context.setQuery(QStringLiteral("audacity")); 0286 runner.match(context); 0287 auto matches = context.matches(); 0288 QVERIFY(std::any_of(matches.cbegin(), matches.cend(), [](const Plasma::QueryMatch &match) { 0289 // Because we use DesktopExecParser, we have a "true" as an exec which is available on all systems 0290 return match.id().endsWith(QLatin1String("/bin/true")); 0291 })); 0292 } 0293 0294 QTEST_MAIN(ServiceRunnerTest) 0295 0296 #include "servicerunnertest.moc"