desktoptojsontest.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. /*
  2. This file is part of the KDE project
  3. SPDX-FileCopyrightText: 2014 Alex Richardson <arichardson.kde@gmail.com>
  4. SPDX-License-Identifier: LGPL-2.0-or-later
  5. */
  6. #include "kcoreaddons_debug.h"
  7. #include <QDebug>
  8. #include <QJsonArray>
  9. #include <QJsonDocument>
  10. #include <QJsonObject>
  11. #include <QObject>
  12. #include <QProcess>
  13. #include <QTemporaryFile>
  14. #include <QTest>
  15. #include <kcoreaddons_export.h>
  16. namespace QTest
  17. {
  18. template<>
  19. inline char *toString(const QJsonValue &val)
  20. {
  21. // simply reuse the QDebug representation
  22. QString result;
  23. QDebug(&result) << val;
  24. return QTest::toString(result);
  25. }
  26. }
  27. class DesktopToJsonTest : public QObject
  28. {
  29. Q_OBJECT
  30. private:
  31. void compareJson(const QJsonObject &actual, const QJsonObject &expected)
  32. {
  33. for (auto it = actual.constBegin(); it != actual.constEnd(); ++it) {
  34. if (expected.constFind(it.key()) == expected.constEnd()) {
  35. qCritical() << "Result has key" << it.key() << "which is not expected!";
  36. QFAIL("Invalid output");
  37. }
  38. if (it.value().isObject() && expected.value(it.key()).isObject()) {
  39. compareJson(it.value().toObject(), expected.value(it.key()).toObject());
  40. } else {
  41. QCOMPARE(it.value(), expected.value(it.key()));
  42. }
  43. }
  44. for (auto it = expected.constBegin(); it != expected.constEnd(); ++it) {
  45. if (actual.constFind(it.key()) == actual.constEnd()) {
  46. qCritical() << "Result is missing key" << it.key();
  47. QFAIL("Invalid output");
  48. }
  49. if (it.value().isObject() && actual.value(it.key()).isObject()) {
  50. compareJson(it.value().toObject(), actual.value(it.key()).toObject());
  51. } else {
  52. QCOMPARE(it.value(), actual.value(it.key()));
  53. }
  54. }
  55. }
  56. private Q_SLOTS:
  57. void testDesktopToJson_data()
  58. {
  59. QTest::addColumn<QByteArray>("input");
  60. QTest::addColumn<QJsonObject>("expectedResult");
  61. QTest::addColumn<bool>("compatibilityMode");
  62. QTest::addColumn<QStringList>("serviceTypes");
  63. QJsonObject expectedResult;
  64. QJsonObject kpluginObj;
  65. QByteArray input =
  66. // include an insignificant group
  67. "[Some Group]\n"
  68. "Foo=Bar\n"
  69. "\n"
  70. "[Desktop Entry]\n"
  71. // only data inside [Desktop Entry] should be included
  72. "Name=Example\n"
  73. // empty lines
  74. "\n"
  75. " \n"
  76. // make sure translations are included:
  77. "Name[de_DE]=Beispiel\n"
  78. // ignore comments:
  79. "#Comment=Comment\n"
  80. " #Comment=Comment\n"
  81. "Categories=foo;bar;a\\;b\n"
  82. // As the case is significant, the keys Name and NAME are not equivalent:
  83. "CaseSensitive=ABC\n"
  84. "CASESENSITIVE=abc\n"
  85. // Space before and after the equals sign should be ignored:
  86. "SpacesBeforeEq =foo\n"
  87. "SpacesAfterEq= foo\n"
  88. // Space before and after the equals sign should be ignored; the = sign is the actual delimiter.
  89. // TODO: error in spec (spaces before and after the key??)
  90. " SpacesBeforeKey=foo\n"
  91. "SpacesAfterKey =foo\n"
  92. // ignore trailing spaces
  93. "TrailingSpaces=foo \n"
  94. // However spaces in the value are significant:
  95. "SpacesInValue=Hello, World!\n"
  96. // The escape sequences \s, \n, \t, \r, and \\ are supported for values of
  97. // type string and localestring, meaning ASCII space, newline, tab,
  98. // carriage return, and backslash, respectively:
  99. "EscapeSequences=So\\sme esc\\nap\\te se\\\\qu\\re\\\\nces\n" // make sure that the last n is a literal n not a newline!
  100. // the standard keys that are used by plugins, make sure correct types are used:
  101. "X-KDE-PluginInfo-Category=Examples\n" // string key
  102. "X-KDE-PluginInfo-Version=1.0\n"
  103. // The multiple values should be separated by a semicolon and the value of the key
  104. // may be optionally terminated by a semicolon. Trailing empty strings must always
  105. // be terminated with a semicolon. Semicolons in these values need to be escaped using \;.
  106. #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 79)
  107. "X-KDE-PluginInfo-Depends=foo,bar,esc\\,aped\n" // string list key
  108. #endif
  109. "X-KDE-ServiceTypes=\n" // empty string list
  110. "X-KDE-PluginInfo-EnabledByDefault=true\n" // bool key
  111. // now start a new group
  112. "[New Group]\n"
  113. "InWrongGroup=true\n";
  114. expectedResult[QStringLiteral("Categories")] = QStringLiteral("foo;bar;a\\;b");
  115. expectedResult[QStringLiteral("CaseSensitive")] = QStringLiteral("ABC");
  116. expectedResult[QStringLiteral("CASESENSITIVE")] = QStringLiteral("abc");
  117. expectedResult[QStringLiteral("SpacesBeforeEq")] = QStringLiteral("foo");
  118. expectedResult[QStringLiteral("SpacesAfterEq")] = QStringLiteral("foo");
  119. expectedResult[QStringLiteral("SpacesBeforeKey")] = QStringLiteral("foo");
  120. expectedResult[QStringLiteral("SpacesAfterKey")] = QStringLiteral("foo");
  121. expectedResult[QStringLiteral("TrailingSpaces")] = QStringLiteral("foo");
  122. expectedResult[QStringLiteral("SpacesInValue")] = QStringLiteral("Hello, World!");
  123. expectedResult[QStringLiteral("EscapeSequences")] = QStringLiteral("So me esc\nap\te se\\qu\re\\nces");
  124. kpluginObj[QStringLiteral("Name")] = QStringLiteral("Example");
  125. kpluginObj[QStringLiteral("Name[de_DE]")] = QStringLiteral("Beispiel");
  126. kpluginObj[QStringLiteral("Category")] = QStringLiteral("Examples");
  127. #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 79)
  128. kpluginObj[QStringLiteral("Dependencies")] =
  129. QJsonArray::fromStringList(QStringList() << QStringLiteral("foo") << QStringLiteral("bar") << QStringLiteral("esc,aped"));
  130. #endif
  131. kpluginObj[QStringLiteral("ServiceTypes")] = QJsonArray::fromStringList(QStringList());
  132. kpluginObj[QStringLiteral("EnabledByDefault")] = true;
  133. kpluginObj[QStringLiteral("Version")] = QStringLiteral("1.0");
  134. QJsonObject compatResult = expectedResult;
  135. compatResult[QStringLiteral("Name")] = QStringLiteral("Example");
  136. compatResult[QStringLiteral("Name[de_DE]")] = QStringLiteral("Beispiel");
  137. compatResult[QStringLiteral("X-KDE-PluginInfo-Category")] = QStringLiteral("Examples");
  138. compatResult[QStringLiteral("X-KDE-PluginInfo-Version")] = QStringLiteral("1.0");
  139. #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 79)
  140. compatResult[QStringLiteral("X-KDE-PluginInfo-Depends")] =
  141. QJsonArray::fromStringList(QStringList() << QStringLiteral("foo") << QStringLiteral("bar") << QStringLiteral("esc,aped"));
  142. #endif
  143. compatResult[QStringLiteral("X-KDE-ServiceTypes")] = QJsonArray::fromStringList(QStringList());
  144. compatResult[QStringLiteral("X-KDE-PluginInfo-EnabledByDefault")] = true;
  145. expectedResult[QStringLiteral("KPlugin")] = kpluginObj;
  146. QTest::newRow("newFormat") << input << expectedResult << false << QStringList();
  147. QTest::newRow("compatFormat") << input << compatResult << true << QStringList();
  148. // test conversion of a currently existing .desktop file (excluding most of the translations):
  149. QByteArray kdevInput =
  150. "[Desktop Entry]\n"
  151. "Type = Service\n"
  152. "Icon=text-x-c++src\n"
  153. "Exec=blubb\n"
  154. "Comment=C/C++ Language Support\n"
  155. "Comment[fr]=Prise en charge du langage C/C++\n"
  156. "Comment[it]=Supporto al linguaggio C/C++\n"
  157. "Name=C++ Support\n"
  158. "Name[fi]=C++-tuki\n"
  159. "Name[fr]=Prise en charge du C++\n"
  160. "GenericName=Language Support\n"
  161. "GenericName[sl]=Podpora jeziku\n"
  162. "ServiceTypes=KDevelop/NonExistentPlugin\n"
  163. "X-KDE-Library=kdevcpplanguagesupport\n"
  164. "X-KDE-PluginInfo-Name=kdevcppsupport\n"
  165. "X-KDE-PluginInfo-Category=Language Support\n"
  166. "X-KDevelop-Version=1\n"
  167. "X-KDevelop-Language=C++\n"
  168. "X-KDevelop-Args=CPP\n"
  169. "X-KDevelop-Interfaces=ILanguageSupport\n"
  170. "X-KDevelop-SupportedMimeTypes=text/x-chdr,text/x-c++hdr,text/x-csrc,text/x-c++src\n"
  171. "X-KDevelop-Mode=NoGUI\n"
  172. "X-KDevelop-LoadMode=AlwaysOn";
  173. QJsonParseError e;
  174. QJsonObject kdevExpected = QJsonDocument::fromJson(
  175. "{\n"
  176. " \"GenericName\": \"Language Support\",\n"
  177. " \"GenericName[sl]\": \"Podpora jeziku\",\n"
  178. " \"KPlugin\": {\n"
  179. " \"Category\": \"Language Support\",\n"
  180. " \"Description\": \"C/C++ Language Support\",\n"
  181. " \"Description[fr]\": \"Prise en charge du langage C/C++\",\n"
  182. " \"Description[it]\": \"Supporto al linguaggio C/C++\",\n"
  183. " \"Icon\": \"text-x-c++src\",\n"
  184. " \"Id\": \"kdevcppsupport\",\n"
  185. " \"Name\": \"C++ Support\",\n"
  186. " \"Name[fi]\": \"C++-tuki\",\n"
  187. " \"Name[fr]\": \"Prise en charge du C++\",\n"
  188. " \"ServiceTypes\": [ \"KDevelop/NonExistentPlugin\" ]\n"
  189. " },\n"
  190. " \"X-KDevelop-Args\": \"CPP\",\n"
  191. " \"X-KDevelop-Interfaces\": \"ILanguageSupport\",\n"
  192. " \"X-KDevelop-Language\": \"C++\",\n"
  193. " \"X-KDevelop-LoadMode\": \"AlwaysOn\",\n"
  194. " \"X-KDevelop-Mode\": \"NoGUI\",\n"
  195. " \"X-KDevelop-SupportedMimeTypes\": \"text/x-chdr,text/x-c++hdr,text/x-csrc,text/x-c++src\",\n"
  196. " \"X-KDevelop-Version\": \"1\"\n"
  197. "}\n",
  198. &e)
  199. .object();
  200. QCOMPARE(e.error, QJsonParseError::NoError);
  201. QTest::newRow("kdevcpplanguagesupport no servicetype") << kdevInput << kdevExpected << false << QStringList();
  202. QJsonObject kdevExpectedWithServiceType =
  203. QJsonDocument::fromJson(
  204. "{\n"
  205. " \"GenericName\": \"Language Support\",\n"
  206. " \"GenericName[sl]\": \"Podpora jeziku\",\n"
  207. " \"KPlugin\": {\n"
  208. " \"Category\": \"Language Support\",\n"
  209. " \"Description\": \"C/C++ Language Support\",\n"
  210. " \"Description[fr]\": \"Prise en charge du langage C/C++\",\n"
  211. " \"Description[it]\": \"Supporto al linguaggio C/C++\",\n"
  212. " \"Icon\": \"text-x-c++src\",\n"
  213. " \"Id\": \"kdevcppsupport\",\n"
  214. " \"Name\": \"C++ Support\",\n"
  215. " \"Name[fi]\": \"C++-tuki\",\n"
  216. " \"Name[fr]\": \"Prise en charge du C++\",\n"
  217. " \"ServiceTypes\": [ \"KDevelop/NonExistentPlugin\" ]\n"
  218. " },\n"
  219. " \"X-KDevelop-Args\": \"CPP\",\n"
  220. " \"X-KDevelop-Interfaces\": [\"ILanguageSupport\"],\n"
  221. " \"X-KDevelop-Language\": \"C++\",\n"
  222. " \"X-KDevelop-LoadMode\": \"AlwaysOn\",\n"
  223. " \"X-KDevelop-Mode\": \"NoGUI\",\n"
  224. " \"X-KDevelop-SupportedMimeTypes\": [\"text/x-chdr\", \"text/x-c++hdr\", \"text/x-csrc\", \"text/x-c++src\"],\n"
  225. " \"X-KDevelop-Version\": 1\n"
  226. "}\n",
  227. &e)
  228. .object();
  229. QCOMPARE(e.error, QJsonParseError::NoError);
  230. const QString kdevServiceTypePath = QFINDTESTDATA("data/servicetypes/fake-kdevelopplugin.desktop");
  231. QVERIFY(!kdevServiceTypePath.isEmpty());
  232. QTest::newRow("kdevcpplanguagesupport with servicetype") << kdevInput << kdevExpectedWithServiceType << false << QStringList(kdevServiceTypePath);
  233. // test conversion of the X-KDE-PluginInfo-Author + X-KDE-PluginInfo-Email key:
  234. QByteArray authorInput =
  235. "[Desktop Entry]\n"
  236. "Type=Service\n"
  237. "X-KDE-PluginInfo-Author=Foo Bar\n"
  238. "X-KDE-PluginInfo-Email=foo.bar@baz.com\n";
  239. QJsonObject authorsExpected = QJsonDocument::fromJson(
  240. "{\n"
  241. " \"KPlugin\": {\n"
  242. " \"Authors\": [ { \"Name\": \"Foo Bar\", \"Email\": \"foo.bar@baz.com\" } ]\n"
  243. " }\n }\n",
  244. &e)
  245. .object();
  246. QCOMPARE(e.error, QJsonParseError::NoError);
  247. QTest::newRow("authors") << authorInput << authorsExpected << false << QStringList();
  248. // test case-insensitive conversion of boolean keys
  249. const QString boolServiceType = QFINDTESTDATA("data/servicetypes/bool-servicetype.desktop");
  250. QVERIFY(!boolServiceType.isEmpty());
  251. QByteArray boolInput1 = "[Desktop Entry]\nType=Service\nX-Test-Bool=true\n";
  252. QByteArray boolInput2 = "[Desktop Entry]\nType=Service\nX-Test-Bool=TRue\n";
  253. QByteArray boolInput3 = "[Desktop Entry]\nType=Service\nX-Test-Bool=false\n";
  254. QByteArray boolInput4 = "[Desktop Entry]\nType=Service\nX-Test-Bool=FALse\n";
  255. auto boolResultTrue = QJsonDocument::fromJson("{\"KPlugin\":{},\"X-Test-Bool\": true}", &e).object();
  256. QCOMPARE(e.error, QJsonParseError::NoError);
  257. auto boolResultFalse = QJsonDocument::fromJson("{\"KPlugin\":{},\"X-Test-Bool\": false}", &e).object();
  258. QCOMPARE(e.error, QJsonParseError::NoError);
  259. QTest::newRow("bool true") << boolInput1 << boolResultTrue << false << QStringList(boolServiceType);
  260. QTest::newRow("bool TRue") << boolInput2 << boolResultTrue << false << QStringList(boolServiceType);
  261. QTest::newRow("bool false") << boolInput3 << boolResultFalse << false << QStringList(boolServiceType);
  262. QTest::newRow("bool FALse") << boolInput4 << boolResultFalse << false << QStringList(boolServiceType);
  263. // test conversion of kcookiejar.desktop (for some reason the wrong boolean values were committed)
  264. QByteArray kcookiejarInput =
  265. "[Desktop Entry]\n"
  266. "Type= Service\n"
  267. "Name=Cookie Jar\n"
  268. "Comment=Stores network cookies\n"
  269. "X-KDE-ServiceTypes=KDEDModule\n"
  270. "X-KDE-Library=kf5/kded/kcookiejar\n"
  271. "X-KDE-Kded-autoload=false\n"
  272. "X-KDE-Kded-load-on-demand=true\n";
  273. auto kcookiejarResult = QJsonDocument::fromJson(
  274. "{\n"
  275. " \"KPlugin\": {\n"
  276. " \"Description\": \"Stores network cookies\",\n"
  277. " \"Name\": \"Cookie Jar\",\n"
  278. " \"ServiceTypes\": [\n"
  279. " \"KDEDModule\"\n"
  280. " ]\n"
  281. " },\n"
  282. "\"X-KDE-Kded-autoload\": false,\n"
  283. "\"X-KDE-Kded-load-on-demand\": true\n"
  284. "}\n",
  285. &e)
  286. .object();
  287. const QString kdedmoduleServiceType = QFINDTESTDATA("data/servicetypes/fake-kdedmodule.desktop");
  288. QVERIFY(!kdedmoduleServiceType.isEmpty());
  289. QTest::newRow("kcookiejar") << kcookiejarInput << kcookiejarResult << false << QStringList(kdedmoduleServiceType);
  290. }
  291. void testDesktopToJson()
  292. {
  293. QTemporaryFile output;
  294. QTemporaryFile inputFile;
  295. QVERIFY(inputFile.open());
  296. QVERIFY(output.open()); // create the file
  297. QFETCH(QByteArray, input);
  298. QFETCH(QJsonObject, expectedResult);
  299. QFETCH(bool, compatibilityMode);
  300. QFETCH(QStringList, serviceTypes);
  301. output.close();
  302. inputFile.write(input);
  303. inputFile.flush();
  304. inputFile.close();
  305. QProcess proc;
  306. proc.setProgram(QStringLiteral(DESKTOP_TO_JSON_EXE));
  307. QStringList arguments = QStringList() << QStringLiteral("-i") << inputFile.fileName() << QStringLiteral("-o") << output.fileName();
  308. if (compatibilityMode) {
  309. arguments << QStringLiteral("-c");
  310. }
  311. for (const QString &s : std::as_const(serviceTypes)) {
  312. arguments << QStringLiteral("-s") << s;
  313. }
  314. proc.setArguments(arguments);
  315. proc.start();
  316. QVERIFY(proc.waitForFinished(10000));
  317. QByteArray errorOut = proc.readAllStandardError();
  318. if (!errorOut.isEmpty()) {
  319. qCWarning(KCOREADDONS_DEBUG).nospace() << "desktoptojson STDERR:\n\n" << errorOut.constData() << "\n";
  320. }
  321. QCOMPARE(proc.exitCode(), 0);
  322. QVERIFY(output.open());
  323. QByteArray jsonString = output.readAll();
  324. QJsonParseError e;
  325. QJsonDocument doc = QJsonDocument::fromJson(jsonString, &e);
  326. QCOMPARE(e.error, QJsonParseError::NoError);
  327. QJsonObject result = doc.object();
  328. compareJson(result, expectedResult);
  329. QVERIFY(!QTest::currentTestFailed());
  330. }
  331. };
  332. QTEST_MAIN(DesktopToJsonTest)
  333. #include "desktoptojsontest.moc"