parser_unittest.cc 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. // Copyright (C) 2016-2017 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. #include <testutils/io_utils.h>
  10. using namespace isc::data;
  11. using namespace std;
  12. namespace isc {
  13. namespace dhcp {
  14. namespace test {
  15. /// @brief compares two JSON trees
  16. ///
  17. /// If differences are discovered, gtest failure is reported (using EXPECT_EQ)
  18. ///
  19. /// @param a first to be compared
  20. /// @param b second to be compared
  21. void compareJSON(ConstElementPtr a, ConstElementPtr b) {
  22. ASSERT_TRUE(a);
  23. ASSERT_TRUE(b);
  24. EXPECT_EQ(a->str(), b->str());
  25. }
  26. /// @brief Tests if the input string can be parsed with specific parser
  27. ///
  28. /// The input text will be passed to bison parser of specified type.
  29. /// Then the same input text is passed to legacy JSON parser and outputs
  30. /// from both parsers are compared. The legacy comparison can be disabled,
  31. /// if the feature tested is not supported by the old parser (e.g.
  32. /// new comment styles)
  33. ///
  34. /// @param txt text to be compared
  35. /// @param parser_type bison parser type to be instantiated
  36. /// @param compare whether to compare the output with legacy JSON parser
  37. void testParser(const std::string& txt, Parser6Context::ParserType parser_type,
  38. bool compare = true) {
  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. if (!compare) {
  50. return;
  51. }
  52. // Now compare if both representations are the same.
  53. ElementPtr reference_json;
  54. ASSERT_NO_THROW(reference_json = Element::fromJSON(txt, true));
  55. compareJSON(reference_json, test_json);
  56. }
  57. TEST(ParserTest, mapInMap) {
  58. string txt = "{ \"xyzzy\": { \"foo\": 123, \"baz\": 456 } }";
  59. testParser(txt, Parser6Context::PARSER_JSON);
  60. }
  61. TEST(ParserTest, listInList) {
  62. string txt = "[ [ \"Britain\", \"Wales\", \"Scotland\" ], "
  63. "[ \"Pomorze\", \"Wielkopolska\", \"Tatry\"] ]";
  64. testParser(txt, Parser6Context::PARSER_JSON);
  65. }
  66. TEST(ParserTest, nestedMaps) {
  67. string txt = "{ \"europe\": { \"UK\": { \"London\": { \"street\": \"221B Baker\" }}}}";
  68. testParser(txt, Parser6Context::PARSER_JSON);
  69. }
  70. TEST(ParserTest, nestedLists) {
  71. string txt = "[ \"half\", [ \"quarter\", [ \"eighth\", [ \"sixteenth\" ]]]]";
  72. testParser(txt, Parser6Context::PARSER_JSON);
  73. }
  74. TEST(ParserTest, listsInMaps) {
  75. string txt = "{ \"constellations\": { \"orion\": [ \"rigel\", \"betelguese\" ], "
  76. "\"cygnus\": [ \"deneb\", \"albireo\"] } }";
  77. testParser(txt, Parser6Context::PARSER_JSON);
  78. }
  79. TEST(ParserTest, mapsInLists) {
  80. string txt = "[ { \"body\": \"earth\", \"gravity\": 1.0 },"
  81. " { \"body\": \"mars\", \"gravity\": 0.376 } ]";
  82. testParser(txt, Parser6Context::PARSER_JSON);
  83. }
  84. TEST(ParserTest, types) {
  85. string txt = "{ \"string\": \"foo\","
  86. "\"integer\": 42,"
  87. "\"boolean\": true,"
  88. "\"map\": { \"foo\": \"bar\" },"
  89. "\"list\": [ 1, 2, 3 ],"
  90. "\"null\": null }";
  91. testParser(txt, Parser6Context::PARSER_JSON);
  92. }
  93. TEST(ParserTest, keywordJSON) {
  94. string txt = "{ \"name\": \"user\","
  95. "\"type\": \"password\","
  96. "\"user\": \"name\","
  97. "\"password\": \"type\" }";
  98. testParser(txt, Parser6Context::PARSER_JSON);
  99. }
  100. TEST(ParserTest, keywordDhcp6) {
  101. string txt = "{ \"Dhcp6\": { \"interfaces-config\": {"
  102. " \"interfaces\": [ \"type\", \"htype\" ] },\n"
  103. "\"preferred-lifetime\": 3000,\n"
  104. "\"rebind-timer\": 2000, \n"
  105. "\"renew-timer\": 1000, \n"
  106. "\"subnet6\": [ { "
  107. " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
  108. " \"subnet\": \"2001:db8:1::/48\", "
  109. " \"interface\": \"test\" } ],\n"
  110. "\"valid-lifetime\": 4000 } }";
  111. testParser(txt, Parser6Context::PARSER_DHCP6);
  112. }
  113. // Tests if bash (#) comments are supported. That's the only comment type that
  114. // was supported by the old parser.
  115. TEST(ParserTest, bashComments) {
  116. string txt= "{ \"Dhcp6\": { \"interfaces-config\": {"
  117. " \"interfaces\": [ \"*\" ]"
  118. "},\n"
  119. "\"preferred-lifetime\": 3000,\n"
  120. "# this is a comment\n"
  121. "\"rebind-timer\": 2000, \n"
  122. "# lots of comments here\n"
  123. "# and here\n"
  124. "\"renew-timer\": 1000, \n"
  125. "\"subnet6\": [ { "
  126. " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
  127. " \"subnet\": \"2001:db8:1::/48\", "
  128. " \"interface\": \"eth0\""
  129. " } ],"
  130. "\"valid-lifetime\": 4000 } }";
  131. testParser(txt, Parser6Context::PARSER_DHCP6);
  132. }
  133. // Tests if C++ (//) comments can start anywhere, not just in the first line.
  134. TEST(ParserTest, cppComments) {
  135. string txt= "{ \"Dhcp6\": { \"interfaces-config\": {"
  136. " \"interfaces\": [ \"*\" ]"
  137. "},\n"
  138. "\"preferred-lifetime\": 3000, // this is a comment \n"
  139. "\"rebind-timer\": 2000, // everything after // is ignored\n"
  140. "\"renew-timer\": 1000, // this will be ignored, too\n"
  141. "\"subnet6\": [ { "
  142. " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
  143. " \"subnet\": \"2001:db8:1::/48\", "
  144. " \"interface\": \"eth0\""
  145. " } ],"
  146. "\"valid-lifetime\": 4000 } }";
  147. testParser(txt, Parser6Context::PARSER_DHCP6, false);
  148. }
  149. // Tests if bash (#) comments can start anywhere, not just in the first line.
  150. TEST(ParserTest, bashCommentsInline) {
  151. string txt= "{ \"Dhcp6\": { \"interfaces-config\": {"
  152. " \"interfaces\": [ \"*\" ]"
  153. "},\n"
  154. "\"preferred-lifetime\": 3000, # this is a comment \n"
  155. "\"rebind-timer\": 2000, # everything after # is ignored\n"
  156. "\"renew-timer\": 1000, # this will be ignored, too\n"
  157. "\"subnet6\": [ { "
  158. " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
  159. " \"subnet\": \"2001:db8:1::/48\", "
  160. " \"interface\": \"eth0\""
  161. " } ],"
  162. "\"valid-lifetime\": 4000 } }";
  163. testParser(txt, Parser6Context::PARSER_DHCP6, false);
  164. }
  165. // Tests if multi-line C style comments are handled correctly.
  166. TEST(ParserTest, multilineComments) {
  167. string txt= "{ \"Dhcp6\": { \"interfaces-config\": {"
  168. " \"interfaces\": [ \"*\" ]"
  169. "},\n"
  170. "\"preferred-lifetime\": 3000, /* this is a C style comment\n"
  171. "that\n can \n span \n multiple \n lines */ \n"
  172. "\"rebind-timer\": 2000,\n"
  173. "\"renew-timer\": 1000, \n"
  174. "\"subnet6\": [ { "
  175. " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
  176. " \"subnet\": \"2001:db8:1::/48\", "
  177. " \"interface\": \"eth0\""
  178. " } ],"
  179. "\"valid-lifetime\": 4000 } }";
  180. testParser(txt, Parser6Context::PARSER_DHCP6, false);
  181. }
  182. /// @brief Loads specified example config file
  183. ///
  184. /// This test loads specified example file twice: first, using the legacy
  185. /// JSON file and then second time using bison parser. Two created Element
  186. /// trees are then compared. The input is decommented before it is passed
  187. /// to legacy parser (as legacy support for comments is very limited).
  188. ///
  189. /// @param fname name of the file to be loaded
  190. void testFile(const std::string& fname) {
  191. ElementPtr reference_json;
  192. ConstElementPtr test_json;
  193. string decommented = decommentJSONfile(fname);
  194. cout << "Parsing file " << fname << "(" << decommented << ")" << endl;
  195. EXPECT_NO_THROW(reference_json = Element::fromJSONFile(decommented, true));
  196. // remove the temporary file
  197. EXPECT_NO_THROW(::remove(decommented.c_str()));
  198. EXPECT_NO_THROW(
  199. try {
  200. Parser6Context ctx;
  201. test_json = ctx.parseFile(fname, Parser6Context::PARSER_DHCP6);
  202. } catch (const std::exception &x) {
  203. cout << "EXCEPTION: " << x.what() << endl;
  204. throw;
  205. });
  206. ASSERT_TRUE(reference_json);
  207. ASSERT_TRUE(test_json);
  208. compareJSON(reference_json, test_json);
  209. }
  210. // This test loads all available existing files. Each config is loaded
  211. // twice: first with the existing Element::fromJSONFile() and then
  212. // the second time with Parser6. Both JSON trees are then compared.
  213. TEST(ParserTest, file) {
  214. vector<string> configs;
  215. configs.push_back("advanced.json");
  216. configs.push_back("backends.json");
  217. configs.push_back("classify.json");
  218. configs.push_back("dhcpv4-over-dhcpv6.json");
  219. configs.push_back("duid.json");
  220. configs.push_back("hooks.json");
  221. configs.push_back("leases-expiration.json");
  222. configs.push_back("multiple-options.json");
  223. configs.push_back("mysql-reservations.json");
  224. configs.push_back("pgsql-reservations.json");
  225. configs.push_back("reservations.json");
  226. configs.push_back("several-subnets.json");
  227. configs.push_back("simple.json");
  228. configs.push_back("stateless.json");
  229. for (int i = 0; i<configs.size(); i++) {
  230. testFile(string(CFG_EXAMPLES) + "/" + configs[i]);
  231. }
  232. }
  233. /// @brief Tests error conditions in Dhcp6Parser
  234. ///
  235. /// @param txt text to be parsed
  236. /// @param parser_type type of the parser to be used in the test
  237. /// @param msg expected content of the exception
  238. void testError(const std::string& txt,
  239. Parser6Context::ParserType parser_type,
  240. const std::string& msg)
  241. {
  242. try {
  243. Parser6Context ctx;
  244. ConstElementPtr parsed = ctx.parseString(txt, parser_type);
  245. FAIL() << "Expected Dhcp6ParseError but nothing was raised (expected: "
  246. << msg << ")";
  247. }
  248. catch (const Dhcp6ParseError& ex) {
  249. EXPECT_EQ(msg, ex.what());
  250. }
  251. catch (...) {
  252. FAIL() << "Expected Dhcp6ParseError but something else was raised";
  253. }
  254. }
  255. // Verify that error conditions are handled correctly.
  256. TEST(ParserTest, errors) {
  257. // no input
  258. testError("", Parser6Context::PARSER_JSON,
  259. "<string>:1.1: syntax error, unexpected end of file");
  260. testError(" ", Parser6Context::PARSER_JSON,
  261. "<string>:1.2: syntax error, unexpected end of file");
  262. testError("\n", Parser6Context::PARSER_JSON,
  263. "<string>:2.1: syntax error, unexpected end of file");
  264. testError("\t", Parser6Context::PARSER_JSON,
  265. "<string>:1.2: syntax error, unexpected end of file");
  266. testError("\r", Parser6Context::PARSER_JSON,
  267. "<string>:1.2: syntax error, unexpected end of file");
  268. // comments
  269. testError("# nothing\n",
  270. Parser6Context::PARSER_JSON,
  271. "<string>:2.1: syntax error, unexpected end of file");
  272. testError(" #\n",
  273. Parser6Context::PARSER_JSON,
  274. "<string>:2.1: syntax error, unexpected end of file");
  275. testError("// nothing\n",
  276. Parser6Context::PARSER_JSON,
  277. "<string>:2.1: syntax error, unexpected end of file");
  278. testError("/* nothing */\n",
  279. Parser6Context::PARSER_JSON,
  280. "<string>:2.1: syntax error, unexpected end of file");
  281. testError("/* no\nthing */\n",
  282. Parser6Context::PARSER_JSON,
  283. "<string>:3.1: syntax error, unexpected end of file");
  284. testError("/* no\nthing */\n\n",
  285. Parser6Context::PARSER_JSON,
  286. "<string>:4.1: syntax error, unexpected end of file");
  287. testError("/* nothing\n",
  288. Parser6Context::PARSER_JSON,
  289. "Comment not closed. (/* in line 1");
  290. testError("\n\n\n/* nothing\n",
  291. Parser6Context::PARSER_JSON,
  292. "Comment not closed. (/* in line 4");
  293. testError("{ /* */*/ }\n",
  294. Parser6Context::PARSER_JSON,
  295. "<string>:1.3-8: Invalid character: *");
  296. testError("{ /* // *// }\n",
  297. Parser6Context::PARSER_JSON,
  298. "<string>:1.3-11: Invalid character: /");
  299. testError("{ /* // */// }\n",
  300. Parser6Context::PARSER_JSON,
  301. "<string>:2.1: syntax error, unexpected end of file, "
  302. "expecting }");
  303. // includes
  304. testError("<?\n",
  305. Parser6Context::PARSER_JSON,
  306. "Directive not closed.");
  307. testError("<?include\n",
  308. Parser6Context::PARSER_JSON,
  309. "Directive not closed.");
  310. string file = string(CFG_EXAMPLES) + "/" + "stateless.json";
  311. testError("<?include \"" + file + "\"\n",
  312. Parser6Context::PARSER_JSON,
  313. "Directive not closed.");
  314. testError("<?include \"/foo/bar\" ?>/n",
  315. Parser6Context::PARSER_JSON,
  316. "Can't open include file /foo/bar");
  317. // JSON keywords
  318. testError("{ \"foo\": True }",
  319. Parser6Context::PARSER_JSON,
  320. "<string>:1.10-13: JSON true reserved keyword is lower case only");
  321. testError("{ \"foo\": False }",
  322. Parser6Context::PARSER_JSON,
  323. "<string>:1.10-14: JSON false reserved keyword is lower case only");
  324. testError("{ \"foo\": NULL }",
  325. Parser6Context::PARSER_JSON,
  326. "<string>:1.10-13: JSON null reserved keyword is lower case only");
  327. testError("{ \"foo\": Tru }",
  328. Parser6Context::PARSER_JSON,
  329. "<string>:1.10: Invalid character: T");
  330. testError("{ \"foo\": nul }",
  331. Parser6Context::PARSER_JSON,
  332. "<string>:1.10: Invalid character: n");
  333. // numbers
  334. testError("123",
  335. Parser6Context::PARSER_DHCP6,
  336. "<string>:1.1-3: syntax error, unexpected integer, "
  337. "expecting {");
  338. testError("-456",
  339. Parser6Context::PARSER_DHCP6,
  340. "<string>:1.1-4: syntax error, unexpected integer, "
  341. "expecting {");
  342. testError("-0001",
  343. Parser6Context::PARSER_DHCP6,
  344. "<string>:1.1-5: syntax error, unexpected integer, "
  345. "expecting {");
  346. testError("1234567890123456789012345678901234567890",
  347. Parser6Context::PARSER_JSON,
  348. "<string>:1.1-40: Failed to convert "
  349. "1234567890123456789012345678901234567890"
  350. " to an integer.");
  351. testError("-3.14e+0",
  352. Parser6Context::PARSER_DHCP6,
  353. "<string>:1.1-8: syntax error, unexpected floating point, "
  354. "expecting {");
  355. testError("1e50000",
  356. Parser6Context::PARSER_JSON,
  357. "<string>:1.1-7: Failed to convert 1e50000 "
  358. "to a floating point.");
  359. // strings
  360. testError("\"aabb\"",
  361. Parser6Context::PARSER_DHCP6,
  362. "<string>:1.1-6: syntax error, unexpected constant string, "
  363. "expecting {");
  364. testError("{ \"aabb\"err",
  365. Parser6Context::PARSER_JSON,
  366. "<string>:1.9: Invalid character: e");
  367. testError("{ err\"aabb\"",
  368. Parser6Context::PARSER_JSON,
  369. "<string>:1.3: Invalid character: e");
  370. testError("\"a\n\tb\"",
  371. Parser6Context::PARSER_JSON,
  372. "<string>:1.1-6: Invalid control in \"a\n\tb\"");
  373. testError("\"a\\n\\tb\"",
  374. Parser6Context::PARSER_DHCP6,
  375. "<string>:1.1-8: syntax error, unexpected constant string, "
  376. "expecting {");
  377. testError("\"a\\x01b\"",
  378. Parser6Context::PARSER_JSON,
  379. "<string>:1.1-8: Bad escape in \"a\\x01b\"");
  380. testError("\"a\\u0162\"",
  381. Parser6Context::PARSER_JSON,
  382. "<string>:1.1-9: Unsupported unicode escape in \"a\\u0162\"");
  383. testError("\"a\\u062z\"",
  384. Parser6Context::PARSER_JSON,
  385. "<string>:1.1-9: Bad escape in \"a\\u062z\"");
  386. testError("\"abc\\\"",
  387. Parser6Context::PARSER_JSON,
  388. "<string>:1.1-6: Overflow escape in \"abc\\\"");
  389. // from data_unittest.c
  390. testError("\\a",
  391. Parser6Context::PARSER_JSON,
  392. "<string>:1.1: Invalid character: \\");
  393. testError("\\",
  394. Parser6Context::PARSER_JSON,
  395. "<string>:1.1: Invalid character: \\");
  396. testError("\\\"\\\"",
  397. Parser6Context::PARSER_JSON,
  398. "<string>:1.1: Invalid character: \\");
  399. // want a map
  400. testError("[]\n",
  401. Parser6Context::PARSER_DHCP6,
  402. "<string>:1.1: syntax error, unexpected [, "
  403. "expecting {");
  404. testError("[]\n",
  405. Parser6Context::PARSER_DHCP6,
  406. "<string>:1.1: syntax error, unexpected [, "
  407. "expecting {");
  408. testError("{ 123 }\n",
  409. Parser6Context::PARSER_JSON,
  410. "<string>:1.3-5: syntax error, unexpected integer, "
  411. "expecting }");
  412. testError("{ 123 }\n",
  413. Parser6Context::PARSER_DHCP6,
  414. "<string>:1.3-5: syntax error, unexpected integer");
  415. testError("{ \"foo\" }\n",
  416. Parser6Context::PARSER_JSON,
  417. "<string>:1.9: syntax error, unexpected }, "
  418. "expecting :");
  419. testError("{ \"foo\" }\n",
  420. Parser6Context::PARSER_DHCP6,
  421. "<string>:1.9: syntax error, unexpected }, expecting :");
  422. testError("{ \"foo\":null }\n",
  423. Parser6Context::PARSER_DHCP6,
  424. "<string>:1.3-7: got unexpected keyword "
  425. "\"foo\" in toplevel map.");
  426. testError("{ \"Dhcp6\" }\n",
  427. Parser6Context::PARSER_DHCP6,
  428. "<string>:1.11: syntax error, unexpected }, "
  429. "expecting :");
  430. testError("{ \"Dhcp4\":[]\n",
  431. Parser6Context::PARSER_DHCP6,
  432. "<string>:2.1: syntax error, unexpected end of file, "
  433. "expecting \",\" or }");
  434. testError("{}{}\n",
  435. Parser6Context::PARSER_JSON,
  436. "<string>:1.3: syntax error, unexpected {, "
  437. "expecting end of file");
  438. // bad commas
  439. testError("{ , }\n",
  440. Parser6Context::PARSER_JSON,
  441. "<string>:1.3: syntax error, unexpected \",\", "
  442. "expecting }");
  443. testError("{ , \"foo\":true }\n",
  444. Parser6Context::PARSER_JSON,
  445. "<string>:1.3: syntax error, unexpected \",\", "
  446. "expecting }");
  447. testError("{ \"foo\":true, }\n",
  448. Parser6Context::PARSER_JSON,
  449. "<string>:1.15: syntax error, unexpected }, "
  450. "expecting constant string");
  451. // bad type
  452. testError("{ \"Dhcp6\":{\n"
  453. " \"preferred-lifetime\":false }}\n",
  454. Parser6Context::PARSER_DHCP6,
  455. "<string>:2.24-28: syntax error, unexpected boolean, "
  456. "expecting integer");
  457. // unknown keyword
  458. testError("{ \"Dhcp6\":{\n"
  459. " \"preferred_lifetime\":600 }}\n",
  460. Parser6Context::PARSER_DHCP6,
  461. "<string>:2.2-21: got unexpected keyword "
  462. "\"preferred_lifetime\" in Dhcp6 map.");
  463. }
  464. // Check unicode escapes
  465. TEST(ParserTest, unicodeEscapes) {
  466. ConstElementPtr result;
  467. string json;
  468. // check we can reread output
  469. for (char c = -128; c < 127; ++c) {
  470. string ins(" ");
  471. ins[1] = c;
  472. ConstElementPtr e(new StringElement(ins));
  473. json = e->str();
  474. ASSERT_NO_THROW(
  475. try {
  476. Parser6Context ctx;
  477. result = ctx.parseString(json, Parser6Context::PARSER_JSON);
  478. } catch (const std::exception &x) {
  479. cout << "EXCEPTION: " << x.what() << endl;
  480. throw;
  481. });
  482. ASSERT_EQ(Element::string, result->getType());
  483. EXPECT_EQ(ins, result->stringValue());
  484. }
  485. }
  486. // This test checks that all representations of a slash is recognized properly.
  487. TEST(ParserTest, unicodeSlash) {
  488. // check the 4 possible encodings of solidus '/'
  489. ConstElementPtr result;
  490. string json = "\"/\\/\\u002f\\u002F\"";
  491. ASSERT_NO_THROW(
  492. try {
  493. Parser6Context ctx;
  494. result = ctx.parseString(json, Parser6Context::PARSER_JSON);
  495. } catch (const std::exception &x) {
  496. cout << "EXCEPTION: " << x.what() << endl;
  497. throw;
  498. });
  499. ASSERT_EQ(Element::string, result->getType());
  500. EXPECT_EQ("////", result->stringValue());
  501. }
  502. };
  503. };
  504. };