/* SPDX-FileCopyrightText: 2014 Alex Richardson SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #include #include #include #include #include #include #include #include "kcoreaddons_debug.h" #include #include #include #include class LibraryPathRestorer { public: explicit LibraryPathRestorer(const QStringList &paths) : mPaths(paths) { } ~LibraryPathRestorer() { QCoreApplication::setLibraryPaths(mPaths); } private: QStringList mPaths; }; class KPluginMetaDataTest : public QObject { Q_OBJECT bool m_canMessage = false; Q_REQUIRED_RESULT bool doMessagesWork() { // Q_SKIP returns, but since this is called in multiple tests we want to return a bool so the caller can // return easily. auto internalCheck = [this] { // Make sure output is well formed AND generated. To that end we cannot run this test when any of the // overriding environment variables are set. // https://bugs.kde.org/show_bug.cgi?id=387006 if (qEnvironmentVariableIsSet("QT_MESSAGE_PATTERN")) { QSKIP("QT_MESSAGE_PATTERN prevents warning expectations from matching"); } if (qEnvironmentVariableIsSet("QT_LOGGING_RULES")) { QSKIP("QT_LOGGING_RULES prevents warning expectations from matching"); } if (qEnvironmentVariableIsSet("QT_LOGGING_CONF")) { QSKIP("QT_LOGGING_CONF prevents warning expectations from matching"); } m_canMessage = true; // Ensure all frameworks output is enabled so the expectations can match. // qtlogging.ini may have disabled it but we can fix that because setFilterRules overrides the ini files. QLoggingCategory::setFilterRules(QStringLiteral("kf.*=true")); }; internalCheck(); return m_canMessage; } private Q_SLOTS: void testFromPluginLoader() { #if !defined(QT_SHARED) QSKIP("Dynamic plugin loading not supported with a static Qt build"); #endif QString location; location = QPluginLoader(QStringLiteral("namespace/jsonplugin_cmake_macro")).fileName(); QVERIFY2(!location.isEmpty(), "Could not find jsonplugin"); // now that this file is translated we need to read it instead of hardcoding the contents here QString jsonLocation = QFINDTESTDATA("data/jsonplugin.json"); QVERIFY2(!jsonLocation.isEmpty(), "Could not find jsonplugin.json"); QFile jsonFile(jsonLocation); QVERIFY(jsonFile.open(QFile::ReadOnly)); QJsonParseError e; QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonFile.readAll(), &e); QCOMPARE(e.error, QJsonParseError::NoError); location = QFileInfo(location).absoluteFilePath(); KPluginMetaData fromQPluginLoader(QPluginLoader(QStringLiteral("namespace/jsonplugin_cmake_macro"))); KPluginMetaData fromFullPath(location); KPluginMetaData fromRelativePath(QStringLiteral("namespace/jsonplugin_cmake_macro")); KPluginMetaData fromRawData(jsonDoc.object(), location); auto description = QStringLiteral("This is a plugin"); QVERIFY(fromQPluginLoader.isValid()); QCOMPARE(fromQPluginLoader.description(), description); QVERIFY(fromFullPath.isValid()); QCOMPARE(fromFullPath.description(), description); QVERIFY(fromRelativePath.isValid()); QCOMPARE(fromRelativePath.description(), description); QVERIFY(fromRawData.isValid()); QCOMPARE(fromRawData.description(), description); // check operator== QCOMPARE(fromRawData, fromRawData); QCOMPARE(fromQPluginLoader, fromQPluginLoader); QCOMPARE(fromFullPath, fromFullPath); QCOMPARE(fromQPluginLoader, fromFullPath); QCOMPARE(fromQPluginLoader, fromRawData); QCOMPARE(fromFullPath, fromQPluginLoader); QCOMPARE(fromFullPath, fromRawData); QCOMPARE(fromRawData, fromQPluginLoader); QCOMPARE(fromRawData, fromFullPath); QVERIFY(!KPluginMetaData(QPluginLoader(QStringLiteral("doesnotexist"))).isValid()); QVERIFY(!KPluginMetaData(QJsonObject(), QString()).isValid()); } void testAllKeys() { QJsonParseError e; QJsonObject jo = QJsonDocument::fromJson( "{\n" " \"KPlugin\": {\n" " \"Name\": \"Date and Time\",\n" " \"Description\": \"Date and time by timezone\",\n" " \"Icon\": \"preferences-system-time\",\n" " \"Authors\": { \"Name\": \"Aaron Seigo\", \"Email\": \"aseigo@kde.org\" },\n" " \"Translators\": { \"Name\": \"No One\", \"Email\": \"no.one@kde.org\" },\n" " \"OtherContributors\": { \"Name\": \"No One\", \"Email\": \"no.one@kde.org\" },\n" " \"Category\": \"Date and Time\",\n" " \"EnabledByDefault\": \"true\",\n" " \"ExtraInformation\": \"Something else\",\n" " \"License\": \"LGPL\",\n" " \"Copyright\": \"(c) Alex Richardson 2015\",\n" " \"Id\": \"time\",\n" " \"Version\": \"1.0\",\n" " \"Website\": \"https://plasma.kde.org/\",\n" " \"MimeTypes\": [ \"image/png\" ]\n" " }\n}\n", &e) .object(); QCOMPARE(e.error, QJsonParseError::NoError); KPluginMetaData m(jo, QString()); QVERIFY(m.isValid()); QCOMPARE(m.pluginId(), QStringLiteral("time")); QCOMPARE(m.name(), QStringLiteral("Date and Time")); QCOMPARE(m.description(), QStringLiteral("Date and time by timezone")); QCOMPARE(m.iconName(), QStringLiteral("preferences-system-time")); QCOMPARE(m.category(), QStringLiteral("Date and Time")); QCOMPARE(m.authors().size(), 1); QCOMPARE(m.authors().constFirst().name(), QStringLiteral("Aaron Seigo")); QCOMPARE(m.authors().constFirst().emailAddress(), QStringLiteral("aseigo@kde.org")); QCOMPARE(m.translators().size(), 1); QCOMPARE(m.translators().constFirst().name(), QStringLiteral("No One")); QCOMPARE(m.translators().constFirst().emailAddress(), QStringLiteral("no.one@kde.org")); QCOMPARE(m.otherContributors().size(), 1); QCOMPARE(m.otherContributors().constFirst().name(), QStringLiteral("No One")); QCOMPARE(m.otherContributors().constFirst().emailAddress(), QStringLiteral("no.one@kde.org")); QVERIFY(m.isEnabledByDefault()); QCOMPARE(m.license(), QStringLiteral("LGPL")); QCOMPARE(m.copyrightText(), QStringLiteral("(c) Alex Richardson 2015")); QCOMPARE(m.version(), QStringLiteral("1.0")); QCOMPARE(m.website(), QStringLiteral("https://plasma.kde.org/")); QCOMPARE(m.mimeTypes(), QStringList(QStringLiteral("image/png"))); } void testTranslations() { QJsonParseError e; QJsonObject jo = QJsonDocument::fromJson( "{ \"KPlugin\": {\n" "\"Name\": \"Name\",\n" "\"Name[de]\": \"Name (de)\",\n" "\"Name[de_DE]\": \"Name (de_DE)\",\n" "\"Description\": \"Description\",\n" "\"Description[de]\": \"Beschreibung (de)\",\n" "\"Description[de_DE]\": \"Beschreibung (de_DE)\"\n" "}\n}", &e) .object(); KPluginMetaData m(jo, QString()); QLocale::setDefault(QLocale::c()); QCOMPARE(m.name(), QStringLiteral("Name")); QCOMPARE(m.description(), QStringLiteral("Description")); QLocale::setDefault(QLocale(QStringLiteral("de_DE"))); QCOMPARE(m.name(), QStringLiteral("Name (de_DE)")); QCOMPARE(m.description(), QStringLiteral("Beschreibung (de_DE)")); QLocale::setDefault(QLocale(QStringLiteral("de_CH"))); QCOMPARE(m.name(), QStringLiteral("Name (de)")); QCOMPARE(m.description(), QStringLiteral("Beschreibung (de)")); QLocale::setDefault(QLocale(QStringLiteral("fr_FR"))); QCOMPARE(m.name(), QStringLiteral("Name")); QCOMPARE(m.description(), QStringLiteral("Description")); } void testReadStringList() { if (!doMessagesWork()) { return; } QJsonParseError e; QJsonObject jo = QJsonDocument::fromJson( "{\n" "\"String\": \"foo\",\n" "\"OneArrayEntry\": [ \"foo\" ],\n" "\"Bool\": true,\n" // make sure booleans are accepted "\"QuotedBool\": \"true\",\n" // make sure booleans are accepted "\"Number\": 12345,\n" // number should also work "\"QuotedNumber\": \"12345\",\n" // number should also work "\"EmptyArray\": [],\n" "\"NumberArray\": [1, 2, 3],\n" "\"BoolArray\": [true, false, true],\n" "\"StringArray\": [\"foo\", \"bar\"],\n" "\"Null\": null,\n" // should return empty list "\"QuotedNull\": \"null\",\n" // this is okay, it is a string "\"ArrayWithNull\": [ \"foo\", null, \"bar\"],\n" // TODO: null is converted to empty string, is this okay? "\"Object\": { \"foo\": \"bar\" }\n" // should return empty list "}", &e) .object(); QCOMPARE(e.error, QJsonParseError::NoError); QTest::ignoreMessage(QtWarningMsg, QRegularExpression(QStringLiteral("Expected JSON property "))); KPluginMetaData data(jo, QStringLiteral("test")); QCOMPARE(data.value(QStringLiteral("String"), QStringList()), QStringList(QStringLiteral("foo"))); QCOMPARE(data.value(QStringLiteral("OneArrayEntry"), QStringList()), QStringList(QStringLiteral("foo"))); QCOMPARE(data.value(QStringLiteral("Bool"), QStringList()), QStringList(QStringLiteral("true"))); QCOMPARE(data.value(QStringLiteral("QuotedBool"), QStringList()), QStringList(QStringLiteral("true"))); QCOMPARE(data.value(QStringLiteral("Number"), QStringList()), QStringList(QStringLiteral("12345"))); QCOMPARE(data.value(QStringLiteral("QuotedNumber"), QStringList()), QStringList(QStringLiteral("12345"))); QCOMPARE(data.value(QStringLiteral("EmptyArray"), QStringList()), QStringList()); QCOMPARE(data.value(QStringLiteral("NumberArray"), QStringList()), QStringList() << QStringLiteral("1") << QStringLiteral("2") << QStringLiteral("3")); QCOMPARE(data.value(QStringLiteral("BoolArray"), QStringList()), QStringList() << QStringLiteral("true") << QStringLiteral("false") << QStringLiteral("true")); QCOMPARE(data.value(QStringLiteral("StringArray"), QStringList()), QStringList() << QStringLiteral("foo") << QStringLiteral("bar")); QCOMPARE(data.value(QStringLiteral("Null"), QStringList()), QStringList()); QCOMPARE(data.value(QStringLiteral("QuotedNull"), QStringList()), QStringList(QStringLiteral("null"))); QCOMPARE(data.value(QStringLiteral("ArrayWithNull"), QStringList()), QStringList() << QStringLiteral("foo") << QString() << QStringLiteral("bar")); QCOMPARE(data.value(QStringLiteral("Object"), QStringList()), QStringList()); } void testJSONMetadata() { const QString inputPath = QFINDTESTDATA("data/testmetadata.json"); KPluginMetaData md = KPluginMetaData::fromJsonFile(inputPath); QVERIFY(md.isValid()); QCOMPARE(md.name(), QStringLiteral("Test")); QCOMPARE(md.value(QStringLiteral("X-Plasma-MainScript")), QStringLiteral("ui/main.qml")); QJsonArray expected; expected.append(QStringLiteral("Export")); QCOMPARE(md.rawData().value(QStringLiteral("X-Purpose-PluginTypes")).toArray(), expected); QCOMPARE(md.value(QStringLiteral("SomeInt"), 24), 42); QCOMPARE(md.value(QStringLiteral("SomeIntAsString"), 24), 42); QCOMPARE(md.value(QStringLiteral("SomeStringNotAInt"), 24), 24); QCOMPARE(md.value(QStringLiteral("DoesNotExist"), 24), 24); QVERIFY(md.value(QStringLiteral("SomeBool"), false)); QVERIFY(!md.value(QStringLiteral("SomeBoolThatIsFalse"), true)); QVERIFY(md.value(QStringLiteral("SomeBoolAsString"), false)); QVERIFY(md.value(QStringLiteral("DoesNotExist"), true)); } void testPathIsAbsolute_data() { #if !defined(QT_SHARED) QSKIP("Dynamic plugin loading not supported with a static Qt build"); #endif QTest::addColumn("inputAbsolute"); QTest::addColumn("pluginPath"); // But for the .json based plugin both are the same. QTest::newRow("json") << QFINDTESTDATA("data/testmetadata.json") << QFINDTESTDATA("data/testmetadata.json"); // And also for the library with embedded JSON metadata. QPluginLoader shlibLoader(QCoreApplication::applicationDirPath() + QStringLiteral("/namespace/jsonplugin_cmake_macro")); QVERIFY2(!shlibLoader.fileName().isEmpty(), "Could not find jsonplugin_cmake_macro"); QString shlibPath = QFileInfo(shlibLoader.fileName()).absoluteFilePath(); QTest::newRow("library") << shlibPath << shlibPath; } void testPathIsAbsolute() { // Test that the fileName() accessor always returns an absolute path if it was used. QFETCH(QString, inputAbsolute); QVERIFY2(QDir::isAbsolutePath(inputAbsolute), qPrintable(inputAbsolute)); QFETCH(QString, pluginPath); const auto createMetaData = [](const QString &path) { if (path.endsWith(QLatin1String(".json"))) { return KPluginMetaData::fromJsonFile(path); } else { return KPluginMetaData(path); } }; KPluginMetaData mdAbsolute = createMetaData(inputAbsolute); QVERIFY(mdAbsolute.isValid()); QCOMPARE(mdAbsolute.fileName(), pluginPath); // All files that have been opened should be stored as absolute paths. QString inputRelative; if (QLibrary::isLibrary(inputAbsolute)) { // We have a plugin without namespace, with the code path below we would end up with // a path relative to the PWD, but we want to check a path relative to the plugin dir. // Because of that we simply use the baseName of the file. inputRelative = QStringLiteral("namespace/") + QFileInfo(inputAbsolute).baseName(); } else { inputRelative = QDir::current().relativeFilePath(inputAbsolute); } QVERIFY2(QDir::isRelativePath(inputRelative), qPrintable(inputRelative)); KPluginMetaData mdRelative = createMetaData(inputRelative); QVERIFY(mdRelative.isValid()); QCOMPARE(mdRelative.fileName(), pluginPath); // Check that creating it with the parsed JSON object and a path keeps the path unchanged const QJsonObject json = mdAbsolute.rawData(); QString pluginRelative = QDir::current().relativeFilePath(pluginPath); QVERIFY2(QDir::isRelativePath(pluginRelative), qPrintable(pluginRelative)); // We should not be normalizing files that have not been openened, so both arguments should be unchanged. KPluginMetaData mdFromJson1(json, pluginRelative); KPluginMetaData mdFromJson2(json, inputRelative); QCOMPARE(mdFromJson1.fileName(), pluginRelative); QCOMPARE(mdFromJson2.fileName(), inputRelative); } void testFindPlugins() { #if !defined(QT_SHARED) QSKIP("Dynamic plugin loading not supported with a static Qt build"); #endif auto sortPlugins = [](const KPluginMetaData &a, const KPluginMetaData &b) { return a.pluginId() < b.pluginId(); }; // it should find jsonplugin and jsonplugin2 since unversionedplugin does not have any meta data auto plugins = KPluginMetaData::findPlugins(QStringLiteral("namespace")); std::sort(plugins.begin(), plugins.end(), sortPlugins); QCOMPARE(plugins.size(), 2); QCOMPARE(plugins[0].pluginId(), QStringLiteral("jsonplugin_cmake_macro")); // ID is not the filename, it is set in the JSON metadata QCOMPARE(plugins[0].description(), QStringLiteral("This is a plugin")); QCOMPARE(plugins[1].pluginId(), QStringLiteral("qtplugin")); // ID is not the filename, it is set in the JSON metadata // filter accepts none plugins = KPluginMetaData::findPlugins(QStringLiteral("namespace"), [](const KPluginMetaData &) { return false; }); std::sort(plugins.begin(), plugins.end(), sortPlugins); QCOMPARE(plugins.size(), 0); // filter accepts all plugins = KPluginMetaData::findPlugins(QStringLiteral("namespace"), [](const KPluginMetaData &) { return true; }); std::sort(plugins.begin(), plugins.end(), sortPlugins); QCOMPARE(plugins.size(), 2); // mimetype filter. Only one match, jsonplugin2 is specific to text/html. auto supportTextPlain = [](const KPluginMetaData &metaData) { return metaData.supportsMimeType(QStringLiteral("text/plain")); }; plugins = KPluginMetaData::findPlugins(QStringLiteral("namespace"), supportTextPlain); QCOMPARE(plugins.size(), 1); QCOMPARE(plugins[0].pluginId(), QStringLiteral("jsonplugin_cmake_macro")); // mimetype filter. Two matches, both support text/html, via inheritance. auto supportTextHtml = [](const KPluginMetaData &metaData) { return metaData.supportsMimeType(QStringLiteral("text/html")); }; plugins = KPluginMetaData::findPlugins(QStringLiteral("namespace"), supportTextHtml); std::sort(plugins.begin(), plugins.end(), sortPlugins); QCOMPARE(plugins.size(), 2); QCOMPARE(plugins[0].pluginId(), QStringLiteral("jsonplugin_cmake_macro")); QCOMPARE(plugins[1].pluginId(), QStringLiteral("qtplugin")); // mimetype filter with invalid mimetype auto supportDoesNotExist = [](const KPluginMetaData &metaData) { return metaData.supportsMimeType(QStringLiteral("does/not/exist")); }; plugins = KPluginMetaData::findPlugins(QStringLiteral("namespace"), supportDoesNotExist); QCOMPARE(plugins.size(), 0); // by plugin invalid id KPluginMetaData plugin = KPluginMetaData::findPluginById(QStringLiteral("namespace"), QStringLiteral("invalidid")); QVERIFY(!plugin.isValid()); // absolute path, no filter plugins = KPluginMetaData::findPlugins(QCoreApplication::applicationDirPath() + QStringLiteral("/namespace")); std::sort(plugins.begin(), plugins.end(), sortPlugins); QCOMPARE(plugins.size(), 2); QCOMPARE(plugins[0].pluginId(), QStringLiteral("jsonplugin_cmake_macro")); QCOMPARE(plugins[1].pluginId(), QStringLiteral("qtplugin")); // This plugin has no explicit pluginId and will fall back to basename of file const KPluginMetaData validPlugin = KPluginMetaData::findPluginById(QStringLiteral("namespace"), QStringLiteral("jsonplugin_cmake_macro")); QVERIFY(validPlugin.isValid()); QCOMPARE(plugins[0].pluginId(), QStringLiteral("jsonplugin_cmake_macro")); } void testStaticPlugins() { #if defined(QT_SHARED) // in a static Qt build we can already have other built-in static plugins here QCOMPARE(QPluginLoader::staticPlugins().count(), 0); #endif const auto plugins = KPluginMetaData::findPlugins(QStringLiteral("staticnamespace")); QCOMPARE(plugins.count(), 1); QCOMPARE(plugins.first().description(), QStringLiteral("This is a plugin")); QCOMPARE(plugins.first().fileName(), QStringLiteral("staticnamespace/static_jsonplugin_cmake_macro")); } void testPluginsWithoutMetaData() { #if !defined(QT_SHARED) QSKIP("Dynamic plugin loading not supported with a static Qt build"); #endif KPluginMetaData emptyMetaData(QStringLiteral("namespace/pluginwithoutmetadata"), KPluginMetaData::AllowEmptyMetaData); QVERIFY(emptyMetaData.isValid()); QCOMPARE(emptyMetaData.pluginId(), QStringLiteral("pluginwithoutmetadata")); const auto plugins = KPluginMetaData::findPlugins(QStringLiteral("namespace"), {}, KPluginMetaData::AllowEmptyMetaData); QCOMPARE(plugins.count(), 3); for (auto plugin : plugins) { QVERIFY(plugin.isValid()); if (plugin.pluginId() == QLatin1String("pluginwithoutmetadata")) { QVERIFY(plugin.rawData().isEmpty()); } else if (plugin.pluginId() == QLatin1String("jsonplugin_cmake_macro") || plugin.pluginId() == QLatin1String("qtplugin")) { QVERIFY(!plugin.rawData().isEmpty()); } else { QVERIFY2(false, "should not be reachable"); } } const auto pluginInvalid = KPluginMetaData::findPluginById(QStringLiteral("namespace"), QStringLiteral("pluginwithoutmetadata")); const auto pluginValid = KPluginMetaData::findPluginById(QStringLiteral("namespace"), // QStringLiteral("pluginwithoutmetadata"), KPluginMetaData::AllowEmptyMetaData); QVERIFY(!pluginInvalid.isValid()); QVERIFY(pluginValid.isValid()); } void testStaticPluginsWithoutMetadata() { QVERIFY(KPluginMetaData::findPlugins(QStringLiteral("staticnamespace3")).isEmpty()); const auto plugins = KPluginMetaData::findPlugins(QStringLiteral("staticnamespace3"), {}, KPluginMetaData::AllowEmptyMetaData); QCOMPARE(plugins.count(), 1); QVERIFY(plugins.first().isValid()); QCOMPARE(plugins.first().pluginId(), QStringLiteral("static_plugin_without_metadata")); } void testReverseDomainNotationPluginId() { #if !defined(QT_SHARED) QSKIP("Dynamic plugin loading not supported with a static Qt build"); #endif KPluginMetaData data(QStringLiteral("org.kde.test")); QVERIFY(data.isValid()); QCOMPARE(data.pluginId(), QStringLiteral("org.kde.test")); } // We can't use the plain pluginId as a classname, thus check if the replacement compiles and the original identifies it used for lookup void testReverseDomanNotationStaticPlugin() { KPluginMetaData data = KPluginMetaData::findPluginById(QStringLiteral("rdnstatic"), QStringLiteral("org.kde.test-staticplugin")); QVERIFY(data.isValid()); QCOMPARE(data.pluginId(), QStringLiteral("org.kde.test-staticplugin")); } void testFindingPluginInAppDirFirst() { #if !defined(QT_SHARED) QSKIP("Dynamic plugin loading not supported with a static Qt build"); #endif const QString originalPluginPath = QPluginLoader(QStringLiteral("namespace/jsonplugin_cmake_macro")).fileName(); const QString pluginFileName = QFileInfo(originalPluginPath).fileName(); const QString pluginNamespace = QStringLiteral("somepluginnamespace"); const QString pluginAppDir = QCoreApplication::applicationDirPath() + QLatin1Char('/') + pluginNamespace; QDir(pluginAppDir).mkpath(QStringLiteral(".")); const QString pluginAppPath = pluginAppDir + QLatin1Char('/') + pluginFileName; QFile::remove(pluginAppPath); QVERIFY(QFile::copy(originalPluginPath, pluginAppPath)); QTemporaryDir temp; QVERIFY(temp.isValid()); QDir dir(temp.path()); QVERIFY(dir.mkdir(pluginNamespace)); QVERIFY(dir.cd(pluginNamespace)); const QString pluginInNamespacePath = dir.absoluteFilePath(pluginFileName); QVERIFY(QFile::copy(originalPluginPath, pluginInNamespacePath)); LibraryPathRestorer restorer(QCoreApplication::libraryPaths()); QCoreApplication::setLibraryPaths(QStringList() << temp.path()); // Our plugin in the applicationDirPath should come first const QString relativePathWithNamespace = QStringLiteral("somepluginnamespace/jsonplugin_cmake_macro"); KPluginMetaData data(relativePathWithNamespace); QVERIFY(data.isValid()); QCOMPARE(data.fileName(), pluginAppPath); // The other one must be valid QVERIFY(KPluginMetaData(pluginInNamespacePath).isValid()); // And after removing the plugin in the applicationDirPath, it should be found QVERIFY(QFile::remove(pluginAppPath)); QCOMPARE(KPluginMetaData(relativePathWithNamespace).fileName(), pluginInNamespacePath); } void testMetaDataQDebugOperator() { #if !defined(QT_SHARED) QSKIP("Dynamic plugin loading not supported with a static Qt build"); #endif const auto list = KPluginMetaData::findPlugins(QStringLiteral("namespace")); qDebug() << list.first(); qDebug() << list; qDebug() << (QList() << list << list << list); } void benchmarkFindPlugins() { QSKIP("Skipped by default"); int loopIterations = 10; QBENCHMARK { for (int i = 0; i < loopIterations; ++i) { const auto plugins = KPluginMetaData::findPlugins(QStringLiteral("namespace"), {}, KPluginMetaData::AllowEmptyMetaData); Q_UNUSED(plugins) } } QList plugins = KPluginMetaData::findPlugins(QStringLiteral("namespace"), {}, KPluginMetaData::AllowEmptyMetaData | KPluginMetaData::CacheMetaData); QBENCHMARK { for (int i = 0; i < loopIterations; ++i) { plugins = KPluginMetaData::findPlugins(QStringLiteral("namespace"), {}, KPluginMetaData::AllowEmptyMetaData | KPluginMetaData::CacheMetaData); } } } }; QTEST_MAIN(KPluginMetaDataTest) #include "kpluginmetadatatest.moc"