Warning, file /frameworks/kcoreaddons/autotests/desktoptojsontest.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 This file is part of the KDE project 0003 SPDX-FileCopyrightText: 2014 Alex Richardson <arichardson.kde@gmail.com> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "kcoreaddons_debug.h" 0009 #include <QDebug> 0010 #include <QJsonArray> 0011 #include <QJsonDocument> 0012 #include <QJsonObject> 0013 #include <QObject> 0014 #include <QProcess> 0015 #include <QTemporaryFile> 0016 #include <QTest> 0017 #include <kcoreaddons_export.h> 0018 0019 namespace QTest 0020 { 0021 template<> 0022 inline char *toString(const QJsonValue &val) 0023 { 0024 // simply reuse the QDebug representation 0025 QString result; 0026 QDebug(&result) << val; 0027 return QTest::toString(result); 0028 } 0029 0030 } 0031 0032 class DesktopToJsonTest : public QObject 0033 { 0034 Q_OBJECT 0035 0036 private: 0037 void compareJson(const QJsonObject &actual, const QJsonObject &expected) 0038 { 0039 for (auto it = actual.constBegin(); it != actual.constEnd(); ++it) { 0040 if (expected.constFind(it.key()) == expected.constEnd()) { 0041 qCritical() << "Result has key" << it.key() << "which is not expected!"; 0042 QFAIL("Invalid output"); 0043 } 0044 if (it.value().isObject() && expected.value(it.key()).isObject()) { 0045 compareJson(it.value().toObject(), expected.value(it.key()).toObject()); 0046 } else { 0047 QCOMPARE(it.value(), expected.value(it.key())); 0048 } 0049 } 0050 for (auto it = expected.constBegin(); it != expected.constEnd(); ++it) { 0051 if (actual.constFind(it.key()) == actual.constEnd()) { 0052 qCritical() << "Result is missing key" << it.key(); 0053 QFAIL("Invalid output"); 0054 } 0055 if (it.value().isObject() && actual.value(it.key()).isObject()) { 0056 compareJson(it.value().toObject(), actual.value(it.key()).toObject()); 0057 } else { 0058 QCOMPARE(it.value(), actual.value(it.key())); 0059 } 0060 } 0061 } 0062 0063 private Q_SLOTS: 0064 0065 void testDesktopToJson_data() 0066 { 0067 QTest::addColumn<QByteArray>("input"); 0068 QTest::addColumn<QJsonObject>("expectedResult"); 0069 QTest::addColumn<bool>("compatibilityMode"); 0070 QTest::addColumn<QStringList>("serviceTypes"); 0071 0072 QJsonObject expectedResult; 0073 QJsonObject kpluginObj; 0074 QByteArray input = 0075 // include an insignificant group 0076 "[Some Group]\n" 0077 "Foo=Bar\n" 0078 "\n" 0079 "[Desktop Entry]\n" 0080 // only data inside [Desktop Entry] should be included 0081 "Name=Example\n" 0082 // empty lines 0083 "\n" 0084 " \n" 0085 // make sure translations are included: 0086 "Name[de_DE]=Beispiel\n" 0087 // ignore comments: 0088 "#Comment=Comment\n" 0089 " #Comment=Comment\n" 0090 "Categories=foo;bar;a\\;b\n" 0091 // As the case is significant, the keys Name and NAME are not equivalent: 0092 "CaseSensitive=ABC\n" 0093 "CASESENSITIVE=abc\n" 0094 // Space before and after the equals sign should be ignored: 0095 "SpacesBeforeEq =foo\n" 0096 "SpacesAfterEq= foo\n" 0097 // Space before and after the equals sign should be ignored; the = sign is the actual delimiter. 0098 // TODO: error in spec (spaces before and after the key??) 0099 " SpacesBeforeKey=foo\n" 0100 "SpacesAfterKey =foo\n" 0101 // ignore trailing spaces 0102 "TrailingSpaces=foo \n" 0103 // However spaces in the value are significant: 0104 "SpacesInValue=Hello, World!\n" 0105 // The escape sequences \s, \n, \t, \r, and \\ are supported for values of 0106 // type string and localestring, meaning ASCII space, newline, tab, 0107 // carriage return, and backslash, respectively: 0108 "EscapeSequences=So\\sme esc\\nap\\te se\\\\qu\\re\\\\nces\n" // make sure that the last n is a literal n not a newline! 0109 // the standard keys that are used by plugins, make sure correct types are used: 0110 "X-KDE-PluginInfo-Category=Examples\n" // string key 0111 "X-KDE-PluginInfo-Version=1.0\n" 0112 // The multiple values should be separated by a semicolon and the value of the key 0113 // may be optionally terminated by a semicolon. Trailing empty strings must always 0114 // be terminated with a semicolon. Semicolons in these values need to be escaped using \;. 0115 #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 79) 0116 "X-KDE-PluginInfo-Depends=foo,bar,esc\\,aped\n" // string list key 0117 #endif 0118 "X-KDE-ServiceTypes=\n" // empty string list 0119 "X-KDE-PluginInfo-EnabledByDefault=true\n" // bool key 0120 // now start a new group 0121 "[New Group]\n" 0122 "InWrongGroup=true\n"; 0123 0124 expectedResult[QStringLiteral("Categories")] = QStringLiteral("foo;bar;a\\;b"); 0125 expectedResult[QStringLiteral("CaseSensitive")] = QStringLiteral("ABC"); 0126 expectedResult[QStringLiteral("CASESENSITIVE")] = QStringLiteral("abc"); 0127 expectedResult[QStringLiteral("SpacesBeforeEq")] = QStringLiteral("foo"); 0128 expectedResult[QStringLiteral("SpacesAfterEq")] = QStringLiteral("foo"); 0129 expectedResult[QStringLiteral("SpacesBeforeKey")] = QStringLiteral("foo"); 0130 expectedResult[QStringLiteral("SpacesAfterKey")] = QStringLiteral("foo"); 0131 expectedResult[QStringLiteral("TrailingSpaces")] = QStringLiteral("foo"); 0132 expectedResult[QStringLiteral("SpacesInValue")] = QStringLiteral("Hello, World!"); 0133 expectedResult[QStringLiteral("EscapeSequences")] = QStringLiteral("So me esc\nap\te se\\qu\re\\nces"); 0134 kpluginObj[QStringLiteral("Name")] = QStringLiteral("Example"); 0135 kpluginObj[QStringLiteral("Name[de_DE]")] = QStringLiteral("Beispiel"); 0136 kpluginObj[QStringLiteral("Category")] = QStringLiteral("Examples"); 0137 #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 79) 0138 kpluginObj[QStringLiteral("Dependencies")] = 0139 QJsonArray::fromStringList(QStringList() << QStringLiteral("foo") << QStringLiteral("bar") << QStringLiteral("esc,aped")); 0140 #endif 0141 kpluginObj[QStringLiteral("ServiceTypes")] = QJsonArray::fromStringList(QStringList()); 0142 kpluginObj[QStringLiteral("EnabledByDefault")] = true; 0143 kpluginObj[QStringLiteral("Version")] = QStringLiteral("1.0"); 0144 QJsonObject compatResult = expectedResult; 0145 compatResult[QStringLiteral("Name")] = QStringLiteral("Example"); 0146 compatResult[QStringLiteral("Name[de_DE]")] = QStringLiteral("Beispiel"); 0147 compatResult[QStringLiteral("X-KDE-PluginInfo-Category")] = QStringLiteral("Examples"); 0148 compatResult[QStringLiteral("X-KDE-PluginInfo-Version")] = QStringLiteral("1.0"); 0149 #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 79) 0150 compatResult[QStringLiteral("X-KDE-PluginInfo-Depends")] = 0151 QJsonArray::fromStringList(QStringList() << QStringLiteral("foo") << QStringLiteral("bar") << QStringLiteral("esc,aped")); 0152 #endif 0153 compatResult[QStringLiteral("X-KDE-ServiceTypes")] = QJsonArray::fromStringList(QStringList()); 0154 compatResult[QStringLiteral("X-KDE-PluginInfo-EnabledByDefault")] = true; 0155 0156 expectedResult[QStringLiteral("KPlugin")] = kpluginObj; 0157 0158 QTest::newRow("newFormat") << input << expectedResult << false << QStringList(); 0159 QTest::newRow("compatFormat") << input << compatResult << true << QStringList(); 0160 0161 // test conversion of a currently existing .desktop file (excluding most of the translations): 0162 QByteArray kdevInput = 0163 "[Desktop Entry]\n" 0164 "Type = Service\n" 0165 "Icon=text-x-c++src\n" 0166 "Exec=blubb\n" 0167 "Comment=C/C++ Language Support\n" 0168 "Comment[fr]=Prise en charge du langage C/C++\n" 0169 "Comment[it]=Supporto al linguaggio C/C++\n" 0170 "Name=C++ Support\n" 0171 "Name[fi]=C++-tuki\n" 0172 "Name[fr]=Prise en charge du C++\n" 0173 "GenericName=Language Support\n" 0174 "GenericName[sl]=Podpora jeziku\n" 0175 "ServiceTypes=KDevelop/NonExistentPlugin\n" 0176 "X-KDE-Library=kdevcpplanguagesupport\n" 0177 "X-KDE-PluginInfo-Name=kdevcppsupport\n" 0178 "X-KDE-PluginInfo-Category=Language Support\n" 0179 "X-KDevelop-Version=1\n" 0180 "X-KDevelop-Language=C++\n" 0181 "X-KDevelop-Args=CPP\n" 0182 "X-KDevelop-Interfaces=ILanguageSupport\n" 0183 "X-KDevelop-SupportedMimeTypes=text/x-chdr,text/x-c++hdr,text/x-csrc,text/x-c++src\n" 0184 "X-KDevelop-Mode=NoGUI\n" 0185 "X-KDevelop-LoadMode=AlwaysOn"; 0186 0187 QJsonParseError e; 0188 QJsonObject kdevExpected = QJsonDocument::fromJson( 0189 "{\n" 0190 " \"GenericName\": \"Language Support\",\n" 0191 " \"GenericName[sl]\": \"Podpora jeziku\",\n" 0192 " \"KPlugin\": {\n" 0193 " \"Category\": \"Language Support\",\n" 0194 " \"Description\": \"C/C++ Language Support\",\n" 0195 " \"Description[fr]\": \"Prise en charge du langage C/C++\",\n" 0196 " \"Description[it]\": \"Supporto al linguaggio C/C++\",\n" 0197 " \"Icon\": \"text-x-c++src\",\n" 0198 " \"Id\": \"kdevcppsupport\",\n" 0199 " \"Name\": \"C++ Support\",\n" 0200 " \"Name[fi]\": \"C++-tuki\",\n" 0201 " \"Name[fr]\": \"Prise en charge du C++\",\n" 0202 " \"ServiceTypes\": [ \"KDevelop/NonExistentPlugin\" ]\n" 0203 " },\n" 0204 " \"X-KDevelop-Args\": \"CPP\",\n" 0205 " \"X-KDevelop-Interfaces\": \"ILanguageSupport\",\n" 0206 " \"X-KDevelop-Language\": \"C++\",\n" 0207 " \"X-KDevelop-LoadMode\": \"AlwaysOn\",\n" 0208 " \"X-KDevelop-Mode\": \"NoGUI\",\n" 0209 " \"X-KDevelop-SupportedMimeTypes\": \"text/x-chdr,text/x-c++hdr,text/x-csrc,text/x-c++src\",\n" 0210 " \"X-KDevelop-Version\": \"1\"\n" 0211 "}\n", 0212 &e) 0213 .object(); 0214 QCOMPARE(e.error, QJsonParseError::NoError); 0215 QTest::newRow("kdevcpplanguagesupport no servicetype") << kdevInput << kdevExpected << false << QStringList(); 0216 0217 QJsonObject kdevExpectedWithServiceType = 0218 QJsonDocument::fromJson( 0219 "{\n" 0220 " \"GenericName\": \"Language Support\",\n" 0221 " \"GenericName[sl]\": \"Podpora jeziku\",\n" 0222 " \"KPlugin\": {\n" 0223 " \"Category\": \"Language Support\",\n" 0224 " \"Description\": \"C/C++ Language Support\",\n" 0225 " \"Description[fr]\": \"Prise en charge du langage C/C++\",\n" 0226 " \"Description[it]\": \"Supporto al linguaggio C/C++\",\n" 0227 " \"Icon\": \"text-x-c++src\",\n" 0228 " \"Id\": \"kdevcppsupport\",\n" 0229 " \"Name\": \"C++ Support\",\n" 0230 " \"Name[fi]\": \"C++-tuki\",\n" 0231 " \"Name[fr]\": \"Prise en charge du C++\",\n" 0232 " \"ServiceTypes\": [ \"KDevelop/NonExistentPlugin\" ]\n" 0233 " },\n" 0234 " \"X-KDevelop-Args\": \"CPP\",\n" 0235 " \"X-KDevelop-Interfaces\": [\"ILanguageSupport\"],\n" 0236 " \"X-KDevelop-Language\": \"C++\",\n" 0237 " \"X-KDevelop-LoadMode\": \"AlwaysOn\",\n" 0238 " \"X-KDevelop-Mode\": \"NoGUI\",\n" 0239 " \"X-KDevelop-SupportedMimeTypes\": [\"text/x-chdr\", \"text/x-c++hdr\", \"text/x-csrc\", \"text/x-c++src\"],\n" 0240 " \"X-KDevelop-Version\": 1\n" 0241 "}\n", 0242 &e) 0243 .object(); 0244 QCOMPARE(e.error, QJsonParseError::NoError); 0245 const QString kdevServiceTypePath = QFINDTESTDATA("data/servicetypes/fake-kdevelopplugin.desktop"); 0246 QVERIFY(!kdevServiceTypePath.isEmpty()); 0247 QTest::newRow("kdevcpplanguagesupport with servicetype") << kdevInput << kdevExpectedWithServiceType << false << QStringList(kdevServiceTypePath); 0248 // test conversion of the X-KDE-PluginInfo-Author + X-KDE-PluginInfo-Email key: 0249 QByteArray authorInput = 0250 "[Desktop Entry]\n" 0251 "Type=Service\n" 0252 "X-KDE-PluginInfo-Author=Foo Bar\n" 0253 "X-KDE-PluginInfo-Email=foo.bar@baz.com\n"; 0254 0255 QJsonObject authorsExpected = QJsonDocument::fromJson( 0256 "{\n" 0257 " \"KPlugin\": {\n" 0258 " \"Authors\": [ { \"Name\": \"Foo Bar\", \"Email\": \"foo.bar@baz.com\" } ]\n" 0259 " }\n }\n", 0260 &e) 0261 .object(); 0262 QCOMPARE(e.error, QJsonParseError::NoError); 0263 QTest::newRow("authors") << authorInput << authorsExpected << false << QStringList(); 0264 0265 // test case-insensitive conversion of boolean keys 0266 const QString boolServiceType = QFINDTESTDATA("data/servicetypes/bool-servicetype.desktop"); 0267 QVERIFY(!boolServiceType.isEmpty()); 0268 0269 QByteArray boolInput1 = "[Desktop Entry]\nType=Service\nX-Test-Bool=true\n"; 0270 QByteArray boolInput2 = "[Desktop Entry]\nType=Service\nX-Test-Bool=TRue\n"; 0271 QByteArray boolInput3 = "[Desktop Entry]\nType=Service\nX-Test-Bool=false\n"; 0272 QByteArray boolInput4 = "[Desktop Entry]\nType=Service\nX-Test-Bool=FALse\n"; 0273 0274 auto boolResultTrue = QJsonDocument::fromJson("{\"KPlugin\":{},\"X-Test-Bool\": true}", &e).object(); 0275 QCOMPARE(e.error, QJsonParseError::NoError); 0276 auto boolResultFalse = QJsonDocument::fromJson("{\"KPlugin\":{},\"X-Test-Bool\": false}", &e).object(); 0277 QCOMPARE(e.error, QJsonParseError::NoError); 0278 QTest::newRow("bool true") << boolInput1 << boolResultTrue << false << QStringList(boolServiceType); 0279 QTest::newRow("bool TRue") << boolInput2 << boolResultTrue << false << QStringList(boolServiceType); 0280 QTest::newRow("bool false") << boolInput3 << boolResultFalse << false << QStringList(boolServiceType); 0281 QTest::newRow("bool FALse") << boolInput4 << boolResultFalse << false << QStringList(boolServiceType); 0282 0283 // test conversion of kcookiejar.desktop (for some reason the wrong boolean values were committed) 0284 QByteArray kcookiejarInput = 0285 "[Desktop Entry]\n" 0286 "Type= Service\n" 0287 "Name=Cookie Jar\n" 0288 "Comment=Stores network cookies\n" 0289 "X-KDE-ServiceTypes=KDEDModule\n" 0290 "X-KDE-Library=kf5/kded/kcookiejar\n" 0291 "X-KDE-Kded-autoload=false\n" 0292 "X-KDE-Kded-load-on-demand=true\n"; 0293 auto kcookiejarResult = QJsonDocument::fromJson( 0294 "{\n" 0295 " \"KPlugin\": {\n" 0296 " \"Description\": \"Stores network cookies\",\n" 0297 " \"Name\": \"Cookie Jar\",\n" 0298 " \"ServiceTypes\": [\n" 0299 " \"KDEDModule\"\n" 0300 " ]\n" 0301 " },\n" 0302 "\"X-KDE-Kded-autoload\": false,\n" 0303 "\"X-KDE-Kded-load-on-demand\": true\n" 0304 "}\n", 0305 &e) 0306 .object(); 0307 const QString kdedmoduleServiceType = QFINDTESTDATA("data/servicetypes/fake-kdedmodule.desktop"); 0308 QVERIFY(!kdedmoduleServiceType.isEmpty()); 0309 QTest::newRow("kcookiejar") << kcookiejarInput << kcookiejarResult << false << QStringList(kdedmoduleServiceType); 0310 } 0311 0312 void testDesktopToJson() 0313 { 0314 QTemporaryFile output; 0315 QTemporaryFile inputFile; 0316 QVERIFY(inputFile.open()); 0317 QVERIFY(output.open()); // create the file 0318 QFETCH(QByteArray, input); 0319 QFETCH(QJsonObject, expectedResult); 0320 QFETCH(bool, compatibilityMode); 0321 QFETCH(QStringList, serviceTypes); 0322 output.close(); 0323 inputFile.write(input); 0324 inputFile.flush(); 0325 inputFile.close(); 0326 0327 QProcess proc; 0328 proc.setProgram(QStringLiteral(DESKTOP_TO_JSON_EXE)); 0329 QStringList arguments = QStringList() << QStringLiteral("-i") << inputFile.fileName() << QStringLiteral("-o") << output.fileName(); 0330 if (compatibilityMode) { 0331 arguments << QStringLiteral("-c"); 0332 } 0333 for (const QString &s : std::as_const(serviceTypes)) { 0334 arguments << QStringLiteral("-s") << s; 0335 } 0336 proc.setArguments(arguments); 0337 proc.start(); 0338 QVERIFY(proc.waitForFinished(10000)); 0339 QByteArray errorOut = proc.readAllStandardError(); 0340 if (!errorOut.isEmpty()) { 0341 qCWarning(KCOREADDONS_DEBUG).nospace() << "desktoptojson STDERR:\n\n" << errorOut.constData() << "\n"; 0342 } 0343 QCOMPARE(proc.exitCode(), 0); 0344 QVERIFY(output.open()); 0345 QByteArray jsonString = output.readAll(); 0346 QJsonParseError e; 0347 QJsonDocument doc = QJsonDocument::fromJson(jsonString, &e); 0348 QCOMPARE(e.error, QJsonParseError::NoError); 0349 QJsonObject result = doc.object(); 0350 compareJson(result, expectedResult); 0351 QVERIFY(!QTest::currentTestFailed()); 0352 } 0353 }; 0354 0355 QTEST_MAIN(DesktopToJsonTest) 0356 0357 #include "desktoptojsontest.moc"