File indexing completed on 2024-04-28 11:44:22

0001 /*
0002     SPDX-FileCopyrightText: 2006 David Faure <faure@kde.org>
0003     SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-only
0006 */
0007 
0008 #include "kservicetest.h"
0009 
0010 #include "setupxdgdirs.h"
0011 
0012 #include <locale.h>
0013 
0014 #include <QTest>
0015 
0016 #include <../src/services/kserviceutil_p.h> // for KServiceUtilPrivate
0017 #include <KConfig>
0018 #include <KConfigGroup>
0019 #include <KDesktopFile>
0020 #include <kapplicationtrader.h>
0021 #include <kbuildsycoca_p.h>
0022 #include <ksycoca.h>
0023 
0024 #include <KPluginMetaData>
0025 #include <kplugininfo.h>
0026 #include <kservicegroup.h>
0027 #include <kservicetype.h>
0028 #include <kservicetypeprofile.h>
0029 #include <kservicetypetrader.h>
0030 
0031 #include <QFile>
0032 #include <QSignalSpy>
0033 #include <QStandardPaths>
0034 #include <QThread>
0035 
0036 #include <QDebug>
0037 #include <QLoggingCategory>
0038 #include <QMimeDatabase>
0039 
0040 QTEST_MAIN(KServiceTest)
0041 
0042 extern KSERVICE_EXPORT int ksycoca_ms_between_checks;
0043 
0044 static void eraseProfiles()
0045 {
0046     QString profilerc = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1String{"/profilerc"};
0047     if (!profilerc.isEmpty()) {
0048         QFile::remove(profilerc);
0049     }
0050 
0051     profilerc = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1String{"/servicetype_profilerc"};
0052     if (!profilerc.isEmpty()) {
0053         QFile::remove(profilerc);
0054     }
0055 }
0056 
0057 void KServiceTest::initTestCase()
0058 {
0059     // Set up a layer in the bin dir so ksycoca finds the KPluginInfo and Application servicetypes
0060     setupXdgDirs();
0061     QStandardPaths::setTestModeEnabled(true);
0062 
0063     QLoggingCategory::setFilterRules(QStringLiteral("*.debug=true"));
0064 
0065     // A non-C locale is necessary for some tests.
0066     // This locale must have the following properties:
0067     //   - some character other than dot as decimal separator
0068     // If it cannot be set, locale-dependent tests are skipped.
0069     setlocale(LC_ALL, "fr_FR.utf8");
0070     m_hasNonCLocale = (setlocale(LC_ALL, nullptr) == QByteArray("fr_FR.utf8"));
0071     if (!m_hasNonCLocale) {
0072         qDebug() << "Setting locale to fr_FR.utf8 failed";
0073     }
0074 
0075     eraseProfiles();
0076 
0077     if (!KSycoca::isAvailable()) {
0078         runKBuildSycoca();
0079     }
0080 
0081     // Create some fake services for the tests below, and ensure they are in ksycoca.
0082 
0083     bool mustUpdateKSycoca = false;
0084 
0085     // fakeservice: deleted and recreated by testDeletingService, don't use in other tests
0086     const QString fakeServiceDeleteMe =
0087         QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kservices5/fakeservice_deleteme.desktop");
0088     if (!QFile::exists(fakeServiceDeleteMe)) {
0089         mustUpdateKSycoca = true;
0090         createFakeService(QStringLiteral("fakeservice_deleteme.desktop"), QString());
0091     }
0092 
0093     // fakeservice: a plugin that implements FakePluginType
0094     const QString fakeService = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kservices5/fakeservice.desktop");
0095     if (!QFile::exists(fakeService)) {
0096         mustUpdateKSycoca = true;
0097         createFakeService(QStringLiteral("fakeservice.desktop"), QStringLiteral("FakePluginType"));
0098     }
0099 
0100     // fakepart: a readwrite part, like katepart
0101     const QString fakePart = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String{"/kservices5/fakepart.desktop"};
0102     if (!QFile::exists(fakePart)) {
0103         mustUpdateKSycoca = true;
0104         KDesktopFile file(fakePart);
0105         KConfigGroup group = file.desktopGroup();
0106         group.writeEntry("Name", "FakePart");
0107         group.writeEntry("Type", "Service");
0108         group.writeEntry("X-KDE-Library", "fakepart");
0109         group.writeEntry("X-KDE-Protocols", "http,ftp");
0110         group.writeEntry("X-KDE-ServiceTypes", "FakeBasePart,FakeDerivedPart");
0111         group.writeEntry("MimeType", "text/plain;text/html;");
0112         group.writeEntry("X-KDE-FormFactors", "tablet,handset");
0113     }
0114 
0115     const QString fakePart2 = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String{"/kservices5/fakepart2.desktop"};
0116     if (!QFile::exists(fakePart2)) {
0117         mustUpdateKSycoca = true;
0118         KDesktopFile file(fakePart2);
0119         KConfigGroup group = file.desktopGroup();
0120         group.writeEntry("Name", "FakePart2");
0121         group.writeEntry("Type", "Service");
0122         group.writeEntry("X-KDE-Library", "fakepart2");
0123         group.writeEntry("X-KDE-ServiceTypes", "FakeBasePart");
0124         group.writeEntry("MimeType", "text/plain;");
0125         group.writeEntry("X-KDE-TestList", QStringList() << QStringLiteral("item1") << QStringLiteral("item2"));
0126         group.writeEntry("X-KDE-FormFactors", "tablet,handset");
0127     }
0128 
0129     const QString preferredPart = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String{"/kservices5/preferredpart.desktop"};
0130     if (!QFile::exists(preferredPart)) {
0131         mustUpdateKSycoca = true;
0132         KDesktopFile file(preferredPart);
0133         KConfigGroup group = file.desktopGroup();
0134         group.writeEntry("Name", "PreferredPart");
0135         group.writeEntry("Type", "Service");
0136         group.writeEntry("X-KDE-Library", "preferredpart");
0137         group.writeEntry("X-KDE-ServiceTypes", "FakeBasePart");
0138         group.writeEntry("MimeType", "text/plain;");
0139     }
0140 
0141     const QString otherPart = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String{"/kservices5/otherpart.desktop"};
0142     if (!QFile::exists(otherPart)) {
0143         mustUpdateKSycoca = true;
0144         KDesktopFile file(otherPart);
0145         KConfigGroup group = file.desktopGroup();
0146         group.writeEntry("Name", "OtherPart");
0147         group.writeEntry("Type", "Service");
0148         group.writeEntry("X-KDE-Library", "otherpart");
0149         group.writeEntry("X-KDE-ServiceTypes", "FakeBasePart");
0150         group.writeEntry("MimeType", "text/plain;");
0151     }
0152 
0153     // faketextplugin: a ktexteditor plugin
0154     const QString fakeTextplugin = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String{"/kservices5/faketextplugin.desktop"};
0155     if (!QFile::exists(fakeTextplugin)) {
0156         mustUpdateKSycoca = true;
0157         KDesktopFile file(fakeTextplugin);
0158         KConfigGroup group = file.desktopGroup();
0159         group.writeEntry("Name", "FakeTextPlugin");
0160         group.writeEntry("Type", "Service");
0161         group.writeEntry("X-KDE-Library", "faketextplugin");
0162         group.writeEntry("X-KDE-ServiceTypes", "FakePluginType");
0163         group.writeEntry("MimeType", "text/plain;");
0164     }
0165 
0166     // fakeplugintype: a servicetype
0167     const QString fakePluginType =
0168         QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String{"/kservicetypes5/fakeplugintype.desktop"};
0169     if (!QFile::exists(fakePluginType)) {
0170         mustUpdateKSycoca = true;
0171         KDesktopFile file(fakePluginType);
0172         KConfigGroup group = file.desktopGroup();
0173         group.writeEntry("Comment", "Fake Text Plugin");
0174         group.writeEntry("Type", "ServiceType");
0175         group.writeEntry("X-KDE-ServiceType", "FakePluginType");
0176         file.group("PropertyDef::X-KDE-Version").writeEntry("Type", "double"); // like in ktexteditorplugin.desktop
0177         group.writeEntry("X-KDE-FormFactors", "tablet,handset");
0178     }
0179 
0180     // fakebasepart: a servicetype (like ReadOnlyPart)
0181     const QString fakeBasePart = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String{"/kservicetypes5/fakebasepart.desktop"};
0182     if (!QFile::exists(fakeBasePart)) {
0183         mustUpdateKSycoca = true;
0184         KDesktopFile file(fakeBasePart);
0185         KConfigGroup group = file.desktopGroup();
0186         group.writeEntry("Comment", "Fake Base Part");
0187         group.writeEntry("Type", "ServiceType");
0188         group.writeEntry("X-KDE-ServiceType", "FakeBasePart");
0189 
0190         KConfigGroup listGroup(&file, "PropertyDef::X-KDE-TestList");
0191         listGroup.writeEntry("Type", "QStringList");
0192     }
0193 
0194     // fakederivedpart: a servicetype deriving from FakeBasePart (like ReadWritePart derives from ReadOnlyPart)
0195     const QString fakeDerivedPart =
0196         QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String{"/kservicetypes5/fakederivedpart.desktop"};
0197     if (!QFile::exists(fakeDerivedPart)) {
0198         mustUpdateKSycoca = true;
0199         KDesktopFile file(fakeDerivedPart);
0200         KConfigGroup group = file.desktopGroup();
0201         group.writeEntry("Comment", "Fake Derived Part");
0202         group.writeEntry("Type", "ServiceType");
0203         group.writeEntry("X-KDE-ServiceType", "FakeDerivedPart");
0204         group.writeEntry("X-KDE-Derived", "FakeBasePart");
0205     }
0206 
0207     // fakekdedmodule
0208     const QString fakeKdedModule =
0209         QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String{"/kservicetypes5/fakekdedmodule.desktop"};
0210     if (!QFile::exists(fakeKdedModule)) {
0211         const QString src = QFINDTESTDATA("fakekdedmodule.desktop");
0212         QVERIFY(QFile::copy(src, fakeKdedModule));
0213         mustUpdateKSycoca = true;
0214     }
0215 
0216     // faketestapp.desktop
0217     const QString testApp = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) + QLatin1String("/org.kde.faketestapp.desktop");
0218     if (!QFile::exists(testApp)) {
0219         QVERIFY(QDir().mkpath(QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation)));
0220         const QString src = QFINDTESTDATA("org.kde.faketestapp.desktop");
0221         QVERIFY(!src.isEmpty());
0222         QVERIFY2(QFile::copy(src, testApp), qPrintable(testApp));
0223         qDebug() << "Created" << testApp;
0224         mustUpdateKSycoca = true;
0225     }
0226 
0227     // otherfakeapp.desktop
0228     const QString otherTestApp = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) + QLatin1String("/org.kde.otherfakeapp.desktop");
0229     if (!QFile::exists(otherTestApp)) {
0230         QVERIFY(QDir().mkpath(QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation)));
0231         const QString src = QFINDTESTDATA("org.kde.otherfakeapp.desktop");
0232         QVERIFY(!src.isEmpty());
0233         QVERIFY2(QFile::copy(src, otherTestApp), qPrintable(otherTestApp));
0234         qDebug() << "Created" << otherTestApp;
0235         mustUpdateKSycoca = true;
0236     }
0237 
0238     if (mustUpdateKSycoca) {
0239         // Update ksycoca in ~/.qttest after creating the above
0240         runKBuildSycoca(true);
0241     }
0242     QVERIFY(KServiceType::serviceType(QStringLiteral("FakePluginType")));
0243     QVERIFY(KServiceType::serviceType(QStringLiteral("FakeBasePart")));
0244     QVERIFY(KServiceType::serviceType(QStringLiteral("FakeDerivedPart")));
0245     QVERIFY(KService::serviceByDesktopName(QStringLiteral("org.kde.faketestapp")));
0246     QVERIFY(KService::serviceByDesktopName(QStringLiteral("org.kde.otherfakeapp")));
0247 }
0248 
0249 void KServiceTest::runKBuildSycoca(bool noincremental)
0250 {
0251 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 80)
0252     QSignalSpy spy(KSycoca::self(), qOverload<const QStringList &>(&KSycoca::databaseChanged));
0253 #else
0254     QSignalSpy spy(KSycoca::self(), &KSycoca::databaseChanged);
0255 #endif
0256 
0257     KBuildSycoca builder;
0258     QVERIFY(builder.recreate(!noincremental));
0259     if (spy.isEmpty()) {
0260         qDebug() << "waiting for signal";
0261         QVERIFY(spy.wait(10000));
0262         qDebug() << "got signal";
0263     }
0264 }
0265 
0266 void KServiceTest::cleanupTestCase()
0267 {
0268     // If I want the konqueror unit tests to work, then I better not have a non-working part
0269     // as the preferred part for text/plain...
0270     const QStringList services = QStringList() << QStringLiteral("fakeservice.desktop") << QStringLiteral("fakepart.desktop")
0271                                                << QStringLiteral("faketextplugin.desktop") << QStringLiteral("fakeservice_querymustrebuild.desktop");
0272     for (const QString &service : services) {
0273         const QString fakeService = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kservices5/") + service;
0274         QFile::remove(fakeService);
0275     }
0276     const QStringList serviceTypes = QStringList() << QStringLiteral("fakeplugintype.desktop");
0277     for (const QString &serviceType : serviceTypes) {
0278         const QString fakeServiceType = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kservicetypes5/") + serviceType;
0279         // QFile::remove(fakeServiceType);
0280     }
0281     KBuildSycoca builder;
0282     builder.recreate();
0283 }
0284 
0285 void KServiceTest::testByName()
0286 {
0287     if (!KSycoca::isAvailable()) {
0288         QSKIP("ksycoca not available");
0289     }
0290 
0291     KServiceType::Ptr s0 = KServiceType::serviceType(QStringLiteral("FakeBasePart"));
0292     QVERIFY2(s0, "KServiceType::serviceType(\"FakeBasePart\") failed!");
0293     QCOMPARE(s0->name(), QStringLiteral("FakeBasePart"));
0294 
0295     KService::Ptr myService = KService::serviceByDesktopPath(QStringLiteral("fakepart.desktop"));
0296     QVERIFY(myService);
0297     QCOMPARE(myService->name(), QStringLiteral("FakePart"));
0298 }
0299 
0300 void KServiceTest::testConstructorFullPath()
0301 {
0302     // Requirement: text/html must be a known mimetype
0303     QVERIFY(QMimeDatabase().mimeTypeForName(QStringLiteral("text/html")).isValid());
0304     const QString fakePart = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String{"/kservices5/fakepart.desktop"};
0305     QVERIFY(QFile::exists(fakePart));
0306     KService service(fakePart);
0307     QVERIFY(service.isValid());
0308     QCOMPARE(service.mimeTypes(), (QStringList{QStringLiteral("text/plain"), QStringLiteral("text/html")}));
0309 }
0310 
0311 void KServiceTest::testConstructorKDesktopFileFullPath()
0312 {
0313     const QString fakePart = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String{"/kservices5/fakepart.desktop"};
0314     QVERIFY(QFile::exists(fakePart));
0315     KDesktopFile desktopFile(fakePart);
0316     KService service(&desktopFile);
0317     QVERIFY(service.isValid());
0318     QCOMPARE(service.mimeTypes(), (QStringList{QStringLiteral("text/plain"), QStringLiteral("text/html")}));
0319 }
0320 
0321 void KServiceTest::testConstructorKDesktopFile() // as happens inside kbuildsycoca.cpp
0322 {
0323     KDesktopFile desktopFile(QStandardPaths::GenericDataLocation, QStringLiteral("kservices5/fakepart.desktop"));
0324     QCOMPARE(KService(&desktopFile, QStringLiteral("kservices5/fakepart.desktop")).mimeTypes(),
0325              (QStringList{QStringLiteral("text/plain"), QStringLiteral("text/html")}));
0326 }
0327 
0328 void KServiceTest::testCopyConstructor()
0329 {
0330     const QString fakePart = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String{"/kservices5/fakepart.desktop"};
0331     QVERIFY(QFile::exists(fakePart));
0332     KDesktopFile desktopFile(fakePart);
0333     // KRun needs to make a copy of a KService that will go out of scope, let's test that here.
0334     KService::Ptr service;
0335     {
0336         KService origService(&desktopFile);
0337         service = new KService(origService);
0338     }
0339     QVERIFY(service->isValid());
0340     QCOMPARE(service->mimeTypes(), (QStringList{QStringLiteral("text/plain"), QStringLiteral("text/html")}));
0341 }
0342 
0343 void KServiceTest::testCopyInvalidService()
0344 {
0345     KService::Ptr service;
0346     {
0347         KService origService{QString()}; // this still sets a d_ptr, so no problem;
0348         QVERIFY(!origService.isValid());
0349         service = new KService(origService);
0350     }
0351     QVERIFY(!service->isValid());
0352 }
0353 
0354 void KServiceTest::testProperty()
0355 {
0356     ksycoca_ms_between_checks = 0;
0357 
0358     // Let's try creating a desktop file and ensuring it's noticed by the timestamp check
0359     QTest::qWait(1000);
0360     const QString fakeCookie = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String{"/kservices5/kded/fakekcookiejar.desktop"};
0361     if (!QFile::exists(fakeCookie)) {
0362         KDesktopFile file(fakeCookie);
0363         KConfigGroup group = file.desktopGroup();
0364         group.writeEntry("Name", "OtherPart");
0365         group.writeEntry("Type", "Service");
0366         group.writeEntry("X-KDE-ServiceTypes", "FakeKDEDModule");
0367         group.writeEntry("X-KDE-Library", "kcookiejar");
0368         group.writeEntry("X-KDE-Kded-autoload", "false");
0369         group.writeEntry("X-KDE-Kded-load-on-demand", "true");
0370         qDebug() << "created" << fakeCookie;
0371     }
0372 
0373     KService::Ptr kdedkcookiejar = KService::serviceByDesktopPath(QStringLiteral("kded/fakekcookiejar.desktop"));
0374     QVERIFY(kdedkcookiejar);
0375     QCOMPARE(kdedkcookiejar->entryPath(), QStringLiteral("kded/fakekcookiejar.desktop"));
0376 
0377     QCOMPARE(kdedkcookiejar->property(QStringLiteral("ServiceTypes")).toStringList().join(QLatin1Char(',')), QStringLiteral("FakeKDEDModule"));
0378     QCOMPARE(kdedkcookiejar->property(QStringLiteral("X-KDE-Kded-autoload")).toBool(), false);
0379     QCOMPARE(kdedkcookiejar->property(QStringLiteral("X-KDE-Kded-load-on-demand")).toBool(), true);
0380     QVERIFY(!kdedkcookiejar->property(QStringLiteral("Name")).toString().isEmpty());
0381     QVERIFY(!kdedkcookiejar->property(QStringLiteral("Name[fr]"), QMetaType::QString).isValid());
0382 
0383     // TODO: for this we must install a servicetype desktop file...
0384     // KService::Ptr kjavaappletviewer = KService::serviceByDesktopPath("kjavaappletviewer.desktop");
0385     // QVERIFY(kjavaappletviewer);
0386     // QCOMPARE(kjavaappletviewer->property("X-KDE-BrowserView-PluginsInfo").toString(), QString("kjava/pluginsinfo"));
0387 
0388     // Test property("X-KDE-Protocols"), which triggers the KServiceReadProperty code.
0389     KService::Ptr fakePart = KService::serviceByDesktopPath(QStringLiteral("fakepart.desktop"));
0390     QVERIFY(fakePart); // see initTestCase; it should be found.
0391     QVERIFY(fakePart->propertyNames().contains(QLatin1String("X-KDE-Protocols")));
0392     QCOMPARE(fakePart->mimeTypes(),
0393              QStringList() << QStringLiteral("text/plain") << QStringLiteral("text/html")); // okular relies on subclasses being kept here
0394     const QStringList protocols = fakePart->property(QStringLiteral("X-KDE-Protocols")).toStringList();
0395     QCOMPARE(protocols, QStringList() << QStringLiteral("http") << QStringLiteral("ftp"));
0396 
0397     // Restore value
0398     ksycoca_ms_between_checks = 1500;
0399 }
0400 
0401 void KServiceTest::testAllServiceTypes()
0402 {
0403     if (!KSycoca::isAvailable()) {
0404         QSKIP("ksycoca not available");
0405     }
0406 
0407     const KServiceType::List allServiceTypes = KServiceType::allServiceTypes();
0408 
0409     // A bit of checking on the allServiceTypes list itself
0410     for (const KServiceType::Ptr &servtype : allServiceTypes) {
0411         const QString name = servtype->name();
0412         QVERIFY(!name.isEmpty());
0413         QVERIFY(servtype->sycocaType() == KST_KServiceType);
0414     }
0415 }
0416 
0417 void KServiceTest::testAllServices()
0418 {
0419     if (!KSycoca::isAvailable()) {
0420         QSKIP("ksycoca not available");
0421     }
0422     const KService::List lst = KService::allServices();
0423     QVERIFY(!lst.isEmpty());
0424     bool foundTestApp = false;
0425 
0426     for (const KService::Ptr &service : lst) {
0427         QVERIFY(service->isType(KST_KService));
0428 
0429         const QString name = service->name();
0430         const QString entryPath = service->entryPath();
0431         if (entryPath.contains(QLatin1String{"fake"})) {
0432             qDebug() << name << "entryPath=" << entryPath << "menuId=" << service->menuId();
0433         }
0434         QVERIFY(!name.isEmpty());
0435         QVERIFY(!entryPath.isEmpty());
0436 
0437         KService::Ptr lookedupService = KService::serviceByDesktopPath(entryPath);
0438         QVERIFY(lookedupService); // not null
0439         QCOMPARE(lookedupService->entryPath(), entryPath);
0440 
0441         if (service->isApplication()) {
0442             const QString menuId = service->menuId();
0443             if (menuId.isEmpty()) {
0444                 qWarning("%s has an empty menuId!", qPrintable(entryPath));
0445             } else if (menuId == QLatin1String{"org.kde.faketestapp.desktop"}) {
0446                 foundTestApp = true;
0447             }
0448             QVERIFY(!menuId.isEmpty());
0449             lookedupService = KService::serviceByMenuId(menuId);
0450             QVERIFY(lookedupService); // not null
0451             QCOMPARE(lookedupService->menuId(), menuId);
0452         }
0453     }
0454     QVERIFY(foundTestApp);
0455 }
0456 
0457 // Helper method for all the trader tests
0458 static bool offerListHasService(const KService::List &offers, const QString &entryPath)
0459 {
0460     bool found = false;
0461     for (const auto &servicePtr : offers) {
0462         if (servicePtr->entryPath() == entryPath) {
0463             if (found) { // should be there only once
0464                 qWarning("ERROR: %s was found twice in the list", qPrintable(entryPath));
0465                 return false; // make test fail
0466             }
0467             found = true;
0468         }
0469     }
0470     return found;
0471 }
0472 
0473 void KServiceTest::testDBUSStartupType()
0474 {
0475     if (!KSycoca::isAvailable()) {
0476         QSKIP("ksycoca not available");
0477     }
0478     KService::Ptr testapp = KService::serviceByDesktopName(QStringLiteral("org.kde.faketestapp"));
0479     QVERIFY(testapp);
0480     QCOMPARE(testapp->menuId(), QStringLiteral("org.kde.faketestapp.desktop"));
0481     // qDebug() << testapp->entryPath();
0482 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 102)
0483     QCOMPARE(int(testapp->dbusStartupType()), int(KService::DBusUnique));
0484 #endif
0485 }
0486 
0487 void KServiceTest::testByStorageId()
0488 {
0489     if (!KSycoca::isAvailable()) {
0490         QSKIP("ksycoca not available");
0491     }
0492     QVERIFY(!QStandardPaths::locate(QStandardPaths::ApplicationsLocation, QStringLiteral("org.kde.faketestapp.desktop")).isEmpty());
0493     QVERIFY(KService::serviceByMenuId(QStringLiteral("org.kde.faketestapp.desktop")));
0494     QVERIFY(!KService::serviceByMenuId(QStringLiteral("org.kde.faketestapp"))); // doesn't work, extension mandatory
0495     QVERIFY(!KService::serviceByMenuId(QStringLiteral("faketestapp.desktop"))); // doesn't work, full filename mandatory
0496     QVERIFY(KService::serviceByStorageId(QStringLiteral("org.kde.faketestapp.desktop")));
0497     QVERIFY(KService::serviceByStorageId(QStringLiteral("org.kde.faketestapp")));
0498 
0499     QVERIFY(KService::serviceByDesktopName(QStringLiteral("org.kde.faketestapp")));
0500     QCOMPARE(KService::serviceByDesktopName(QStringLiteral("org.kde.faketestapp"))->menuId(), QStringLiteral("org.kde.faketestapp.desktop"));
0501 }
0502 
0503 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 90)
0504 void KServiceTest::testServiceTypeTraderForReadOnlyPart()
0505 {
0506     if (!KSycoca::isAvailable()) {
0507         QSKIP("ksycoca not available");
0508     }
0509 
0510     // Querying trader for services associated with FakeBasePart
0511     KService::List offers = KServiceTypeTrader::self()->query(QStringLiteral("FakeBasePart"));
0512     QVERIFY(offers.count() > 0);
0513 
0514     if (!offerListHasService(offers, QStringLiteral("fakepart.desktop")) //
0515         || !offerListHasService(offers, QStringLiteral("fakepart2.desktop")) //
0516         || !offerListHasService(offers, QStringLiteral("otherpart.desktop")) //
0517         || !offerListHasService(offers, QStringLiteral("preferredpart.desktop"))) {
0518         for (KService::Ptr service : std::as_const(offers)) {
0519             qDebug("%s %s", qPrintable(service->name()), qPrintable(service->entryPath()));
0520         }
0521     }
0522 
0523     m_firstOffer = offers[0]->entryPath();
0524 
0525     QVERIFY(offerListHasService(offers, QStringLiteral("fakepart.desktop")));
0526     QVERIFY(offerListHasService(offers, QStringLiteral("fakepart2.desktop")));
0527     QVERIFY(offerListHasService(offers, QStringLiteral("otherpart.desktop")));
0528     QVERIFY(offerListHasService(offers, QStringLiteral("preferredpart.desktop")));
0529 
0530     // Check ordering according to InitialPreference
0531     int lastPreference = -1;
0532 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 67)
0533     bool lastAllowedAsDefault = true;
0534 #endif
0535     for (KService::Ptr service : std::as_const(offers)) {
0536         const int preference = service->initialPreference(); // ## might be wrong if we use per-servicetype preferences...
0537         // qDebug( "%s has preference %d, allowAsDefault=%d", qPrintable( path ), preference, service->allowAsDefault() );
0538 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 67)
0539         if (lastAllowedAsDefault && !service->allowAsDefault()) {
0540             // first "not allowed as default" offer
0541             lastAllowedAsDefault = false;
0542             lastPreference = -1; // restart
0543         }
0544 #endif
0545         if (lastPreference != -1) {
0546             QVERIFY(preference <= lastPreference);
0547         }
0548         lastPreference = preference;
0549     }
0550 
0551 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 90)
0552     // Now look for any FakePluginType
0553     offers = KServiceTypeTrader::self()->query(QStringLiteral("FakePluginType"));
0554     QVERIFY(offerListHasService(offers, QStringLiteral("fakeservice.desktop")));
0555     QVERIFY(offerListHasService(offers, QStringLiteral("faketextplugin.desktop")));
0556 #endif
0557 }
0558 
0559 void KServiceTest::testTraderConstraints()
0560 {
0561     if (!KSycoca::isAvailable()) {
0562         QSKIP("ksycoca not available");
0563     }
0564 
0565     KService::List offers;
0566 
0567     // Baseline: no constraints
0568     offers = KServiceTypeTrader::self()->query(QStringLiteral("FakePluginType"));
0569     QCOMPARE(offers.count(), 2);
0570     QVERIFY(offerListHasService(offers, QStringLiteral("faketextplugin.desktop")));
0571     QVERIFY(offerListHasService(offers, QStringLiteral("fakeservice.desktop")));
0572 
0573     // String-based constraint
0574     offers = KServiceTypeTrader::self()->query(QStringLiteral("FakePluginType"), QStringLiteral("Library == 'faketextplugin'"));
0575     QCOMPARE(offers.count(), 1);
0576     QVERIFY(offerListHasService(offers, QStringLiteral("faketextplugin.desktop")));
0577 
0578     // Match case insensitive
0579     offers = KServiceTypeTrader::self()->query(QStringLiteral("FakePluginType"), QStringLiteral("Library =~ 'fAkEteXtpLuGin'"));
0580     QCOMPARE(offers.count(), 1);
0581     QVERIFY(offerListHasService(offers, QStringLiteral("faketextplugin.desktop")));
0582 
0583     // "contains"
0584     offers = KServiceTypeTrader::self()->query(QStringLiteral("FakePluginType"),
0585                                                QStringLiteral("'textplugin' ~ Library")); // note: "is contained in", not "contains"...
0586     QCOMPARE(offers.count(), 1);
0587     QVERIFY(offerListHasService(offers, QStringLiteral("faketextplugin.desktop")));
0588 
0589     // "contains" case insensitive
0590     offers = KServiceTypeTrader::self()->query(QStringLiteral("FakePluginType"),
0591                                                QStringLiteral("'teXtPluGin' ~~ Library")); // note: "is contained in", not "contains"...
0592     QCOMPARE(offers.count(), 1);
0593     QVERIFY(offerListHasService(offers, QStringLiteral("faketextplugin.desktop")));
0594 
0595     // sub-sequence case sensitive
0596     offers = KServiceTypeTrader::self()->query(QStringLiteral("FakePluginType"), QStringLiteral("'txtlug' subseq Library"));
0597     QCOMPARE(offers.count(), 1);
0598     QVERIFY(offerListHasService(offers, QStringLiteral("faketextplugin.desktop")));
0599 
0600     // sub-sequence case insensitive
0601     offers = KServiceTypeTrader::self()->query(QStringLiteral("FakePluginType"), QStringLiteral("'tXtLuG' ~subseq Library"));
0602     QCOMPARE(offers.count(), 1);
0603     QVERIFY(offerListHasService(offers, QStringLiteral("faketextplugin.desktop")));
0604 
0605     if (m_hasNonCLocale) {
0606         // Test float parsing, must use dot as decimal separator independent of locale.
0607         offers = KServiceTypeTrader::self()->query(QStringLiteral("FakePluginType"), QStringLiteral("([X-KDE-Version] > 4.559) and ([X-KDE-Version] < 4.561)"));
0608         QCOMPARE(offers.count(), 1);
0609         QVERIFY(offerListHasService(offers, QStringLiteral("fakeservice.desktop")));
0610     }
0611 
0612     // A test with an invalid query, to test for memleaks
0613     offers = KServiceTypeTrader::self()->query(QStringLiteral("FakePluginType"), QStringLiteral("A == B OR C == D AND OR Foo == 'Parse Error'"));
0614     QVERIFY(offers.isEmpty());
0615 }
0616 #endif
0617 
0618 void KServiceTest::testSubseqConstraints()
0619 {
0620     auto test = [](const char *pattern, const char *text, bool sensitive) {
0621         return KApplicationTrader::isSubsequence(QString::fromLatin1(pattern), QString::fromLatin1(text), sensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
0622     };
0623 
0624     // Case Sensitive
0625     QVERIFY2(!test("", "", 1), "both empty");
0626     QVERIFY2(!test("", "something", 1), "empty pattern");
0627     QVERIFY2(!test("something", "", 1), "empty text");
0628     QVERIFY2(test("lngfile", "somereallylongfile", 1), "match ending");
0629     QVERIFY2(test("somelong", "somereallylongfile", 1), "match beginning");
0630     QVERIFY2(test("reallylong", "somereallylongfile", 1), "match middle");
0631     QVERIFY2(test("across", "a 23 c @#! r o01 o 5 s_s", 1), "match across");
0632     QVERIFY2(!test("nocigar", "soclosebutnociga", 1), "close but no match");
0633     QVERIFY2(!test("god", "dog", 1), "incorrect letter order");
0634     QVERIFY2(!test("mismatch", "mIsMaTcH", 1), "case sensitive mismatch");
0635 
0636     // Case insensitive
0637     QVERIFY2(test("mismatch", "mIsMaTcH", 0), "case insensitive match");
0638     QVERIFY2(test("tryhards", "Try Your Hardest", 0), "uppercase text");
0639     QVERIFY2(test("TRYHARDS", "try your hardest", 0), "uppercase pattern");
0640 }
0641 
0642 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 104)
0643 void KServiceTest::testHasServiceType1() // with services constructed with a full path (rare)
0644 {
0645     QString fakepartPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kservices5/fakepart.desktop"));
0646     QVERIFY(!fakepartPath.isEmpty());
0647     KService fakepart(fakepartPath);
0648     QVERIFY(fakepart.hasServiceType(QStringLiteral("FakeBasePart")));
0649     QVERIFY(fakepart.hasServiceType(QStringLiteral("FakeDerivedPart")));
0650     QCOMPARE(fakepart.mimeTypes(), QStringList() << QStringLiteral("text/plain") << QStringLiteral("text/html"));
0651 
0652     QString faketextPluginPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kservices5/faketextplugin.desktop"));
0653     QVERIFY(!faketextPluginPath.isEmpty());
0654     KService faketextPlugin(faketextPluginPath);
0655     QVERIFY(faketextPlugin.hasServiceType(QStringLiteral("FakePluginType")));
0656     QVERIFY(!faketextPlugin.hasServiceType(QStringLiteral("FakeBasePart")));
0657 }
0658 
0659 void KServiceTest::testHasServiceType2() // with services coming from ksycoca
0660 {
0661     KService::Ptr fakepart = KService::serviceByDesktopPath(QStringLiteral("fakepart.desktop"));
0662     QVERIFY(fakepart);
0663     QVERIFY(fakepart->hasServiceType(QStringLiteral("FakeBasePart")));
0664     QVERIFY(fakepart->hasServiceType(QStringLiteral("FakeDerivedPart")));
0665     QCOMPARE(fakepart->mimeTypes(), QStringList() << QStringLiteral("text/plain") << QStringLiteral("text/html"));
0666 
0667     KService::Ptr faketextPlugin = KService::serviceByDesktopPath(QStringLiteral("faketextplugin.desktop"));
0668     QVERIFY(faketextPlugin);
0669     QVERIFY(faketextPlugin->hasServiceType(QStringLiteral("FakePluginType")));
0670     QVERIFY(!faketextPlugin->hasServiceType(QStringLiteral("FakeBasePart")));
0671 }
0672 #endif
0673 
0674 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 66)
0675 void KServiceTest::testWriteServiceTypeProfile()
0676 {
0677     const QString serviceType = QStringLiteral("FakeBasePart");
0678     KService::List services;
0679     KService::List disabledServices;
0680     services.append(KService::serviceByDesktopPath(QStringLiteral("preferredpart.desktop")));
0681     services.append(KService::serviceByDesktopPath(QStringLiteral("fakepart.desktop")));
0682     disabledServices.append(KService::serviceByDesktopPath(QStringLiteral("fakepart2.desktop")));
0683 
0684     for (const KService::Ptr &serv : std::as_const(services)) {
0685         QVERIFY(serv);
0686     }
0687     for (const KService::Ptr &serv : std::as_const(disabledServices)) {
0688         QVERIFY(serv);
0689     }
0690 
0691     KServiceTypeProfile::writeServiceTypeProfile(serviceType, services, disabledServices);
0692 
0693     // Check that the file got written
0694     QString profilerc = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1String{"/servicetype_profilerc"};
0695     QVERIFY(!profilerc.isEmpty());
0696     QVERIFY(QFile::exists(profilerc));
0697 
0698     KService::List offers = KServiceTypeTrader::self()->query(serviceType);
0699     QVERIFY(offers.count() > 0); // not empty
0700 
0701     // foreach( KService::Ptr service, offers )
0702     //    qDebug( "%s %s", qPrintable( service->name() ), qPrintable( service->entryPath() ) );
0703 
0704     QVERIFY(offers.count() >= 2);
0705     QCOMPARE(offers[0]->entryPath(), QStringLiteral("preferredpart.desktop"));
0706     QCOMPARE(offers[1]->entryPath(), QStringLiteral("fakepart.desktop"));
0707     QVERIFY(offerListHasService(offers, QStringLiteral("otherpart.desktop"))); // should still be somewhere in there
0708     QVERIFY(!offerListHasService(offers, QStringLiteral("fakepart2.desktop"))); // it got disabled above
0709 }
0710 #endif
0711 
0712 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 90)
0713 void KServiceTest::testDefaultOffers()
0714 {
0715     // Now that we have a user-profile, let's see if defaultOffers indeed gives us the default ordering.
0716     const QString serviceType = QStringLiteral("FakeBasePart");
0717     KService::List offers = KServiceTypeTrader::self()->defaultOffers(serviceType);
0718     QVERIFY(offers.count() > 0); // not empty
0719     QVERIFY(offerListHasService(offers, QStringLiteral("fakepart2.desktop"))); // it's here even though it's disabled in the profile
0720     QVERIFY(offerListHasService(offers, QStringLiteral("otherpart.desktop")));
0721     if (m_firstOffer.isEmpty()) {
0722         QSKIP("testServiceTypeTraderForReadOnlyPart not run");
0723     }
0724     QCOMPARE(offers[0]->entryPath(), m_firstOffer);
0725 }
0726 #endif
0727 
0728 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 66)
0729 void KServiceTest::testDeleteServiceTypeProfile()
0730 {
0731     const QString serviceType = QStringLiteral("FakeBasePart");
0732     KServiceTypeProfile::deleteServiceTypeProfile(serviceType);
0733 
0734     KService::List offers = KServiceTypeTrader::self()->query(serviceType);
0735     QVERIFY(offers.count() > 0); // not empty
0736     QVERIFY(offerListHasService(offers, QStringLiteral("fakepart2.desktop"))); // it's back
0737 
0738     if (m_firstOffer.isEmpty()) {
0739         QSKIP("testServiceTypeTraderForReadOnlyPart not run");
0740     }
0741     QCOMPARE(offers[0]->entryPath(), m_firstOffer);
0742 }
0743 #endif
0744 
0745 void KServiceTest::testActionsAndDataStream()
0746 {
0747     KService::Ptr service = KService::serviceByStorageId(QStringLiteral("org.kde.faketestapp.desktop"));
0748     QVERIFY(service);
0749     QVERIFY(!service->property(QStringLiteral("Name[fr]"), QMetaType::QString).isValid());
0750     const QList<KServiceAction> actions = service->actions();
0751     QCOMPARE(actions.count(), 2); // NewWindow, NewTab
0752     const KServiceAction newTabAction = actions.at(1);
0753     QCOMPARE(newTabAction.name(), QStringLiteral("NewTab"));
0754     QCOMPARE(newTabAction.exec(), QStringLiteral("konsole --new-tab"));
0755     QCOMPARE(newTabAction.icon(), QStringLiteral("tab-new"));
0756     QCOMPARE(newTabAction.noDisplay(), false);
0757     QVERIFY(!newTabAction.isSeparator());
0758     QCOMPARE(newTabAction.service()->name(), service->name());
0759 }
0760 
0761 void KServiceTest::testServiceGroups()
0762 {
0763     KServiceGroup::Ptr root = KServiceGroup::root();
0764     QVERIFY(root);
0765     qDebug() << root->groupEntries().count();
0766 
0767     KServiceGroup::Ptr group = root;
0768     QVERIFY(group);
0769     const KServiceGroup::List list = group->entries(true, // sorted
0770                                                     true, // exclude no display entries,
0771                                                     false, // allow separators
0772                                                     true); // sort by generic name
0773 
0774     qDebug() << list.count();
0775     for (KServiceGroup::SPtr s : list) {
0776         qDebug() << s->name() << s->entryPath();
0777     }
0778 
0779     // No unit test here yet, but at least this can be valgrinded for errors.
0780 }
0781 
0782 void KServiceTest::testDeletingService()
0783 {
0784     // workaround unexplained inotify issue (in CI only...)
0785     QTest::qWait(1000);
0786 
0787     const QString serviceName = QStringLiteral("fakeservice_deleteme.desktop");
0788     KService::Ptr fakeService = KService::serviceByDesktopPath(serviceName);
0789     QVERIFY(fakeService); // see initTestCase; it should be found.
0790 
0791     // Test deleting a service
0792     const QString servPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kservices5/") + serviceName;
0793     QVERIFY(QFile::exists(servPath));
0794     QFile::remove(servPath);
0795     runKBuildSycoca();
0796     ksycoca_ms_between_checks = 0; // need it to check the ksycoca mtime
0797     QVERIFY(!KService::serviceByDesktopPath(serviceName)); // not in ksycoca anymore
0798 
0799     // Restore value
0800     ksycoca_ms_between_checks = 1500;
0801 
0802     QVERIFY(fakeService); // the whole point of refcounting is that this KService instance is still valid.
0803     QVERIFY(!QFile::exists(servPath));
0804 
0805     // Recreate it, for future tests
0806     createFakeService(serviceName, QString());
0807     QVERIFY(QFile::exists(servPath));
0808     qDebug() << "executing kbuildsycoca (2)";
0809 
0810     runKBuildSycoca();
0811 
0812     if (QThread::currentThread() != QCoreApplication::instance()->thread()) {
0813         m_sycocaUpdateDone.ref();
0814     }
0815 }
0816 
0817 void KServiceTest::createFakeService(const QString &filename, const QString &serviceType)
0818 {
0819     const QString fakeService = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kservices5/") + filename;
0820     KDesktopFile file(fakeService);
0821     KConfigGroup group = file.desktopGroup();
0822     group.writeEntry("Name", "FakePlugin");
0823     group.writeEntry("Type", "Service");
0824     group.writeEntry("X-KDE-Library", "fakeservice");
0825     group.writeEntry("X-KDE-Version", "4.56");
0826     group.writeEntry("ServiceTypes", serviceType);
0827     group.writeEntry("MimeType", "text/plain;");
0828 }
0829 
0830 #include <QFutureSynchronizer>
0831 #include <QThreadPool>
0832 #include <QtConcurrentRun>
0833 
0834 // Testing for concurrent access to ksycoca from multiple threads
0835 // It's especially interesting to run this test as ./kservicetest testThreads
0836 // so that even the ksycoca initialization is happening from N threads at the same time.
0837 // Use valgrind --tool=helgrind to see the race conditions.
0838 
0839 void KServiceTest::testReaderThreads()
0840 {
0841     QThreadPool::globalInstance()->setMaxThreadCount(10);
0842     QFutureSynchronizer<void> sync;
0843 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0844     sync.addFuture(QtConcurrent::run(&KServiceTest::testAllServices, this));
0845     sync.addFuture(QtConcurrent::run(&KServiceTest::testAllServices, this));
0846     sync.addFuture(QtConcurrent::run(&KServiceTest::testAllServices, this));
0847     sync.addFuture(QtConcurrent::run(&KServiceTest::testHasServiceType1, this));
0848     sync.addFuture(QtConcurrent::run(&KServiceTest::testAllServices, this));
0849     sync.addFuture(QtConcurrent::run(&KServiceTest::testAllServices, this));
0850 #else
0851     sync.addFuture(QtConcurrent::run(this, &KServiceTest::testAllServices));
0852     sync.addFuture(QtConcurrent::run(this, &KServiceTest::testAllServices));
0853     sync.addFuture(QtConcurrent::run(this, &KServiceTest::testAllServices));
0854 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 104)
0855     sync.addFuture(QtConcurrent::run(this, &KServiceTest::testHasServiceType1));
0856 #endif
0857     sync.addFuture(QtConcurrent::run(this, &KServiceTest::testAllServices));
0858     sync.addFuture(QtConcurrent::run(this, &KServiceTest::testAllServices));
0859 #endif
0860     sync.waitForFinished();
0861     QThreadPool::globalInstance()->setMaxThreadCount(1); // delete those threads
0862 }
0863 
0864 void KServiceTest::testThreads()
0865 {
0866     QThreadPool::globalInstance()->setMaxThreadCount(10);
0867     QFutureSynchronizer<void> sync;
0868 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0869     sync.addFuture(QtConcurrent::run(&KServiceTest::testAllServices, this));
0870     sync.addFuture(QtConcurrent::run(&KServiceTest::testHasServiceType1, this));
0871     sync.addFuture(QtConcurrent::run(&KServiceTest::testDeletingService, this));
0872 #else
0873     sync.addFuture(QtConcurrent::run(this, &KServiceTest::testAllServices));
0874 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 104)
0875     sync.addFuture(QtConcurrent::run(this, &KServiceTest::testHasServiceType1));
0876 #endif
0877     sync.addFuture(QtConcurrent::run(this, &KServiceTest::testDeletingService));
0878 
0879 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 90)
0880     sync.addFuture(QtConcurrent::run(this, &KServiceTest::testTraderConstraints));
0881 #endif
0882 #endif // QT_VERSION
0883 
0884     // process events (DBus, inotify...), until we get all expected signals
0885     QTRY_COMPARE_WITH_TIMEOUT(m_sycocaUpdateDone.loadRelaxed(), 1, 15000); // not using a bool, just to silence helgrind
0886     qDebug() << "Joining all threads";
0887     sync.waitForFinished();
0888 }
0889 
0890 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 86)
0891 void KServiceTest::testOperatorKPluginName()
0892 {
0893     QT_WARNING_PUSH
0894     QT_WARNING_DISABLE_DEPRECATED
0895     KService fservice(QFINDTESTDATA("fakeplugin.desktop"));
0896     KPluginName fname(fservice);
0897     QVERIFY(fname.isValid());
0898     QCOMPARE(fname.name(), QStringLiteral("fakeplugin"));
0899     KPluginLoader fplugin(fservice);
0900     QVERIFY(fplugin.factory());
0901 
0902     // make sure constness doesn't break anything
0903     const KService const_fservice(QFINDTESTDATA("fakeplugin.desktop"));
0904     KPluginName const_fname(const_fservice);
0905     QVERIFY(const_fname.isValid());
0906     QCOMPARE(const_fname.name(), QStringLiteral("fakeplugin"));
0907     KPluginLoader const_fplugin(const_fservice);
0908     QVERIFY(const_fplugin.factory());
0909 
0910     KService nservice(QFINDTESTDATA("noplugin.desktop"));
0911     KPluginName nname(nservice);
0912     QVERIFY(!nname.isValid());
0913     QVERIFY2(nname.name().isEmpty(), qPrintable(nname.name()));
0914     QVERIFY(!nname.errorString().isEmpty());
0915     KPluginLoader nplugin(nservice);
0916     QVERIFY(!nplugin.factory());
0917 
0918     KService iservice(QStringLiteral("idonotexist.desktop"));
0919     KPluginName iname(iservice);
0920     QVERIFY(!iname.isValid());
0921     QVERIFY2(iname.name().isEmpty(), qPrintable(iname.name()));
0922     QVERIFY(!iname.errorString().isEmpty());
0923     KPluginLoader iplugin(iservice);
0924     QVERIFY(!iplugin.factory());
0925     QT_WARNING_POP
0926 }
0927 #endif
0928 
0929 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 90)
0930 void KServiceTest::testKPluginInfoQuery()
0931 {
0932     KPluginInfo info(KPluginMetaData::fromDesktopFile(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)
0933                                                       + QLatin1String{"/kservices5/fakepart2.desktop"}));
0934 
0935     QCOMPARE(info.property(QStringLiteral("X-KDE-TestList")).toStringList().size(), 2);
0936 }
0937 #endif
0938 
0939 void KServiceTest::testCompleteBaseName()
0940 {
0941     QCOMPARE(KServiceUtilPrivate::completeBaseName(QStringLiteral("/home/x/.qttest/share/kservices5/fakepart2.desktop")), QStringLiteral("fakepart2"));
0942     // dots in filename before .desktop extension:
0943     QCOMPARE(KServiceUtilPrivate::completeBaseName(QStringLiteral("/home/x/.qttest/share/kservices5/org.kde.fakeapp.desktop")),
0944              QStringLiteral("org.kde.fakeapp"));
0945 }
0946 
0947 void KServiceTest::testEntryPathToName()
0948 {
0949     QCOMPARE(KService(QStringLiteral("c.desktop")).name(), QStringLiteral("c"));
0950     QCOMPARE(KService(QStringLiteral("a.b.c.desktop")).name(), QStringLiteral("a.b.c")); // dots in filename before .desktop extension
0951     QCOMPARE(KService(QStringLiteral("/hallo/a.b.c.desktop")).name(), QStringLiteral("a.b.c"));
0952 }
0953 
0954 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 0)
0955 void KServiceTest::testKPluginMetaData()
0956 {
0957     const QString fakePart = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String{"/kservices5/fakepart.desktop"};
0958     KPluginMetaData md = KPluginMetaData::fromDesktopFile(fakePart);
0959     KService::Ptr service(new KService(fakePart));
0960     KPluginInfo info(service);
0961     auto info_md = info.toMetaData();
0962     QCOMPARE(info_md.formFactors(), md.formFactors());
0963 }
0964 #endif
0965 
0966 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 90)
0967 void KServiceTest::testTraderQueryMustRebuildSycoca()
0968 {
0969     QVERIFY(!KServiceTypeProfile::hasProfile(QStringLiteral("FakeBasePart")));
0970     QTest::qWait(1000);
0971     createFakeService(QStringLiteral("fakeservice_querymustrebuild.desktop"), QString()); // just to touch the dir
0972     KService::List offers = KServiceTypeTrader::self()->query(QStringLiteral("FakeBasePart"));
0973     QVERIFY(offers.count() > 0);
0974 }
0975 #endif
0976 
0977 void KServiceTest::testAliasFor()
0978 {
0979     if (!KSycoca::isAvailable()) {
0980         QSKIP("ksycoca not available");
0981     }
0982     KService::Ptr testapp = KService::serviceByDesktopName(QStringLiteral("org.kde.faketestapp"));
0983     QVERIFY(testapp);
0984     QCOMPARE(testapp->aliasFor(), QStringLiteral("org.kde.okular"));
0985 }
0986 
0987 void KServiceTest::testMimeType()
0988 {
0989     if (!KSycoca::isAvailable()) {
0990         QSKIP("ksycoca not available");
0991     }
0992 
0993     KService::Ptr testapp = KService::serviceByDesktopName(QStringLiteral("org.kde.otherfakeapp"));
0994     QVERIFY(testapp);
0995     QCOMPARE(testapp->mimeTypes(), {QStringLiteral("application/pdf")});
0996 }
0997 
0998 void KServiceTest::testProtocols()
0999 {
1000     if (!KSycoca::isAvailable()) {
1001         QSKIP("ksycoca not available");
1002     }
1003 
1004     KService::Ptr testapp = KService::serviceByDesktopName(QStringLiteral("org.kde.otherfakeapp"));
1005     QVERIFY(testapp);
1006     QStringList expectedProtocols{QStringLiteral("http"), QStringLiteral("tel")};
1007     QCOMPARE(testapp->supportedProtocols(), expectedProtocols);
1008 }
1009 
1010 void KServiceTest::testServiceActionService()
1011 {
1012     if (!KSycoca::isAvailable()) {
1013         QSKIP("ksycoca not available");
1014     }
1015 
1016     const QString filePath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("applications/org.kde.faketestapp.desktop"));
1017     QVERIFY(QFile::exists(filePath));
1018     KService service(filePath);
1019     QVERIFY(service.isValid());
1020 
1021     const KServiceAction action = service.actions().first();
1022     QCOMPARE(action.service()->property(QStringLiteral("DBusActivatable"), QMetaType::Bool).toBool(), true);
1023     QCOMPARE(action.service()->actions().size(), 2);
1024 }
1025 
1026 #include "moc_kservicetest.cpp"