Warning, file /frameworks/kcoreaddons/autotests/kpluginmetadatatest.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
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 <kpluginloader.h> 0018 #include <kpluginmetadata.h> 0019 0020 #include <QLocale> 0021 #include <QLoggingCategory> 0022 0023 namespace QTest 0024 { 0025 template<> 0026 inline char *toString(const QJsonValue &val) 0027 { 0028 // simply reuse the QDebug representation 0029 QString result; 0030 QDebug(&result) << val; 0031 return QTest::toString(result); 0032 } 0033 } 0034 0035 class LibraryPathRestorer 0036 { 0037 public: 0038 explicit LibraryPathRestorer(const QStringList &paths) 0039 : mPaths(paths) 0040 { 0041 } 0042 ~LibraryPathRestorer() 0043 { 0044 QCoreApplication::setLibraryPaths(mPaths); 0045 } 0046 0047 private: 0048 QStringList mPaths; 0049 }; 0050 0051 class KPluginMetaDataTest : public QObject 0052 { 0053 Q_OBJECT 0054 bool m_canMessage = false; 0055 0056 void doMessagesWorkInternal() 0057 { 0058 } 0059 0060 Q_REQUIRED_RESULT bool doMessagesWork() 0061 { 0062 // Q_SKIP returns, but since this is called in multiple tests we want to return a bool so the caller can 0063 // return easily. 0064 auto internalCheck = [this] { 0065 // Make sure output is well formed AND generated. To that end we cannot run this test when any of the 0066 // overriding environment variables are set. 0067 // https://bugs.kde.org/show_bug.cgi?id=387006 0068 if (qEnvironmentVariableIsSet("QT_MESSAGE_PATTERN")) { 0069 QSKIP("QT_MESSAGE_PATTERN prevents warning expectations from matching"); 0070 } 0071 if (qEnvironmentVariableIsSet("QT_LOGGING_RULES")) { 0072 QSKIP("QT_LOGGING_RULES prevents warning expectations from matching"); 0073 } 0074 if (qEnvironmentVariableIsSet("QT_LOGGING_CONF")) { 0075 QSKIP("QT_LOGGING_CONF prevents warning expectations from matching"); 0076 } 0077 m_canMessage = true; 0078 // Ensure all frameworks output is enabled so the expectations can match. 0079 // qtlogging.ini may have disabled it but we can fix that because setFilterRules overrides the ini files. 0080 QLoggingCategory::setFilterRules(QStringLiteral("kf.*=true")); 0081 }; 0082 internalCheck(); 0083 return m_canMessage; 0084 } 0085 private Q_SLOTS: 0086 0087 void testFromPluginLoader() 0088 { 0089 QString location; 0090 location = QPluginLoader(QStringLiteral("jsonplugin")).fileName(); 0091 QVERIFY2(!location.isEmpty(), "Could not find jsonplugin"); 0092 0093 // now that this file is translated we need to read it instead of hardcoding the contents here 0094 QString jsonLocation = QFINDTESTDATA("jsonplugin.json"); 0095 QVERIFY2(!jsonLocation.isEmpty(), "Could not find jsonplugin.json"); 0096 QFile jsonFile(jsonLocation); 0097 QVERIFY(jsonFile.open(QFile::ReadOnly)); 0098 QJsonParseError e; 0099 QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonFile.readAll(), &e); 0100 QCOMPARE(e.error, QJsonParseError::NoError); 0101 0102 location = QFileInfo(location).absoluteFilePath(); 0103 0104 KPluginMetaData fromQPluginLoader(QPluginLoader(QStringLiteral("jsonplugin"))); 0105 KPluginMetaData fromFullPath(location); 0106 KPluginMetaData fromRelativePath(QStringLiteral("jsonplugin")); 0107 KPluginMetaData fromRawData(jsonDoc.object(), location); 0108 0109 auto description = QStringLiteral("This is a plugin"); 0110 0111 #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 86) 0112 KPluginMetaData fromKPluginLoader(KPluginLoader(QStringLiteral("jsonplugin"))); 0113 QVERIFY(fromKPluginLoader.isValid()); 0114 QCOMPARE(fromKPluginLoader.description(), description); 0115 QCOMPARE(fromKPluginLoader, fromKPluginLoader); 0116 QCOMPARE(fromQPluginLoader, fromKPluginLoader); 0117 QCOMPARE(fromKPluginLoader, fromQPluginLoader); 0118 QCOMPARE(fromKPluginLoader, fromFullPath); 0119 QCOMPARE(fromKPluginLoader, fromRawData); 0120 QCOMPARE(fromFullPath, fromKPluginLoader); 0121 QCOMPARE(fromRawData, fromKPluginLoader); 0122 QVERIFY(!KPluginMetaData(KPluginLoader(QStringLiteral("doesnotexist"))).isValid()); 0123 #endif 0124 0125 QVERIFY(fromQPluginLoader.isValid()); 0126 QCOMPARE(fromQPluginLoader.description(), description); 0127 QVERIFY(fromFullPath.isValid()); 0128 QCOMPARE(fromFullPath.description(), description); 0129 QVERIFY(fromRelativePath.isValid()); 0130 QCOMPARE(fromRelativePath.description(), description); 0131 QVERIFY(fromRawData.isValid()); 0132 QCOMPARE(fromRawData.description(), description); 0133 0134 // check operator== 0135 QCOMPARE(fromRawData, fromRawData); 0136 QCOMPARE(fromQPluginLoader, fromQPluginLoader); 0137 QCOMPARE(fromFullPath, fromFullPath); 0138 0139 QCOMPARE(fromQPluginLoader, fromFullPath); 0140 QCOMPARE(fromQPluginLoader, fromRawData); 0141 0142 QCOMPARE(fromFullPath, fromQPluginLoader); 0143 QCOMPARE(fromFullPath, fromRawData); 0144 0145 QCOMPARE(fromRawData, fromQPluginLoader); 0146 QCOMPARE(fromRawData, fromFullPath); 0147 0148 QVERIFY(!KPluginMetaData(QPluginLoader(QStringLiteral("doesnotexist"))).isValid()); 0149 QVERIFY(!KPluginMetaData(QJsonObject(), QString()).isValid()); 0150 } 0151 0152 void testAllKeys() 0153 { 0154 QJsonParseError e; 0155 QJsonObject jo = QJsonDocument::fromJson( 0156 "{\n" 0157 " \"KPlugin\": {\n" 0158 " \"Name\": \"Date and Time\",\n" 0159 " \"Description\": \"Date and time by timezone\",\n" 0160 " \"Icon\": \"preferences-system-time\",\n" 0161 " \"Authors\": { \"Name\": \"Aaron Seigo\", \"Email\": \"aseigo@kde.org\" },\n" 0162 " \"Translators\": { \"Name\": \"No One\", \"Email\": \"no.one@kde.org\" },\n" 0163 " \"OtherContributors\": { \"Name\": \"No One\", \"Email\": \"no.one@kde.org\" },\n" 0164 " \"Category\": \"Date and Time\",\n" 0165 " \"Dependencies\": [ \"foo\", \"bar\"],\n" 0166 " \"EnabledByDefault\": \"true\",\n" 0167 " \"ExtraInformation\": \"Something else\",\n" 0168 " \"License\": \"LGPL\",\n" 0169 " \"Copyright\": \"(c) Alex Richardson 2015\",\n" 0170 " \"Id\": \"time\",\n" 0171 " \"Version\": \"1.0\",\n" 0172 " \"Website\": \"https://plasma.kde.org/\",\n" 0173 " \"MimeTypes\": [ \"image/png\" ],\n" 0174 " \"ServiceTypes\": [\"Plasma/DataEngine\"]\n" 0175 " }\n}\n", 0176 &e) 0177 .object(); 0178 QCOMPARE(e.error, QJsonParseError::NoError); 0179 KPluginMetaData m(jo, QString()); 0180 QVERIFY(m.isValid()); 0181 QCOMPARE(m.pluginId(), QStringLiteral("time")); 0182 QCOMPARE(m.name(), QStringLiteral("Date and Time")); 0183 QCOMPARE(m.description(), QStringLiteral("Date and time by timezone")); 0184 #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 87) 0185 QCOMPARE(m.extraInformation(), QStringLiteral("Something else")); 0186 #endif 0187 QCOMPARE(m.iconName(), QStringLiteral("preferences-system-time")); 0188 QCOMPARE(m.category(), QStringLiteral("Date and Time")); 0189 #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 79) 0190 QCOMPARE(m.dependencies(), QStringList() << QStringLiteral("foo") << QStringLiteral("bar")); 0191 #endif 0192 QCOMPARE(m.authors().size(), 1); 0193 QCOMPARE(m.authors().constFirst().name(), QStringLiteral("Aaron Seigo")); 0194 QCOMPARE(m.authors().constFirst().emailAddress(), QStringLiteral("aseigo@kde.org")); 0195 QCOMPARE(m.translators().size(), 1); 0196 QCOMPARE(m.translators().constFirst().name(), QStringLiteral("No One")); 0197 QCOMPARE(m.translators().constFirst().emailAddress(), QStringLiteral("no.one@kde.org")); 0198 QCOMPARE(m.otherContributors().size(), 1); 0199 QCOMPARE(m.otherContributors().constFirst().name(), QStringLiteral("No One")); 0200 QCOMPARE(m.otherContributors().constFirst().emailAddress(), QStringLiteral("no.one@kde.org")); 0201 QVERIFY(m.isEnabledByDefault()); 0202 QCOMPARE(m.license(), QStringLiteral("LGPL")); 0203 QCOMPARE(m.copyrightText(), QStringLiteral("(c) Alex Richardson 2015")); 0204 QCOMPARE(m.version(), QStringLiteral("1.0")); 0205 QCOMPARE(m.website(), QStringLiteral("https://plasma.kde.org/")); 0206 #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 89) 0207 QCOMPARE(m.serviceTypes(), QStringList() << QStringLiteral("Plasma/DataEngine")); 0208 #endif 0209 QCOMPARE(m.mimeTypes(), QStringList() << QStringLiteral("image/png")); 0210 } 0211 0212 void testTranslations() 0213 { 0214 QJsonParseError e; 0215 QJsonObject jo = QJsonDocument::fromJson( 0216 "{ \"KPlugin\": {\n" 0217 "\"Name\": \"Name\",\n" 0218 "\"Name[de]\": \"Name (de)\",\n" 0219 "\"Name[de_DE]\": \"Name (de_DE)\",\n" 0220 "\"Description\": \"Description\",\n" 0221 "\"Description[de]\": \"Beschreibung (de)\",\n" 0222 "\"Description[de_DE]\": \"Beschreibung (de_DE)\"\n" 0223 "}\n}", 0224 &e) 0225 .object(); 0226 KPluginMetaData m(jo, QString()); 0227 QLocale::setDefault(QLocale::c()); 0228 QCOMPARE(m.name(), QStringLiteral("Name")); 0229 QCOMPARE(m.description(), QStringLiteral("Description")); 0230 0231 QLocale::setDefault(QLocale(QStringLiteral("de_DE"))); 0232 QCOMPARE(m.name(), QStringLiteral("Name (de_DE)")); 0233 QCOMPARE(m.description(), QStringLiteral("Beschreibung (de_DE)")); 0234 0235 QLocale::setDefault(QLocale(QStringLiteral("de_CH"))); 0236 QCOMPARE(m.name(), QStringLiteral("Name (de)")); 0237 QCOMPARE(m.description(), QStringLiteral("Beschreibung (de)")); 0238 0239 QLocale::setDefault(QLocale(QStringLiteral("fr_FR"))); 0240 QCOMPARE(m.name(), QStringLiteral("Name")); 0241 QCOMPARE(m.description(), QStringLiteral("Description")); 0242 } 0243 0244 void testReadStringList() 0245 { 0246 if (!doMessagesWork()) { 0247 return; 0248 } 0249 QJsonParseError e; 0250 QJsonObject jo = QJsonDocument::fromJson( 0251 "{\n" 0252 "\"String\": \"foo\",\n" 0253 "\"OneArrayEntry\": [ \"foo\" ],\n" 0254 "\"Bool\": true,\n" // make sure booleans are accepted 0255 "\"QuotedBool\": \"true\",\n" // make sure booleans are accepted 0256 "\"Number\": 12345,\n" // number should also work 0257 "\"QuotedNumber\": \"12345\",\n" // number should also work 0258 "\"EmptyArray\": [],\n" 0259 "\"NumberArray\": [1, 2, 3],\n" 0260 "\"BoolArray\": [true, false, true],\n" 0261 "\"StringArray\": [\"foo\", \"bar\"],\n" 0262 "\"Null\": null,\n" // should return empty list 0263 "\"QuotedNull\": \"null\",\n" // this is okay, it is a string 0264 "\"ArrayWithNull\": [ \"foo\", null, \"bar\"],\n" // TODO: null is converted to empty string, is this okay? 0265 "\"Object\": { \"foo\": \"bar\" }\n" // should return empty list 0266 "}", 0267 &e) 0268 .object(); 0269 QCOMPARE(e.error, QJsonParseError::NoError); 0270 QTest::ignoreMessage(QtWarningMsg, QRegularExpression(QStringLiteral("Expected JSON property "))); 0271 KPluginMetaData data(jo, QStringLiteral("test")); 0272 QCOMPARE(data.value(QStringLiteral("String"), QStringList()), QStringList(QStringLiteral("foo"))); 0273 QCOMPARE(data.value(QStringLiteral("OneArrayEntry"), QStringList()), QStringList(QStringLiteral("foo"))); 0274 QCOMPARE(data.value(QStringLiteral("Bool"), QStringList()), QStringList(QStringLiteral("true"))); 0275 QCOMPARE(data.value(QStringLiteral("QuotedBool"), QStringList()), QStringList(QStringLiteral("true"))); 0276 QCOMPARE(data.value(QStringLiteral("Number"), QStringList()), QStringList(QStringLiteral("12345"))); 0277 QCOMPARE(data.value(QStringLiteral("QuotedNumber"), QStringList()), QStringList(QStringLiteral("12345"))); 0278 QCOMPARE(data.value(QStringLiteral("EmptyArray"), QStringList()), QStringList()); 0279 QCOMPARE(data.value(QStringLiteral("NumberArray"), QStringList()), QStringList() << QStringLiteral("1") << QStringLiteral("2") << QStringLiteral("3")); 0280 QCOMPARE(data.value(QStringLiteral("BoolArray"), QStringList()), 0281 QStringList() << QStringLiteral("true") << QStringLiteral("false") << QStringLiteral("true")); 0282 QCOMPARE(data.value(QStringLiteral("StringArray"), QStringList()), QStringList() << QStringLiteral("foo") << QStringLiteral("bar")); 0283 QCOMPARE(data.value(QStringLiteral("Null"), QStringList()), QStringList()); 0284 QCOMPARE(data.value(QStringLiteral("QuotedNull"), QStringList()), QStringList(QStringLiteral("null"))); 0285 QCOMPARE(data.value(QStringLiteral("ArrayWithNull"), QStringList()), QStringList() << QStringLiteral("foo") << QString() << QStringLiteral("bar")); 0286 QCOMPARE(data.value(QStringLiteral("Object"), QStringList()), QStringList()); 0287 } 0288 0289 #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 91) 0290 void testFromDesktopFile() 0291 { 0292 const QString dfile = QFINDTESTDATA("data/fakeplugin.desktop"); 0293 KPluginMetaData md = KPluginMetaData::fromDesktopFile(dfile); 0294 QVERIFY(md.isValid()); 0295 QCOMPARE(md.pluginId(), QStringLiteral("fakeplugin")); 0296 QCOMPARE(md.fileName(), QStringLiteral("fakeplugin")); 0297 QCOMPARE(md.metaDataFileName(), dfile); 0298 QCOMPARE(md.iconName(), QStringLiteral("preferences-system-time")); 0299 QCOMPARE(md.license(), QStringLiteral("LGPL")); 0300 QCOMPARE(md.website(), QStringLiteral("https://kde.org/")); 0301 QCOMPARE(md.category(), QStringLiteral("Examples")); 0302 QCOMPARE(md.version(), QStringLiteral("1.0")); 0303 #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 79) 0304 QCOMPARE(md.dependencies(), QStringList()); 0305 #endif 0306 QCOMPARE(md.isHidden(), false); 0307 #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 89) 0308 QCOMPARE(md.serviceTypes(), QStringList(QStringLiteral("KService/NSA"))); 0309 #endif 0310 QCOMPARE(md.mimeTypes(), QStringList() << QStringLiteral("image/png") << QStringLiteral("application/pdf")); 0311 0312 auto kp = md.rawData()[QStringLiteral("KPlugin")].toObject(); 0313 QStringList formFactors = kp.value(QStringLiteral("FormFactors")).toVariant().toStringList(); 0314 QCOMPARE(formFactors, QStringList() << QStringLiteral("mediacenter") << QStringLiteral("desktop")); 0315 QCOMPARE(md.formFactors(), QStringList() << QStringLiteral("mediacenter") << QStringLiteral("desktop")); 0316 0317 const QString dfilehidden = QFINDTESTDATA("data/hiddenplugin.desktop"); 0318 KPluginMetaData mdhidden = KPluginMetaData::fromDesktopFile(dfilehidden); 0319 QVERIFY(mdhidden.isValid()); 0320 QCOMPARE(mdhidden.isHidden(), true); 0321 } 0322 0323 void twoStepsParseTest() 0324 { 0325 QStandardPaths::setTestModeEnabled(true); 0326 const QString dfile = QFINDTESTDATA("data/twostepsparsetest.desktop"); 0327 const QString typesPath = QFINDTESTDATA("data/servicetypes/example-servicetype.desktop"); 0328 KPluginMetaData md = KPluginMetaData::fromDesktopFile(dfile, QStringList() << typesPath); 0329 QVERIFY(md.isValid()); 0330 QStringList list = md.value(QStringLiteral("X-Test-List"), QStringList()); 0331 QCOMPARE(list, QStringList({QStringLiteral("first"), QStringLiteral("second")})); 0332 } 0333 0334 void testServiceTypes_data() 0335 { 0336 const QString kdevServiceTypePath = QFINDTESTDATA("data/servicetypes/fake-kdevelopplugin.desktop"); 0337 const QString invalidServiceTypePath = QFINDTESTDATA("data/servicetypes/invalid-servicetype.desktop"); 0338 const QString exampleServiceTypePath = QFINDTESTDATA("data/servicetypes/example-servicetype.desktop"); 0339 QVERIFY(!kdevServiceTypePath.isEmpty()); 0340 QVERIFY(!invalidServiceTypePath.isEmpty()); 0341 QVERIFY(!exampleServiceTypePath.isEmpty()); 0342 } 0343 0344 void testServiceType() 0345 { 0346 if (!doMessagesWork()) { 0347 return; 0348 } 0349 const QString typesPath = QFINDTESTDATA("data/servicetypes/example-servicetype.desktop"); 0350 QVERIFY(!typesPath.isEmpty()); 0351 const QString inputPath = QFINDTESTDATA("data/servicetypes/example-input.desktop"); 0352 QVERIFY(!inputPath.isEmpty()); 0353 QTest::ignoreMessage( 0354 QtWarningMsg, 0355 // We also print out a list of paths we searched in. With the ".+" we ensure that they are printed out, 0356 // but don't make fragile assumptions on the exact message 0357 QRegularExpression(QStringLiteral("Unable to find service type for service \"bar/foo\" listed in \"") + inputPath + QLatin1String("\" .+"))); 0358 KPluginMetaData md = KPluginMetaData::fromDesktopFile(inputPath, QStringList() << typesPath); 0359 QVERIFY(md.isValid()); 0360 QCOMPARE(md.name(), QStringLiteral("Example")); 0361 #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 89) 0362 QCOMPARE(md.serviceTypes(), QStringList() << QStringLiteral("example/servicetype") << QStringLiteral("bar/foo")); 0363 #endif 0364 QCOMPARE(md.rawData().size(), 8); 0365 QVERIFY(md.rawData().value(QStringLiteral("KPlugin")).isObject()); 0366 QCOMPARE(md.rawData().value(QStringLiteral("X-Test-Integer")), QJsonValue(42)); 0367 QCOMPARE(md.rawData().value(QStringLiteral("X-Test-Bool")), QJsonValue(true)); 0368 QCOMPARE(md.rawData().value(QStringLiteral("X-Test-Double")), QJsonValue(42.42)); 0369 QCOMPARE(md.rawData().value(QStringLiteral("X-Test-String")), QJsonValue(QStringLiteral("foobar"))); 0370 QCOMPARE(md.rawData().value(QStringLiteral("X-Test-List")), 0371 QJsonValue( 0372 QJsonArray::fromStringList(QStringList() << QStringLiteral("a") << QStringLiteral("b") << QStringLiteral("c") << QStringLiteral("def")))); 0373 QCOMPARE(md.rawData().value(QStringLiteral("X-Test-Size")), QJsonValue(QStringLiteral("10,20"))); // QSize no longer supported (and also no longer used) 0374 QCOMPARE(md.rawData().value(QStringLiteral("X-Test-Unknown")), QJsonValue(QStringLiteral("true"))); // unknown property -> string 0375 #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 88) 0376 const QString charOverloadVlaue = md.value(QStringLiteral("X-Test-Unknown"), "true"); 0377 QCOMPARE(charOverloadVlaue, QStringLiteral("true")); 0378 #endif 0379 } 0380 0381 void testBadGroupsInServiceType() 0382 { 0383 if (!doMessagesWork()) { 0384 return; 0385 } 0386 const QString typesPath = QFINDTESTDATA("data/servicetypes/bad-groups-servicetype.desktop"); 0387 QVERIFY(!typesPath.isEmpty()); 0388 const QString inputPath = QFINDTESTDATA("data/servicetypes/bad-groups-input.desktop"); 0389 QVERIFY(!inputPath.isEmpty()); 0390 QTest::ignoreMessage(QtWarningMsg, "Illegal .desktop group definition (does not end with ']'): \"[PropertyDef::MissingTerminator\""); 0391 QTest::ignoreMessage(QtWarningMsg, "Illegal .desktop group definition (does not end with ']'): \"[PropertyDef::\""); 0392 QTest::ignoreMessage(QtWarningMsg, "Illegal .desktop group definition (does not end with ']'): \"[\""); 0393 QTest::ignoreMessage(QtWarningMsg, "Read empty .desktop file group name! Invalid file?"); 0394 QTest::ignoreMessage(QtWarningMsg, 0395 QRegularExpression(QStringLiteral("Skipping invalid group \"\" in service type \".*/bad-groups-servicetype.desktop\""))); 0396 QTest::ignoreMessage(QtWarningMsg, 0397 QRegularExpression(QStringLiteral("Skipping invalid group \"DoesNotStartWithPropertyDef::SomeOtherProperty\" in service type " 0398 "\".+/data/servicetypes/bad-groups-servicetype.desktop\""))); 0399 QTest::ignoreMessage(QtWarningMsg, "Could not find Type= key in group \"PropertyDef::MissingType\""); 0400 QTest::ignoreMessage(QtWarningMsg, 0401 QRegularExpression(QStringLiteral("Property type \"integer\" is not a known QVariant type. Found while parsing property " 0402 "definition for \"InvalidType\" in \".+/data/servicetypes/bad-groups-servicetype.desktop\""))); 0403 QTest::ignoreMessage(QtWarningMsg, 0404 QRegularExpression(QStringLiteral(".+/data/servicetypes/bad-groups-input.desktop:\\d+: Key name is missing: \"=11\""))); 0405 QTest::ignoreMessage(QtWarningMsg, 0406 QRegularExpression(QStringLiteral(".+/data/servicetypes/bad-groups-input.desktop:\\d+: Key name is missing: \"=13\""))); 0407 QTest::ignoreMessage(QtWarningMsg, 0408 QRegularExpression(QStringLiteral(".+/data/servicetypes/bad-groups-input.desktop:\\d+: Key name is missing: \"=14\""))); 0409 KPluginMetaData md = KPluginMetaData::fromDesktopFile(inputPath, QStringList() << typesPath); 0410 QVERIFY(md.isValid()); 0411 QCOMPARE(md.name(), QStringLiteral("Bad Groups")); 0412 QCOMPARE(md.rawData().size(), 8); 0413 QCOMPARE(md.rawData().value(QStringLiteral("ThisIsOkay")), QJsonValue(10)); // integer 0414 // 11 is empty group 0415 QCOMPARE(md.rawData().value(QStringLiteral("MissingTerminator")), QJsonValue(12)); // accept missing group terminator (for now) -> integer 0416 // 13 is empty group name 0417 // 14 is empty group name 0418 QCOMPARE(md.rawData().value(QStringLiteral("SomeOtherProperty")), 0419 QJsonValue(QStringLiteral("15"))); // does not start with PropertyDef:: -> fall back to string 0420 QCOMPARE(md.rawData().value(QStringLiteral("TrailingSpacesAreOkay")), QJsonValue(16)); // accept trailing spaces in group name -> integer 0421 QCOMPARE(md.rawData().value(QStringLiteral("MissingType")), QJsonValue(QStringLiteral("17"))); // Type= missing -> fall back to string 0422 QCOMPARE(md.rawData().value(QStringLiteral("InvalidType")), QJsonValue(QStringLiteral("18"))); // Type= is invalid -> fall back to string 0423 QCOMPARE(md.rawData().value(QStringLiteral("ThisIsOkayAgain")), QJsonValue(19)); // valid definition after invalid ones should still work -> integer 0424 } 0425 #endif 0426 0427 void testJSONMetadata() 0428 { 0429 const QString inputPath = QFINDTESTDATA("data/testmetadata.json"); 0430 KPluginMetaData md = KPluginMetaData::fromJsonFile(inputPath); 0431 QVERIFY(md.isValid()); 0432 QCOMPARE(md.name(), QStringLiteral("Test")); 0433 0434 QCOMPARE(md.value(QStringLiteral("X-Plasma-MainScript")), QStringLiteral("ui/main.qml")); 0435 QJsonArray expected; 0436 expected.append(QStringLiteral("Export")); 0437 QCOMPARE(md.rawData().value(QStringLiteral("X-Purpose-PluginTypes")).toArray(), expected); 0438 QCOMPARE(md.value(QStringLiteral("SomeInt"), 24), 42); 0439 QCOMPARE(md.value(QStringLiteral("SomeIntAsString"), 24), 42); 0440 QCOMPARE(md.value(QStringLiteral("SomeStringNotAInt"), 24), 24); 0441 QCOMPARE(md.value(QStringLiteral("DoesNotExist"), 24), 24); 0442 0443 QVERIFY(md.value(QStringLiteral("SomeBool"), false)); 0444 QVERIFY(!md.value(QStringLiteral("SomeBoolThatIsFalse"), true)); 0445 QVERIFY(md.value(QStringLiteral("SomeBoolAsString"), false)); 0446 QVERIFY(md.value(QStringLiteral("DoesNotExist"), true)); 0447 } 0448 0449 void testPathIsAbsolute_data() 0450 { 0451 QTest::addColumn<QString>("inputAbsolute"); 0452 QTest::addColumn<QString>("pluginPath"); 0453 0454 #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 91) 0455 // The .desktop file has X-KDE-Library, so .fileName() returns different file 0456 QTest::newRow("desktop") << QFINDTESTDATA("data/fakeplugin.desktop") << QStringLiteral("fakeplugin"); 0457 #endif 0458 // But for the .json based plugin both are the same. 0459 QTest::newRow("json") << QFINDTESTDATA("data/testmetadata.json") << QFINDTESTDATA("data/testmetadata.json"); 0460 // And also for the library with embedded JSON metadata. 0461 QPluginLoader shlibLoader(QCoreApplication::applicationDirPath() + QStringLiteral("/jsonplugin")); 0462 QVERIFY2(!shlibLoader.fileName().isEmpty(), "Could not find jsonplugin"); 0463 QString shlibPath = QFileInfo(shlibLoader.fileName()).absoluteFilePath(); 0464 QTest::newRow("library") << shlibPath << shlibPath; 0465 } 0466 0467 void testPathIsAbsolute() 0468 { 0469 // Test that the fileName() accessor always returns an absolute path if it was used. 0470 QFETCH(QString, inputAbsolute); 0471 QVERIFY2(QDir::isAbsolutePath(inputAbsolute), qPrintable(inputAbsolute)); 0472 QFETCH(QString, pluginPath); 0473 0474 const auto createMetaData = [](const QString &path) { 0475 #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 91) 0476 return KPluginMetaData(path); 0477 #else 0478 if (path.endsWith(QLatin1String(".json"))) { 0479 return KPluginMetaData::fromJsonFile(path); 0480 } else { 0481 return KPluginMetaData(path); 0482 } 0483 #endif 0484 }; 0485 0486 KPluginMetaData mdAbsolute = createMetaData(inputAbsolute); 0487 QVERIFY(mdAbsolute.isValid()); 0488 QCOMPARE(mdAbsolute.metaDataFileName(), inputAbsolute); 0489 QCOMPARE(mdAbsolute.fileName(), pluginPath); 0490 0491 // All files that have been opened should be stored as absolute paths. 0492 QString inputRelative; 0493 if (QLibrary::isLibrary(inputAbsolute)) { 0494 // We have a plugin without namespace, with the code path below we would end up with 0495 // a path relative to the PWD, but we want to check a path relative to the plugin dir. 0496 // Because of that we simply use the baseName of the file. 0497 inputRelative = QFileInfo(inputAbsolute).baseName(); 0498 } else { 0499 inputRelative = QDir::current().relativeFilePath(inputAbsolute); 0500 } 0501 QVERIFY2(QDir::isRelativePath(inputRelative), qPrintable(inputRelative)); 0502 KPluginMetaData mdRelative = createMetaData(inputRelative); 0503 QVERIFY(mdRelative.isValid()); 0504 QCOMPARE(mdRelative.metaDataFileName(), inputAbsolute); 0505 QCOMPARE(mdRelative.fileName(), pluginPath); 0506 0507 // Check that creating it with the parsed JSON object and a path keeps the path unchanged 0508 const QJsonObject json = mdAbsolute.rawData(); 0509 QString pluginRelative = QDir::current().relativeFilePath(pluginPath); 0510 QVERIFY2(QDir::isRelativePath(pluginRelative), qPrintable(pluginRelative)); 0511 // TODO: KF6: no need to test both constructors once they are merged into one overload. 0512 KPluginMetaData mdFromJson1(json, pluginRelative, inputRelative); 0513 QCOMPARE(mdFromJson1.metaDataFileName(), inputRelative); 0514 // We should not be normalizing files that have not been openened, so both arguments should be unchanged. 0515 QCOMPARE(mdFromJson1.fileName(), pluginRelative); 0516 KPluginMetaData mdFromJson2(json, inputRelative); 0517 QCOMPARE(mdFromJson2.metaDataFileName(), inputRelative); 0518 QCOMPARE(mdFromJson2.fileName(), inputRelative); 0519 } 0520 0521 void testFindPlugins() 0522 { 0523 QTemporaryDir temp; 0524 QVERIFY(temp.isValid()); 0525 QDir dir(temp.path()); 0526 QVERIFY(dir.mkdir(QStringLiteral("kpluginmetadatatest"))); 0527 QVERIFY(dir.cd(QStringLiteral("kpluginmetadatatest"))); 0528 for (const QString &name : {QStringLiteral("jsonplugin"), QStringLiteral("unversionedplugin"), QStringLiteral("jsonplugin2")}) { 0529 const QString pluginPath = QPluginLoader(name).fileName(); 0530 QVERIFY2(!pluginPath.isEmpty(), qPrintable(pluginPath)); 0531 QVERIFY2(QFile::copy(pluginPath, dir.absoluteFilePath(QFileInfo(pluginPath).fileName())), 0532 qPrintable(dir.absoluteFilePath(QFileInfo(pluginPath).fileName()))); 0533 } 0534 0535 LibraryPathRestorer restorer(QCoreApplication::libraryPaths()); 0536 // we only want plugins from our temporary dir 0537 QCoreApplication::setLibraryPaths(QStringList() << temp.path()); 0538 0539 auto sortPlugins = [](const KPluginMetaData &a, const KPluginMetaData &b) { 0540 return a.pluginId() < b.pluginId(); 0541 }; 0542 // it should find jsonplugin and jsonplugin2 since unversionedplugin does not have any meta data 0543 auto plugins = KPluginMetaData::findPlugins(QStringLiteral("kpluginmetadatatest")); 0544 std::sort(plugins.begin(), plugins.end(), sortPlugins); 0545 QCOMPARE(plugins.size(), 2); 0546 QCOMPARE(plugins[0].pluginId(), QStringLiteral("foobar")); // ID is not the filename, it is set in the JSON metadata 0547 QCOMPARE(plugins[0].description(), QStringLiteral("This is another plugin")); 0548 QCOMPARE(plugins[1].pluginId(), QStringLiteral("jsonplugin")); 0549 QCOMPARE(plugins[1].description(), QStringLiteral("This is a plugin")); 0550 0551 // filter accepts none 0552 plugins = KPluginMetaData::findPlugins(QStringLiteral("kpluginmetadatatest"), [](const KPluginMetaData &) { 0553 return false; 0554 }); 0555 std::sort(plugins.begin(), plugins.end(), sortPlugins); 0556 QCOMPARE(plugins.size(), 0); 0557 0558 // filter accepts all 0559 plugins = KPluginMetaData::findPlugins(QStringLiteral("kpluginmetadatatest"), [](const KPluginMetaData &) { 0560 return true; 0561 }); 0562 std::sort(plugins.begin(), plugins.end(), sortPlugins); 0563 QCOMPARE(plugins.size(), 2); 0564 QCOMPARE(plugins[0].description(), QStringLiteral("This is another plugin")); 0565 QCOMPARE(plugins[1].description(), QStringLiteral("This is a plugin")); 0566 0567 // mimetype filter. Only one match, jsonplugin2 is specific to text/html. 0568 auto supportTextPlain = [](const KPluginMetaData &metaData) { 0569 return metaData.supportsMimeType(QLatin1String("text/plain")); 0570 }; 0571 plugins = KPluginMetaData::findPlugins(QStringLiteral("kpluginmetadatatest"), supportTextPlain); 0572 QCOMPARE(plugins.size(), 1); 0573 QCOMPARE(plugins[0].description(), QStringLiteral("This is a plugin")); 0574 0575 // mimetype filter. Two matches, both support text/html, via inheritance. 0576 auto supportTextHtml = [](const KPluginMetaData &metaData) { 0577 return metaData.supportsMimeType(QLatin1String("text/html")); 0578 }; 0579 plugins = KPluginMetaData::findPlugins(QStringLiteral("kpluginmetadatatest"), supportTextHtml); 0580 std::sort(plugins.begin(), plugins.end(), sortPlugins); 0581 QCOMPARE(plugins.size(), 2); 0582 QCOMPARE(plugins[0].description(), QStringLiteral("This is another plugin")); 0583 QCOMPARE(plugins[1].description(), QStringLiteral("This is a plugin")); 0584 0585 // mimetype filter with invalid mimetype 0586 auto supportDoesNotExist = [](const KPluginMetaData &metaData) { 0587 return metaData.supportsMimeType(QLatin1String("does/not/exist")); 0588 }; 0589 plugins = KPluginMetaData::findPlugins(QStringLiteral("kpluginmetadatatest"), supportDoesNotExist); 0590 QCOMPARE(plugins.size(), 0); 0591 0592 // invalid std::function as filter 0593 plugins = KPluginMetaData::findPlugins(QStringLiteral("kpluginmetadatatest")); 0594 std::sort(plugins.begin(), plugins.end(), sortPlugins); 0595 QCOMPARE(plugins.size(), 2); 0596 QCOMPARE(plugins[0].description(), QStringLiteral("This is another plugin")); 0597 QCOMPARE(plugins[1].description(), QStringLiteral("This is a plugin")); 0598 0599 // by plugin id 0600 KPluginMetaData plugin = KPluginMetaData::findPluginById(dir.absolutePath(), QStringLiteral("foobar")); 0601 QVERIFY(plugin.isValid()); 0602 QCOMPARE(plugin.description(), QStringLiteral("This is another plugin")); 0603 0604 // by plugin invalid id 0605 plugin = KPluginMetaData::findPluginById(dir.absolutePath(), QStringLiteral("invalidid")); 0606 QVERIFY(!plugin.isValid()); 0607 0608 // absolute path, no filter 0609 plugins = KPluginMetaData::findPlugins(dir.absolutePath()); 0610 std::sort(plugins.begin(), plugins.end(), sortPlugins); 0611 QCOMPARE(plugins.size(), 2); 0612 QCOMPARE(plugins[0].description(), QStringLiteral("This is another plugin")); 0613 QCOMPARE(plugins[1].description(), QStringLiteral("This is a plugin")); 0614 0615 // This plugin has no explicit pluginId and will fall back to basename of file 0616 const KPluginMetaData validPlugin = KPluginMetaData::findPluginById(dir.absolutePath(), QStringLiteral("jsonplugin")); 0617 QVERIFY(validPlugin.isValid()); 0618 QCOMPARE(plugins[0].description(), QStringLiteral("This is another plugin")); 0619 0620 // The basename matches, but the pluginId does not match 0621 const KPluginMetaData nonMatchingPluginId = KPluginMetaData::findPluginById(dir.absolutePath(), QStringLiteral("jsonplugin2")); 0622 QVERIFY(!nonMatchingPluginId.isValid()); 0623 0624 const KPluginMetaData nonExistingPlugin = KPluginMetaData::findPluginById(dir.absolutePath(), QStringLiteral("invalidid")); 0625 QVERIFY(!nonExistingPlugin.isValid()); 0626 } 0627 0628 void testStaticPlugins() 0629 { 0630 QCOMPARE(QPluginLoader::staticPlugins().count(), 0); 0631 0632 const auto plugins = KPluginMetaData::findPlugins(QStringLiteral("staticnamespace")); 0633 QCOMPARE(plugins.count(), 1); 0634 0635 QCOMPARE(plugins.first().description(), QStringLiteral("This is a plugin")); 0636 QCOMPARE(plugins.first().fileName(), QStringLiteral("staticnamespace/static_jsonplugin_cmake_macro")); 0637 } 0638 0639 void testPluginsWithoutMetaData() 0640 { 0641 KPluginMetaData emptyMetaData(QStringLiteral("namespace/pluginwithoutmetadata"), KPluginMetaData::AllowEmptyMetaData); 0642 QVERIFY(emptyMetaData.isValid()); 0643 QCOMPARE(emptyMetaData.pluginId(), QStringLiteral("pluginwithoutmetadata")); 0644 0645 const auto plugins = KPluginMetaData::findPlugins(QStringLiteral("namespace"), {}, KPluginMetaData::AllowEmptyMetaData); 0646 QCOMPARE(plugins.count(), 2); 0647 for (auto plugin : plugins) { 0648 if (plugin.pluginId() == QLatin1String("pluginwithoutmetadata")) { 0649 QVERIFY(plugin.isValid()); 0650 QVERIFY(plugin.rawData().isEmpty()); 0651 } else if (plugin.pluginId() == QLatin1String("jsonplugin_cmake_macro")) { 0652 QVERIFY(plugin.isValid()); 0653 QVERIFY(!plugin.rawData().isEmpty()); 0654 } else { 0655 Q_UNREACHABLE(); 0656 } 0657 } 0658 } 0659 0660 void testStaticPluginsWithoutMetadata() 0661 { 0662 QVERIFY(KPluginMetaData::findPlugins(QStringLiteral("staticnamespace3")).isEmpty()); 0663 const auto plugins = KPluginMetaData::findPlugins(QStringLiteral("staticnamespace3"), {}, KPluginMetaData::AllowEmptyMetaData); 0664 QCOMPARE(plugins.count(), 1); 0665 QVERIFY(plugins.first().isValid()); 0666 QCOMPARE(plugins.first().pluginId(), QStringLiteral("static_plugin_without_metadata")); 0667 } 0668 0669 void testReverseDomainNotationPluginId() 0670 { 0671 KPluginMetaData data(QStringLiteral("org.kde.test")); 0672 QVERIFY(data.isValid()); 0673 QCOMPARE(data.pluginId(), QStringLiteral("org.kde.test")); 0674 } 0675 0676 void testFindingPluginInAppDirFirst() 0677 { 0678 const QString originalPluginPath = QPluginLoader(QStringLiteral("namespace/jsonplugin_cmake_macro")).fileName(); 0679 const QString pluginFileName = QFileInfo(originalPluginPath).fileName(); 0680 const QString pluginNamespace = QStringLiteral("somepluginnamespace"); 0681 const QString pluginAppDir = QCoreApplication::applicationDirPath() + QLatin1Char('/') + pluginNamespace; 0682 QDir(pluginAppDir).mkpath(QStringLiteral(".")); 0683 const QString pluginAppPath = pluginAppDir + QLatin1Char('/') + pluginFileName; 0684 QFile::remove(pluginAppPath); 0685 0686 QVERIFY(QFile::copy(originalPluginPath, pluginAppPath)); 0687 0688 QTemporaryDir temp; 0689 QVERIFY(temp.isValid()); 0690 QDir dir(temp.path()); 0691 QVERIFY(dir.mkdir(pluginNamespace)); 0692 QVERIFY(dir.cd(pluginNamespace)); 0693 0694 const QString pluginInNamespacePath = dir.absoluteFilePath(pluginFileName); 0695 QVERIFY(QFile::copy(originalPluginPath, pluginInNamespacePath)); 0696 0697 LibraryPathRestorer restorer(QCoreApplication::libraryPaths()); 0698 QCoreApplication::setLibraryPaths(QStringList() << temp.path()); 0699 0700 // Our plugin in the applicationDirPath should come first 0701 const QString relativePathWithNamespace = QStringLiteral("somepluginnamespace/jsonplugin_cmake_macro"); 0702 KPluginMetaData data(relativePathWithNamespace); 0703 QVERIFY(data.isValid()); 0704 QCOMPARE(data.fileName(), pluginAppPath); 0705 0706 // The other one must be valid 0707 QVERIFY(KPluginMetaData(pluginInNamespacePath).isValid()); 0708 // And after removing the plugin in the applicationDirPath, it should be found 0709 QVERIFY(QFile::remove(pluginAppPath)); 0710 QCOMPARE(KPluginMetaData(relativePathWithNamespace).fileName(), pluginInNamespacePath); 0711 } 0712 }; 0713 0714 QTEST_MAIN(KPluginMetaDataTest) 0715 0716 #include "kpluginmetadatatest.moc"