parser_unittest.cc 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. // Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this
  5. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6. #include <gtest/gtest.h>
  7. #include <cc/data.h>
  8. #include <dhcp6/parser_context.h>
  9. using namespace isc::data;
  10. using namespace std;
  11. namespace {
  12. void compareJSON(ConstElementPtr a, ConstElementPtr b, bool print = true) {
  13. ASSERT_TRUE(a);
  14. ASSERT_TRUE(b);
  15. if (print) {
  16. // std::cout << "JSON A: -----" << endl << a->str() << std::endl;
  17. // std::cout << "JSON B: -----" << endl << b->str() << std::endl;
  18. // cout << "---------" << endl << endl;
  19. }
  20. EXPECT_EQ(a->str(), b->str());
  21. }
  22. void testParser(const std::string& txt, Parser6Context::ParserType parser_type) {
  23. ElementPtr reference_json;
  24. ConstElementPtr test_json;
  25. ASSERT_NO_THROW(reference_json = Element::fromJSON(txt, true));
  26. ASSERT_NO_THROW({
  27. try {
  28. Parser6Context ctx;
  29. test_json = ctx.parseString(txt, parser_type);
  30. } catch (const std::exception &e) {
  31. cout << "EXCEPTION: " << e.what() << endl;
  32. throw;
  33. }
  34. });
  35. // Now compare if both representations are the same.
  36. compareJSON(reference_json, test_json);
  37. }
  38. void testParser2(const std::string& txt, Parser6Context::ParserType parser_type) {
  39. ConstElementPtr test_json;
  40. ASSERT_NO_THROW({
  41. try {
  42. Parser6Context ctx;
  43. test_json = ctx.parseString(txt, parser_type);
  44. } catch (const std::exception &e) {
  45. cout << "EXCEPTION: " << e.what() << endl;
  46. throw;
  47. }
  48. });
  49. /// @todo: Implement actual validation here. since the original
  50. /// Element::fromJSON does not support several comment types, we don't
  51. /// have anything to compare with.
  52. /// std::cout << "Original text:" << txt << endl;
  53. /// std::cout << "Parsed text :" << test_json->str() << endl;
  54. }
  55. TEST(ParserTest, mapInMap) {
  56. string txt = "{ \"xyzzy\": { \"foo\": 123, \"baz\": 456 } }";
  57. testParser(txt, Parser6Context::SUBPARSER_JSON);
  58. }
  59. TEST(ParserTest, listInList) {
  60. string txt = "[ [ \"Britain\", \"Wales\", \"Scotland\" ], "
  61. "[ \"Pomorze\", \"Wielkopolska\", \"Tatry\"] ]";
  62. testParser(txt, Parser6Context::SUBPARSER_JSON);
  63. }
  64. TEST(ParserTest, nestedMaps) {
  65. string txt = "{ \"europe\": { \"UK\": { \"London\": { \"street\": \"221B Baker\" }}}}";
  66. testParser(txt, Parser6Context::SUBPARSER_JSON);
  67. }
  68. TEST(ParserTest, nestedLists) {
  69. string txt = "[ \"half\", [ \"quarter\", [ \"eighth\", [ \"sixteenth\" ]]]]";
  70. testParser(txt, Parser6Context::SUBPARSER_JSON);
  71. }
  72. TEST(ParserTest, listsInMaps) {
  73. string txt = "{ \"constellations\": { \"orion\": [ \"rigel\", \"betelguese\" ], "
  74. "\"cygnus\": [ \"deneb\", \"albireo\"] } }";
  75. testParser(txt, Parser6Context::SUBPARSER_JSON);
  76. }
  77. TEST(ParserTest, mapsInLists) {
  78. string txt = "[ { \"body\": \"earth\", \"gravity\": 1.0 },"
  79. " { \"body\": \"mars\", \"gravity\": 0.376 } ]";
  80. testParser(txt, Parser6Context::SUBPARSER_JSON);
  81. }
  82. TEST(ParserTest, types) {
  83. string txt = "{ \"string\": \"foo\","
  84. "\"integer\": 42,"
  85. "\"boolean\": true,"
  86. "\"map\": { \"foo\": \"bar\" },"
  87. "\"list\": [ 1, 2, 3 ],"
  88. "\"null\": null }";
  89. testParser(txt, Parser6Context::SUBPARSER_JSON);
  90. }
  91. TEST(ParserTest, keywordJSON) {
  92. string txt = "{ \"name\": \"user\","
  93. "\"type\": \"password\","
  94. "\"user\": \"name\","
  95. "\"password\": \"type\" }";
  96. testParser(txt, Parser6Context::SUBPARSER_JSON);
  97. }
  98. TEST(ParserTest, keywordDhcp6) {
  99. string txt = "{ \"Dhcp6\": { \"interfaces-config\": {"
  100. " \"interfaces\": [ \"type\", \"htype\" ] },\n"
  101. "\"preferred-lifetime\": 3000,\n"
  102. "\"rebind-timer\": 2000, \n"
  103. "\"renew-timer\": 1000, \n"
  104. "\"subnet6\": [ { "
  105. " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
  106. " \"subnet\": \"2001:db8:1::/48\", "
  107. " \"interface\": \"test\" } ],\n"
  108. "\"valid-lifetime\": 4000 } }";
  109. testParser2(txt, Parser6Context::PARSER_DHCP6);
  110. }
  111. TEST(ParserTest, bashComments) {
  112. string txt= "{ \"Dhcp6\": { \"interfaces-config\": {"
  113. " \"interfaces\": [ \"*\" ]"
  114. "},\n"
  115. "\"preferred-lifetime\": 3000,\n"
  116. "# this is a comment\n"
  117. "\"rebind-timer\": 2000, \n"
  118. "# lots of comments here\n"
  119. "# and here\n"
  120. "\"renew-timer\": 1000, \n"
  121. "\"subnet6\": [ { "
  122. " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
  123. " \"subnet\": \"2001:db8:1::/48\", "
  124. " \"interface\": \"eth0\""
  125. " } ],"
  126. "\"valid-lifetime\": 4000 } }";
  127. testParser2(txt, Parser6Context::PARSER_DHCP6);
  128. }
  129. TEST(ParserTest, cComments) {
  130. string txt= "{ \"Dhcp6\": { \"interfaces-config\": {"
  131. " \"interfaces\": [ \"*\" ]"
  132. "},\n"
  133. "\"preferred-lifetime\": 3000, // this is a comment \n"
  134. "\"rebind-timer\": 2000, // everything after // is ignored\n"
  135. "\"renew-timer\": 1000, // this will be ignored, too\n"
  136. "\"subnet6\": [ { "
  137. " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
  138. " \"subnet\": \"2001:db8:1::/48\", "
  139. " \"interface\": \"eth0\""
  140. " } ],"
  141. "\"valid-lifetime\": 4000 } }";
  142. testParser2(txt, Parser6Context::PARSER_DHCP6);
  143. }
  144. TEST(ParserTest, bashCommentsInline) {
  145. string txt= "{ \"Dhcp6\": { \"interfaces-config\": {"
  146. " \"interfaces\": [ \"*\" ]"
  147. "},\n"
  148. "\"preferred-lifetime\": 3000, # this is a comment \n"
  149. "\"rebind-timer\": 2000, # everything after # is ignored\n"
  150. "\"renew-timer\": 1000, # this will be ignored, too\n"
  151. "\"subnet6\": [ { "
  152. " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
  153. " \"subnet\": \"2001:db8:1::/48\", "
  154. " \"interface\": \"eth0\""
  155. " } ],"
  156. "\"valid-lifetime\": 4000 } }";
  157. testParser2(txt, Parser6Context::PARSER_DHCP6);
  158. }
  159. TEST(ParserTest, multilineComments) {
  160. string txt= "{ \"Dhcp6\": { \"interfaces-config\": {"
  161. " \"interfaces\": [ \"*\" ]"
  162. "},\n"
  163. "\"preferred-lifetime\": 3000, /* this is a C style comment\n"
  164. "that\n can \n span \n multiple \n lines */ \n"
  165. "\"rebind-timer\": 2000,\n"
  166. "\"renew-timer\": 1000, \n"
  167. "\"subnet6\": [ { "
  168. " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
  169. " \"subnet\": \"2001:db8:1::/48\", "
  170. " \"interface\": \"eth0\""
  171. " } ],"
  172. "\"valid-lifetime\": 4000 } }";
  173. testParser2(txt, Parser6Context::PARSER_DHCP6);
  174. }
  175. void testFile(const std::string& fname, bool print) {
  176. ElementPtr reference_json;
  177. ConstElementPtr test_json;
  178. cout << "Attempting to load file " << fname << endl;
  179. EXPECT_NO_THROW(reference_json = Element::fromJSONFile(fname, true));
  180. EXPECT_NO_THROW(
  181. try {
  182. Parser6Context ctx;
  183. test_json = ctx.parseFile(fname, Parser6Context::PARSER_DHCP6);
  184. } catch (const std::exception &x) {
  185. cout << "EXCEPTION: " << x.what() << endl;
  186. throw;
  187. });
  188. ASSERT_TRUE(reference_json);
  189. ASSERT_TRUE(test_json);
  190. compareJSON(reference_json, test_json, print);
  191. }
  192. // This test loads all available existing files. Each config is loaded
  193. // twice: first with the existing Element::fromJSONFile() and then
  194. // the second time with Parser6. Both JSON trees are then compared.
  195. TEST(ParserTest, file) {
  196. vector<string> configs;
  197. configs.push_back("advanced.json");
  198. configs.push_back("backends.json");
  199. configs.push_back("classify.json");
  200. configs.push_back("dhcpv4-over-dhcpv6.json");
  201. configs.push_back("duid.json");
  202. configs.push_back("hooks.json");
  203. configs.push_back("leases-expiration.json");
  204. configs.push_back("multiple-options.json");
  205. configs.push_back("mysql-reservations.json");
  206. configs.push_back("pgsql-reservations.json");
  207. configs.push_back("reservations.json");
  208. configs.push_back("several-subnets.json");
  209. configs.push_back("simple.json");
  210. configs.push_back("stateless.json");
  211. for (int i = 0; i<configs.size(); i++) {
  212. testFile(string(CFG_EXAMPLES) + "/" + configs[i], false);
  213. }
  214. }
  215. void testError(const std::string& txt,
  216. Parser6Context::ParserType parser_type,
  217. const std::string& msg)
  218. {
  219. try {
  220. Parser6Context ctx;
  221. ConstElementPtr parsed = ctx.parseString(txt, parser_type);
  222. FAIL() << "Expected Dhcp6ParseError but nothing was raised";
  223. }
  224. catch (const Dhcp6ParseError& ex) {
  225. EXPECT_EQ(msg, ex.what());
  226. }
  227. catch (...) {
  228. FAIL() << "Expected Dhcp6ParseError but something else was raised";
  229. }
  230. }
  231. // Check errors
  232. TEST(ParserTest, errors) {
  233. // no input
  234. testError("", Parser6Context::SUBPARSER_JSON,
  235. "<string>:1.1: syntax error, unexpected end of file");
  236. testError(" ", Parser6Context::SUBPARSER_JSON,
  237. "<string>:1.2: syntax error, unexpected end of file");
  238. testError("\n", Parser6Context::SUBPARSER_JSON,
  239. "<string>:2.1: syntax error, unexpected end of file");
  240. // comments
  241. testError("# nothing\n",
  242. Parser6Context::SUBPARSER_JSON,
  243. "<string>:2.1: syntax error, unexpected end of file, "
  244. "expecting {");
  245. testError(" #\n",
  246. Parser6Context::SUBPARSER_JSON,
  247. "<string>:2.1: syntax error, unexpected end of file, "
  248. "expecting {");
  249. testError("// nothing\n",
  250. Parser6Context::SUBPARSER_JSON,
  251. "<string>:2.1: syntax error, unexpected end of file, "
  252. "expecting {");
  253. testError("/* nothing */\n",
  254. Parser6Context::SUBPARSER_JSON,
  255. "<string>:2.1: syntax error, unexpected end of file, "
  256. "expecting {");
  257. testError("/* no\nthing */\n",
  258. Parser6Context::SUBPARSER_JSON,
  259. "<string>:3.1: syntax error, unexpected end of file, "
  260. "expecting {");
  261. testError("/* no\nthing */\n\n",
  262. Parser6Context::SUBPARSER_JSON,
  263. "<string>:4.1: syntax error, unexpected end of file, "
  264. "expecting {");
  265. testError("/* nothing\n",
  266. Parser6Context::SUBPARSER_JSON,
  267. "Comment not closed. (/* in line 1");
  268. testError("\n\n\n/* nothing\n",
  269. Parser6Context::SUBPARSER_JSON,
  270. "Comment not closed. (/* in line 4");
  271. testError("{ /* */*/ }\n",
  272. Parser6Context::SUBPARSER_JSON,
  273. "<string>:1.3-8: Invalid character: *");
  274. testError("{ /* // *// }\n",
  275. Parser6Context::SUBPARSER_JSON,
  276. "<string>:1.3-11: Invalid character: /");
  277. testError("{ /* // */// }\n",
  278. Parser6Context::SUBPARSER_JSON,
  279. "<string>:2.1: syntax error, unexpected end of file, "
  280. "expecting }");
  281. // includes
  282. testError("<?\n",
  283. Parser6Context::SUBPARSER_JSON,
  284. "Directive not closed.");
  285. testError("<?include\n",
  286. Parser6Context::SUBPARSER_JSON,
  287. "Directive not closed.");
  288. string file = string(CFG_EXAMPLES) + "/" + "stateless.json";
  289. testError("<?include \"" + file + "\"\n",
  290. Parser6Context::SUBPARSER_JSON,
  291. "Directive not closed.");
  292. testError("<?include \"/foo/bar\" ?>/n",
  293. Parser6Context::SUBPARSER_JSON,
  294. "Can't open include file /foo/bar");
  295. // numbers
  296. testError("123",
  297. Parser6Context::SUBPARSER_JSON,
  298. "<string>:1.1-3: syntax error, unexpected integer, "
  299. "expecting {");
  300. testError("-456",
  301. Parser6Context::SUBPARSER_JSON,
  302. "<string>:1.1-4: syntax error, unexpected integer, "
  303. "expecting {");
  304. testError("-0001",
  305. Parser6Context::SUBPARSER_JSON,
  306. "<string>:1.1-5: syntax error, unexpected integer, "
  307. "expecting {");
  308. testError("1234567890123456789012345678901234567890",
  309. Parser6Context::SUBPARSER_JSON,
  310. "<string>:1.1-40: Failed to convert "
  311. "1234567890123456789012345678901234567890"
  312. " to an integer.");
  313. testError("-3.14e+0",
  314. Parser6Context::SUBPARSER_JSON,
  315. "<string>:1.1-8: syntax error, unexpected floating point, "
  316. "expecting {");
  317. testError("1e50000",
  318. Parser6Context::SUBPARSER_JSON,
  319. "<string>:1.1-7: Failed to convert 1e50000 "
  320. "to a floating point.");
  321. // strings
  322. testError("\"aabb\"",
  323. Parser6Context::SUBPARSER_JSON,
  324. "<string>:1.1-6: syntax error, unexpected constant string, "
  325. "expecting {");
  326. testError("{ \"aabb\"err",
  327. Parser6Context::SUBPARSER_JSON,
  328. "<string>:1.9: Invalid character: e");
  329. testError("{ err\"aabb\"",
  330. Parser6Context::SUBPARSER_JSON,
  331. "<string>:1.3: Invalid character: e");
  332. testError("\"a\n\tb\"",
  333. Parser6Context::SUBPARSER_JSON,
  334. "<string>:1.1-6: Invalid control in \"a\n\tb\"");
  335. testError("\"a\\n\\tb\"",
  336. Parser6Context::SUBPARSER_JSON,
  337. "<string>:1.1-8: syntax error, unexpected constant string, "
  338. "expecting {");
  339. testError("\"a\\x01b\"",
  340. Parser6Context::SUBPARSER_JSON,
  341. "<string>:1.1-8: Bad escape in \"a\\x01b\"");
  342. testError("\"a\\u0062\"",
  343. Parser6Context::SUBPARSER_JSON,
  344. "<string>:1.1-9: Unsupported unicode escape in \"a\\u0062\"");
  345. testError("\"a\\u062z\"",
  346. Parser6Context::SUBPARSER_JSON,
  347. "<string>:1.1-9: Bad escape in \"a\\u062z\"");
  348. testError("\"abc\\\"",
  349. Parser6Context::SUBPARSER_JSON,
  350. "<string>:1.1-6: Overflow escape in \"abc\\\"");
  351. // from data_unittest.c
  352. testError("\\a",
  353. Parser6Context::SUBPARSER_JSON,
  354. "<string>:1.1: Invalid character: \\");
  355. testError("\\",
  356. Parser6Context::SUBPARSER_JSON,
  357. "<string>:1.1: Invalid character: \\");
  358. testError("\\\"\\\"",
  359. Parser6Context::SUBPARSER_JSON,
  360. "<string>:1.1: Invalid character: \\");
  361. // want a map
  362. testError("[]\n",
  363. Parser6Context::SUBPARSER_JSON,
  364. "<string>:1.1: syntax error, unexpected [, "
  365. "expecting {");
  366. testError("[]\n",
  367. Parser6Context::PARSER_DHCP6,
  368. "<string>:1.1: syntax error, unexpected [, "
  369. "expecting {");
  370. testError("{ 123 }\n",
  371. Parser6Context::SUBPARSER_JSON,
  372. "<string>:1.3-5: syntax error, unexpected integer, "
  373. "expecting }");
  374. testError("{ 123 }\n",
  375. Parser6Context::PARSER_DHCP6,
  376. "<string>:1.3-5: syntax error, unexpected integer");
  377. testError("{ \"foo\" }\n",
  378. Parser6Context::SUBPARSER_JSON,
  379. "<string>:1.9: syntax error, unexpected }, "
  380. "expecting :");
  381. testError("{ \"foo\" }\n",
  382. Parser6Context::PARSER_DHCP6,
  383. "<string>:1.9: syntax error, unexpected }, expecting :");
  384. testError("{ \"foo\":null }\n",
  385. Parser6Context::PARSER_DHCP6,
  386. "<string>:1.3-7: got unexpected keyword "
  387. "\"foo\" in toplevel map.");
  388. testError("{ \"Dhcp6\" }\n",
  389. Parser6Context::PARSER_DHCP6,
  390. "<string>:1.11: syntax error, unexpected }, "
  391. "expecting :");
  392. testError("{ \"Dhcp4\":[]\n",
  393. Parser6Context::PARSER_DHCP6,
  394. "<string>:2.1: syntax error, unexpected end of file, "
  395. "expecting \",\" or }");
  396. testError("{}{}\n",
  397. Parser6Context::SUBPARSER_JSON,
  398. "<string>:1.3: syntax error, unexpected {, "
  399. "expecting end of file");
  400. // bad commas
  401. testError("{ , }\n",
  402. Parser6Context::SUBPARSER_JSON,
  403. "<string>:1.3: syntax error, unexpected \",\", "
  404. "expecting }");
  405. testError("{ , \"foo\":true }\n",
  406. Parser6Context::SUBPARSER_JSON,
  407. "<string>:1.3: syntax error, unexpected \",\", "
  408. "expecting }");
  409. testError("{ \"foo\":true, }\n",
  410. Parser6Context::SUBPARSER_JSON,
  411. "<string>:1.15: syntax error, unexpected }, "
  412. "expecting constant string");
  413. // bad type
  414. testError("{ \"Dhcp6\":{\n"
  415. " \"preferred-lifetime\":false }}\n",
  416. Parser6Context::PARSER_DHCP6,
  417. "<string>:2.24-28: syntax error, unexpected boolean, "
  418. "expecting integer");
  419. // unknown keyword
  420. testError("{ \"Dhcp6\":{\n"
  421. " \"preferred_lifetime\":600 }}\n",
  422. Parser6Context::PARSER_DHCP6,
  423. "<string>:2.2-21: got unexpected keyword "
  424. "\"preferred_lifetime\" in Dhcp6 map.");
  425. }
  426. };