File indexing completed on 2024-05-12 03:54:48

0001 /*
0002     SPDX-FileCopyrightText: 2014 Alex Richardson <arichardson.kde@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 #include <QJsonArray>
0008 #include <QJsonDocument>
0009 #include <QJsonParseError>
0010 #include <QPluginLoader>
0011 #include <QRegularExpression>
0012 #include <QStandardPaths>
0013 #include <QTest>
0014 
0015 #include "kcoreaddons_debug.h"
0016 #include <kaboutdata.h>
0017 #include <kpluginmetadata.h>
0018 
0019 #include <QLocale>
0020 #include <QLoggingCategory>
0021 
0022 class LibraryPathRestorer
0023 {
0024 public:
0025     explicit LibraryPathRestorer(const QStringList &paths)
0026         : mPaths(paths)
0027     {
0028     }
0029     ~LibraryPathRestorer()
0030     {
0031         QCoreApplication::setLibraryPaths(mPaths);
0032     }
0033 
0034 private:
0035     QStringList mPaths;
0036 };
0037 
0038 class KPluginMetaDataTest : public QObject
0039 {
0040     Q_OBJECT
0041 
0042     bool m_canMessage = false;
0043 
0044     Q_REQUIRED_RESULT bool doMessagesWork()
0045     {
0046         // Q_SKIP returns, but since this is called in multiple tests we want to return a bool so the caller can
0047         // return easily.
0048         auto internalCheck = [this] {
0049             // Make sure output is well formed AND generated. To that end we cannot run this test when any of the
0050             // overriding environment variables are set.
0051             // https://bugs.kde.org/show_bug.cgi?id=387006
0052             if (qEnvironmentVariableIsSet("QT_MESSAGE_PATTERN")) {
0053                 QSKIP("QT_MESSAGE_PATTERN prevents warning expectations from matching");
0054             }
0055             if (qEnvironmentVariableIsSet("QT_LOGGING_RULES")) {
0056                 QSKIP("QT_LOGGING_RULES prevents warning expectations from matching");
0057             }
0058             if (qEnvironmentVariableIsSet("QT_LOGGING_CONF")) {
0059                 QSKIP("QT_LOGGING_CONF prevents warning expectations from matching");
0060             }
0061             m_canMessage = true;
0062             // Ensure all frameworks output is enabled so the expectations can match.
0063             // qtlogging.ini may have disabled it but we can fix that because setFilterRules overrides the ini files.
0064             QLoggingCategory::setFilterRules(QStringLiteral("kf.*=true"));
0065         };
0066         internalCheck();
0067         return m_canMessage;
0068     }
0069 private Q_SLOTS:
0070 
0071     void testFromPluginLoader()
0072     {
0073         QString location;
0074         location = QPluginLoader(QStringLiteral("namespace/jsonplugin_cmake_macro")).fileName();
0075         QVERIFY2(!location.isEmpty(), "Could not find jsonplugin");
0076 
0077         // now that this file is translated we need to read it instead of hardcoding the contents here
0078         QString jsonLocation = QFINDTESTDATA("data/jsonplugin.json");
0079         QVERIFY2(!jsonLocation.isEmpty(), "Could not find jsonplugin.json");
0080         QFile jsonFile(jsonLocation);
0081         QVERIFY(jsonFile.open(QFile::ReadOnly));
0082         QJsonParseError e;
0083         QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonFile.readAll(), &e);
0084         QCOMPARE(e.error, QJsonParseError::NoError);
0085 
0086         location = QFileInfo(location).absoluteFilePath();
0087 
0088         KPluginMetaData fromQPluginLoader(QPluginLoader(QStringLiteral("namespace/jsonplugin_cmake_macro")));
0089         KPluginMetaData fromFullPath(location);
0090         KPluginMetaData fromRelativePath(QStringLiteral("namespace/jsonplugin_cmake_macro"));
0091         KPluginMetaData fromRawData(jsonDoc.object(), location);
0092 
0093         auto description = QStringLiteral("This is a plugin");
0094 
0095         QVERIFY(fromQPluginLoader.isValid());
0096         QCOMPARE(fromQPluginLoader.description(), description);
0097         QVERIFY(fromFullPath.isValid());
0098         QCOMPARE(fromFullPath.description(), description);
0099         QVERIFY(fromRelativePath.isValid());
0100         QCOMPARE(fromRelativePath.description(), description);
0101         QVERIFY(fromRawData.isValid());
0102         QCOMPARE(fromRawData.description(), description);
0103 
0104         // check operator==
0105         QCOMPARE(fromRawData, fromRawData);
0106         QCOMPARE(fromQPluginLoader, fromQPluginLoader);
0107         QCOMPARE(fromFullPath, fromFullPath);
0108 
0109         QCOMPARE(fromQPluginLoader, fromFullPath);
0110         QCOMPARE(fromQPluginLoader, fromRawData);
0111 
0112         QCOMPARE(fromFullPath, fromQPluginLoader);
0113         QCOMPARE(fromFullPath, fromRawData);
0114 
0115         QCOMPARE(fromRawData, fromQPluginLoader);
0116         QCOMPARE(fromRawData, fromFullPath);
0117 
0118         QVERIFY(!KPluginMetaData(QPluginLoader(QStringLiteral("doesnotexist"))).isValid());
0119         QVERIFY(!KPluginMetaData(QJsonObject(), QString()).isValid());
0120     }
0121 
0122     void testAllKeys()
0123     {
0124         QJsonParseError e;
0125         QJsonObject jo = QJsonDocument::fromJson(
0126                              "{\n"
0127                              " \"KPlugin\": {\n"
0128                              " \"Name\": \"Date and Time\",\n"
0129                              " \"Description\": \"Date and time by timezone\",\n"
0130                              " \"Icon\": \"preferences-system-time\",\n"
0131                              " \"Authors\": { \"Name\": \"Aaron Seigo\", \"Email\": \"aseigo@kde.org\" },\n"
0132                              " \"Translators\": { \"Name\": \"No One\", \"Email\": \"no.one@kde.org\" },\n"
0133                              " \"OtherContributors\": { \"Name\": \"No One\", \"Email\": \"no.one@kde.org\" },\n"
0134                              " \"Category\": \"Date and Time\",\n"
0135                              " \"EnabledByDefault\": \"true\",\n"
0136                              " \"ExtraInformation\": \"Something else\",\n"
0137                              " \"License\": \"LGPL\",\n"
0138                              " \"Copyright\": \"(c) Alex Richardson 2015\",\n"
0139                              " \"Id\": \"time\",\n"
0140                              " \"Version\": \"1.0\",\n"
0141                              " \"Website\": \"https://plasma.kde.org/\",\n"
0142                              " \"MimeTypes\": [ \"image/png\" ]\n"
0143                              " }\n}\n",
0144                              &e)
0145                              .object();
0146         QCOMPARE(e.error, QJsonParseError::NoError);
0147         KPluginMetaData m(jo, QString());
0148         QVERIFY(m.isValid());
0149         QCOMPARE(m.pluginId(), QStringLiteral("time"));
0150         QCOMPARE(m.name(), QStringLiteral("Date and Time"));
0151         QCOMPARE(m.description(), QStringLiteral("Date and time by timezone"));
0152         QCOMPARE(m.iconName(), QStringLiteral("preferences-system-time"));
0153         QCOMPARE(m.category(), QStringLiteral("Date and Time"));
0154         QCOMPARE(m.authors().size(), 1);
0155         QCOMPARE(m.authors().constFirst().name(), QStringLiteral("Aaron Seigo"));
0156         QCOMPARE(m.authors().constFirst().emailAddress(), QStringLiteral("aseigo@kde.org"));
0157         QCOMPARE(m.translators().size(), 1);
0158         QCOMPARE(m.translators().constFirst().name(), QStringLiteral("No One"));
0159         QCOMPARE(m.translators().constFirst().emailAddress(), QStringLiteral("no.one@kde.org"));
0160         QCOMPARE(m.otherContributors().size(), 1);
0161         QCOMPARE(m.otherContributors().constFirst().name(), QStringLiteral("No One"));
0162         QCOMPARE(m.otherContributors().constFirst().emailAddress(), QStringLiteral("no.one@kde.org"));
0163         QVERIFY(m.isEnabledByDefault());
0164         QCOMPARE(m.license(), QStringLiteral("LGPL"));
0165         QCOMPARE(m.copyrightText(), QStringLiteral("(c) Alex Richardson 2015"));
0166         QCOMPARE(m.version(), QStringLiteral("1.0"));
0167         QCOMPARE(m.website(), QStringLiteral("https://plasma.kde.org/"));
0168         QCOMPARE(m.mimeTypes(), QStringList(QStringLiteral("image/png")));
0169     }
0170 
0171     void testTranslations()
0172     {
0173         QJsonParseError e;
0174         QJsonObject jo = QJsonDocument::fromJson(
0175                              "{ \"KPlugin\": {\n"
0176                              "\"Name\": \"Name\",\n"
0177                              "\"Name[de]\": \"Name (de)\",\n"
0178                              "\"Name[de_DE]\": \"Name (de_DE)\",\n"
0179                              "\"Description\": \"Description\",\n"
0180                              "\"Description[de]\": \"Beschreibung (de)\",\n"
0181                              "\"Description[de_DE]\": \"Beschreibung (de_DE)\"\n"
0182                              "}\n}",
0183                              &e)
0184                              .object();
0185         KPluginMetaData m(jo, QString());
0186         QLocale::setDefault(QLocale::c());
0187         QCOMPARE(m.name(), QStringLiteral("Name"));
0188         QCOMPARE(m.description(), QStringLiteral("Description"));
0189 
0190         QLocale::setDefault(QLocale(QStringLiteral("de_DE")));
0191         QCOMPARE(m.name(), QStringLiteral("Name (de_DE)"));
0192         QCOMPARE(m.description(), QStringLiteral("Beschreibung (de_DE)"));
0193 
0194         QLocale::setDefault(QLocale(QStringLiteral("de_CH")));
0195         QCOMPARE(m.name(), QStringLiteral("Name (de)"));
0196         QCOMPARE(m.description(), QStringLiteral("Beschreibung (de)"));
0197 
0198         QLocale::setDefault(QLocale(QStringLiteral("fr_FR")));
0199         QCOMPARE(m.name(), QStringLiteral("Name"));
0200         QCOMPARE(m.description(), QStringLiteral("Description"));
0201     }
0202 
0203     void testReadStringList()
0204     {
0205         if (!doMessagesWork()) {
0206             return;
0207         }
0208         QJsonParseError e;
0209         QJsonObject jo = QJsonDocument::fromJson(
0210                              "{\n"
0211                              "\"String\": \"foo\",\n"
0212                              "\"OneArrayEntry\": [ \"foo\" ],\n"
0213                              "\"Bool\": true,\n" // make sure booleans are accepted
0214                              "\"QuotedBool\": \"true\",\n" // make sure booleans are accepted
0215                              "\"Number\": 12345,\n" // number should also work
0216                              "\"QuotedNumber\": \"12345\",\n" // number should also work
0217                              "\"EmptyArray\": [],\n"
0218                              "\"NumberArray\": [1, 2, 3],\n"
0219                              "\"BoolArray\": [true, false, true],\n"
0220                              "\"StringArray\": [\"foo\", \"bar\"],\n"
0221                              "\"Null\": null,\n" // should return empty list
0222                              "\"QuotedNull\": \"null\",\n" // this is okay, it is a string
0223                              "\"ArrayWithNull\": [ \"foo\", null, \"bar\"],\n" // TODO: null is converted to empty string, is this okay?
0224                              "\"Object\": { \"foo\": \"bar\" }\n" // should return empty list
0225                              "}",
0226                              &e)
0227                              .object();
0228         QCOMPARE(e.error, QJsonParseError::NoError);
0229         QTest::ignoreMessage(QtWarningMsg, QRegularExpression(QStringLiteral("Expected JSON property ")));
0230         KPluginMetaData data(jo, QStringLiteral("test"));
0231         QCOMPARE(data.value(QStringLiteral("String"), QStringList()), QStringList(QStringLiteral("foo")));
0232         QCOMPARE(data.value(QStringLiteral("OneArrayEntry"), QStringList()), QStringList(QStringLiteral("foo")));
0233         QCOMPARE(data.value(QStringLiteral("Bool"), QStringList()), QStringList(QStringLiteral("true")));
0234         QCOMPARE(data.value(QStringLiteral("QuotedBool"), QStringList()), QStringList(QStringLiteral("true")));
0235         QCOMPARE(data.value(QStringLiteral("Number"), QStringList()), QStringList(QStringLiteral("12345")));
0236         QCOMPARE(data.value(QStringLiteral("QuotedNumber"), QStringList()), QStringList(QStringLiteral("12345")));
0237         QCOMPARE(data.value(QStringLiteral("EmptyArray"), QStringList()), QStringList());
0238         QCOMPARE(data.value(QStringLiteral("NumberArray"), QStringList()), QStringList() << QStringLiteral("1") << QStringLiteral("2") << QStringLiteral("3"));
0239         QCOMPARE(data.value(QStringLiteral("BoolArray"), QStringList()),
0240                  QStringList() << QStringLiteral("true") << QStringLiteral("false") << QStringLiteral("true"));
0241         QCOMPARE(data.value(QStringLiteral("StringArray"), QStringList()), QStringList() << QStringLiteral("foo") << QStringLiteral("bar"));
0242         QCOMPARE(data.value(QStringLiteral("Null"), QStringList()), QStringList());
0243         QCOMPARE(data.value(QStringLiteral("QuotedNull"), QStringList()), QStringList(QStringLiteral("null")));
0244         QCOMPARE(data.value(QStringLiteral("ArrayWithNull"), QStringList()), QStringList() << QStringLiteral("foo") << QString() << QStringLiteral("bar"));
0245         QCOMPARE(data.value(QStringLiteral("Object"), QStringList()), QStringList());
0246     }
0247 
0248     void testJSONMetadata()
0249     {
0250         const QString inputPath = QFINDTESTDATA("data/testmetadata.json");
0251         KPluginMetaData md = KPluginMetaData::fromJsonFile(inputPath);
0252         QVERIFY(md.isValid());
0253         QCOMPARE(md.name(), QStringLiteral("Test"));
0254 
0255         QCOMPARE(md.value(QStringLiteral("X-Plasma-MainScript")), QStringLiteral("ui/main.qml"));
0256         QJsonArray expected;
0257         expected.append(QStringLiteral("Export"));
0258         QCOMPARE(md.rawData().value(QStringLiteral("X-Purpose-PluginTypes")).toArray(), expected);
0259         QCOMPARE(md.value(QStringLiteral("SomeInt"), 24), 42);
0260         QCOMPARE(md.value(QStringLiteral("SomeIntAsString"), 24), 42);
0261         QCOMPARE(md.value(QStringLiteral("SomeStringNotAInt"), 24), 24);
0262         QCOMPARE(md.value(QStringLiteral("DoesNotExist"), 24), 24);
0263 
0264         QVERIFY(md.value(QStringLiteral("SomeBool"), false));
0265         QVERIFY(!md.value(QStringLiteral("SomeBoolThatIsFalse"), true));
0266         QVERIFY(md.value(QStringLiteral("SomeBoolAsString"), false));
0267         QVERIFY(md.value(QStringLiteral("DoesNotExist"), true));
0268     }
0269 
0270     void testPathIsAbsolute_data()
0271     {
0272         QTest::addColumn<QString>("inputAbsolute");
0273         QTest::addColumn<QString>("pluginPath");
0274 
0275         // But for the .json based plugin both are the same.
0276         QTest::newRow("json") << QFINDTESTDATA("data/testmetadata.json") << QFINDTESTDATA("data/testmetadata.json");
0277         // And also for the library with embedded JSON metadata.
0278         QPluginLoader shlibLoader(QCoreApplication::applicationDirPath() + QStringLiteral("/namespace/jsonplugin_cmake_macro"));
0279         QVERIFY2(!shlibLoader.fileName().isEmpty(), "Could not find jsonplugin_cmake_macro");
0280         QString shlibPath = QFileInfo(shlibLoader.fileName()).absoluteFilePath();
0281         QTest::newRow("library") << shlibPath << shlibPath;
0282     }
0283 
0284     void testPathIsAbsolute()
0285     {
0286         // Test that the fileName() accessor always returns an absolute path if it was used.
0287         QFETCH(QString, inputAbsolute);
0288         QVERIFY2(QDir::isAbsolutePath(inputAbsolute), qPrintable(inputAbsolute));
0289         QFETCH(QString, pluginPath);
0290 
0291         const auto createMetaData = [](const QString &path) {
0292             if (path.endsWith(QLatin1String(".json"))) {
0293                 return KPluginMetaData::fromJsonFile(path);
0294             } else {
0295                 return KPluginMetaData(path);
0296             }
0297         };
0298 
0299         KPluginMetaData mdAbsolute = createMetaData(inputAbsolute);
0300         QVERIFY(mdAbsolute.isValid());
0301         QCOMPARE(mdAbsolute.fileName(), pluginPath);
0302 
0303         // All files that have been opened should be stored as absolute paths.
0304         QString inputRelative;
0305         if (QLibrary::isLibrary(inputAbsolute)) {
0306             // We have a plugin without namespace, with the code path below we would end up with
0307             // a path relative to the PWD, but we want to check a path relative to the plugin dir.
0308             // Because of that we simply use the baseName of the file.
0309             inputRelative = QStringLiteral("namespace/") + QFileInfo(inputAbsolute).baseName();
0310         } else {
0311             inputRelative = QDir::current().relativeFilePath(inputAbsolute);
0312         }
0313         QVERIFY2(QDir::isRelativePath(inputRelative), qPrintable(inputRelative));
0314         KPluginMetaData mdRelative = createMetaData(inputRelative);
0315         QVERIFY(mdRelative.isValid());
0316         QCOMPARE(mdRelative.fileName(), pluginPath);
0317 
0318         // Check that creating it with the parsed JSON object and a path keeps the path unchanged
0319         const QJsonObject json = mdAbsolute.rawData();
0320         QString pluginRelative = QDir::current().relativeFilePath(pluginPath);
0321         QVERIFY2(QDir::isRelativePath(pluginRelative), qPrintable(pluginRelative));
0322 
0323         // We should not be normalizing files that have not been openened, so both arguments should be unchanged.
0324         KPluginMetaData mdFromJson1(json, pluginRelative);
0325         KPluginMetaData mdFromJson2(json, inputRelative);
0326         QCOMPARE(mdFromJson1.fileName(), pluginRelative);
0327         QCOMPARE(mdFromJson2.fileName(), inputRelative);
0328     }
0329 
0330     void testFindPlugins()
0331     {
0332         auto sortPlugins = [](const KPluginMetaData &a, const KPluginMetaData &b) {
0333             return a.pluginId() < b.pluginId();
0334         };
0335         // it should find jsonplugin and jsonplugin2 since unversionedplugin does not have any meta data
0336         auto plugins = KPluginMetaData::findPlugins(QStringLiteral("namespace"));
0337         std::sort(plugins.begin(), plugins.end(), sortPlugins);
0338         QCOMPARE(plugins.size(), 2);
0339         QCOMPARE(plugins[0].pluginId(), QStringLiteral("jsonplugin_cmake_macro")); // ID is not the filename, it is set in the JSON metadata
0340         QCOMPARE(plugins[0].description(), QStringLiteral("This is a plugin"));
0341         QCOMPARE(plugins[1].pluginId(), QStringLiteral("qtplugin")); // ID is not the filename, it is set in the JSON metadata
0342 
0343         // filter accepts none
0344         plugins = KPluginMetaData::findPlugins(QStringLiteral("namespace"), [](const KPluginMetaData &) {
0345             return false;
0346         });
0347         std::sort(plugins.begin(), plugins.end(), sortPlugins);
0348         QCOMPARE(plugins.size(), 0);
0349 
0350         // filter accepts all
0351         plugins = KPluginMetaData::findPlugins(QStringLiteral("namespace"), [](const KPluginMetaData &) {
0352             return true;
0353         });
0354         std::sort(plugins.begin(), plugins.end(), sortPlugins);
0355         QCOMPARE(plugins.size(), 2);
0356 
0357         // mimetype filter. Only one match, jsonplugin2 is specific to text/html.
0358         auto supportTextPlain = [](const KPluginMetaData &metaData) {
0359             return metaData.supportsMimeType(QStringLiteral("text/plain"));
0360         };
0361         plugins = KPluginMetaData::findPlugins(QStringLiteral("namespace"), supportTextPlain);
0362         QCOMPARE(plugins.size(), 1);
0363         QCOMPARE(plugins[0].pluginId(), QStringLiteral("jsonplugin_cmake_macro"));
0364 
0365         // mimetype filter. Two matches, both support text/html, via inheritance.
0366         auto supportTextHtml = [](const KPluginMetaData &metaData) {
0367             return metaData.supportsMimeType(QStringLiteral("text/html"));
0368         };
0369         plugins = KPluginMetaData::findPlugins(QStringLiteral("namespace"), supportTextHtml);
0370         std::sort(plugins.begin(), plugins.end(), sortPlugins);
0371         QCOMPARE(plugins.size(), 2);
0372         QCOMPARE(plugins[0].pluginId(), QStringLiteral("jsonplugin_cmake_macro"));
0373         QCOMPARE(plugins[1].pluginId(), QStringLiteral("qtplugin"));
0374 
0375         // mimetype filter with invalid mimetype
0376         auto supportDoesNotExist = [](const KPluginMetaData &metaData) {
0377             return metaData.supportsMimeType(QStringLiteral("does/not/exist"));
0378         };
0379         plugins = KPluginMetaData::findPlugins(QStringLiteral("namespace"), supportDoesNotExist);
0380         QCOMPARE(plugins.size(), 0);
0381 
0382         // by plugin invalid id
0383         KPluginMetaData plugin = KPluginMetaData::findPluginById(QStringLiteral("namespace"), QStringLiteral("invalidid"));
0384         QVERIFY(!plugin.isValid());
0385 
0386         // absolute path, no filter
0387         plugins = KPluginMetaData::findPlugins(QCoreApplication::applicationDirPath() + QStringLiteral("/namespace"));
0388         std::sort(plugins.begin(), plugins.end(), sortPlugins);
0389         QCOMPARE(plugins.size(), 2);
0390         QCOMPARE(plugins[0].pluginId(), QStringLiteral("jsonplugin_cmake_macro"));
0391         QCOMPARE(plugins[1].pluginId(), QStringLiteral("qtplugin"));
0392 
0393         // This plugin has no explicit pluginId and will fall back to basename of file
0394         const KPluginMetaData validPlugin = KPluginMetaData::findPluginById(QStringLiteral("namespace"), QStringLiteral("jsonplugin_cmake_macro"));
0395         QVERIFY(validPlugin.isValid());
0396         QCOMPARE(plugins[0].pluginId(), QStringLiteral("jsonplugin_cmake_macro"));
0397     }
0398 
0399     void testStaticPlugins()
0400     {
0401         QCOMPARE(QPluginLoader::staticPlugins().count(), 0);
0402 
0403         const auto plugins = KPluginMetaData::findPlugins(QStringLiteral("staticnamespace"));
0404         QCOMPARE(plugins.count(), 1);
0405 
0406         QCOMPARE(plugins.first().description(), QStringLiteral("This is a plugin"));
0407         QCOMPARE(plugins.first().fileName(), QStringLiteral("staticnamespace/static_jsonplugin_cmake_macro"));
0408     }
0409 
0410     void testPluginsWithoutMetaData()
0411     {
0412         KPluginMetaData emptyMetaData(QStringLiteral("namespace/pluginwithoutmetadata"), KPluginMetaData::AllowEmptyMetaData);
0413         QVERIFY(emptyMetaData.isValid());
0414         QCOMPARE(emptyMetaData.pluginId(), QStringLiteral("pluginwithoutmetadata"));
0415 
0416         const auto plugins = KPluginMetaData::findPlugins(QStringLiteral("namespace"), {}, KPluginMetaData::AllowEmptyMetaData);
0417         QCOMPARE(plugins.count(), 3);
0418         for (auto plugin : plugins) {
0419             QVERIFY(plugin.isValid());
0420             if (plugin.pluginId() == QLatin1String("pluginwithoutmetadata")) {
0421                 QVERIFY(plugin.rawData().isEmpty());
0422             } else if (plugin.pluginId() == QLatin1String("jsonplugin_cmake_macro") || plugin.pluginId() == QLatin1String("qtplugin")) {
0423                 QVERIFY(!plugin.rawData().isEmpty());
0424             } else {
0425                 QVERIFY2(false, "should not be reachable");
0426             }
0427         }
0428         const auto pluginInvalid = KPluginMetaData::findPluginById(QStringLiteral("namespace"), QStringLiteral("pluginwithoutmetadata"));
0429         const auto pluginValid = KPluginMetaData::findPluginById(QStringLiteral("namespace"), //
0430                                                                  QStringLiteral("pluginwithoutmetadata"),
0431                                                                  KPluginMetaData::AllowEmptyMetaData);
0432         QVERIFY(!pluginInvalid.isValid());
0433         QVERIFY(pluginValid.isValid());
0434     }
0435 
0436     void testStaticPluginsWithoutMetadata()
0437     {
0438         QVERIFY(KPluginMetaData::findPlugins(QStringLiteral("staticnamespace3")).isEmpty());
0439         const auto plugins = KPluginMetaData::findPlugins(QStringLiteral("staticnamespace3"), {}, KPluginMetaData::AllowEmptyMetaData);
0440         QCOMPARE(plugins.count(), 1);
0441         QVERIFY(plugins.first().isValid());
0442         QCOMPARE(plugins.first().pluginId(), QStringLiteral("static_plugin_without_metadata"));
0443     }
0444 
0445     void testReverseDomainNotationPluginId()
0446     {
0447         KPluginMetaData data(QStringLiteral("org.kde.test"));
0448         QVERIFY(data.isValid());
0449         QCOMPARE(data.pluginId(), QStringLiteral("org.kde.test"));
0450     }
0451 
0452     // We can't use the plain pluginId as a classname, thus check if the replacement compiles and the original identifies it used for lookup
0453     void testReverseDomanNotationStaticPlugin()
0454     {
0455         KPluginMetaData data = KPluginMetaData::findPluginById(QStringLiteral("rdnstatic"), QStringLiteral("org.kde.test-staticplugin"));
0456         QVERIFY(data.isValid());
0457         QCOMPARE(data.pluginId(), QStringLiteral("org.kde.test-staticplugin"));
0458     }
0459 
0460     void testFindingPluginInAppDirFirst()
0461     {
0462         const QString originalPluginPath = QPluginLoader(QStringLiteral("namespace/jsonplugin_cmake_macro")).fileName();
0463         const QString pluginFileName = QFileInfo(originalPluginPath).fileName();
0464         const QString pluginNamespace = QStringLiteral("somepluginnamespace");
0465         const QString pluginAppDir = QCoreApplication::applicationDirPath() + QLatin1Char('/') + pluginNamespace;
0466         QDir(pluginAppDir).mkpath(QStringLiteral("."));
0467         const QString pluginAppPath = pluginAppDir + QLatin1Char('/') + pluginFileName;
0468         QFile::remove(pluginAppPath);
0469 
0470         QVERIFY(QFile::copy(originalPluginPath, pluginAppPath));
0471 
0472         QTemporaryDir temp;
0473         QVERIFY(temp.isValid());
0474         QDir dir(temp.path());
0475         QVERIFY(dir.mkdir(pluginNamespace));
0476         QVERIFY(dir.cd(pluginNamespace));
0477 
0478         const QString pluginInNamespacePath = dir.absoluteFilePath(pluginFileName);
0479         QVERIFY(QFile::copy(originalPluginPath, pluginInNamespacePath));
0480 
0481         LibraryPathRestorer restorer(QCoreApplication::libraryPaths());
0482         QCoreApplication::setLibraryPaths(QStringList() << temp.path());
0483 
0484         // Our plugin in the applicationDirPath should come first
0485         const QString relativePathWithNamespace = QStringLiteral("somepluginnamespace/jsonplugin_cmake_macro");
0486         KPluginMetaData data(relativePathWithNamespace);
0487         QVERIFY(data.isValid());
0488         QCOMPARE(data.fileName(), pluginAppPath);
0489 
0490         // The other one must be valid
0491         QVERIFY(KPluginMetaData(pluginInNamespacePath).isValid());
0492         // And after removing the plugin in the applicationDirPath, it should be found
0493         QVERIFY(QFile::remove(pluginAppPath));
0494         QCOMPARE(KPluginMetaData(relativePathWithNamespace).fileName(), pluginInNamespacePath);
0495     }
0496 
0497     void testMetaDataQDebugOperator()
0498     {
0499         const auto list = KPluginMetaData::findPlugins(QStringLiteral("namespace"));
0500         qDebug() << list.first();
0501         qDebug() << list;
0502         qDebug() << (QList<KPluginMetaData>() << list << list << list);
0503     }
0504     void benchmarkFindPlugins()
0505     {
0506         QSKIP("Skipped by default");
0507         int loopIterations = 10;
0508         QBENCHMARK {
0509             for (int i = 0; i < loopIterations; ++i) {
0510                 const auto plugins = KPluginMetaData::findPlugins(QStringLiteral("namespace"), {}, KPluginMetaData::AllowEmptyMetaData);
0511                 Q_UNUSED(plugins)
0512             }
0513         }
0514         QList<KPluginMetaData> plugins =
0515             KPluginMetaData::findPlugins(QStringLiteral("namespace"), {}, KPluginMetaData::AllowEmptyMetaData | KPluginMetaData::CacheMetaData);
0516         QBENCHMARK {
0517             for (int i = 0; i < loopIterations; ++i) {
0518                 plugins = KPluginMetaData::findPlugins(QStringLiteral("namespace"), {}, KPluginMetaData::AllowEmptyMetaData | KPluginMetaData::CacheMetaData);
0519             }
0520         }
0521     }
0522 };
0523 
0524 QTEST_MAIN(KPluginMetaDataTest)
0525 
0526 #include "kpluginmetadatatest.moc"