parser_unittest.cc 17.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include <gtest/gtest.h>
#include <cc/data.h>
#include <dhcp6/parser_context.h>

using namespace isc::data;
using namespace std;

namespace {

16
void compareJSON(ConstElementPtr a, ConstElementPtr b, bool print = true) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
17 18
    ASSERT_TRUE(a);
    ASSERT_TRUE(b);
19
    if (print) {
20 21 22
        // std::cout << "JSON A: -----" << endl << a->str() << std::endl;
        // std::cout << "JSON B: -----" << endl << b->str() << std::endl;
        // cout << "---------" << endl << endl;
23
    }
24 25 26
    EXPECT_EQ(a->str(), b->str());
}

27
void testParser(const std::string& txt, Parser6Context::ParserType parser_type) {
28 29 30
    ElementPtr reference_json;
    ConstElementPtr test_json;

31 32 33
    ASSERT_NO_THROW(reference_json = Element::fromJSON(txt, true));
    ASSERT_NO_THROW({
            try {
34
        Parser6Context ctx;
35
        test_json = ctx.parseString(txt, parser_type);
36 37 38 39 40
            } catch (const std::exception &e) {
                cout << "EXCEPTION: " << e.what() << endl;
                throw;
            }

41 42 43 44 45 46
    });

    // Now compare if both representations are the same.
    compareJSON(reference_json, test_json);
}

47
void testParser2(const std::string& txt, Parser6Context::ParserType parser_type) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
48 49
    ConstElementPtr test_json;

50 51
    ASSERT_NO_THROW({
            try {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
52
        Parser6Context ctx;
53
        test_json = ctx.parseString(txt, parser_type);
54 55 56 57
            } catch (const std::exception &e) {
                cout << "EXCEPTION: " << e.what() << endl;
                throw;
            }
Tomek Mrugalski's avatar
Tomek Mrugalski committed
58 59 60 61
    });
    /// @todo: Implement actual validation here. since the original
    /// Element::fromJSON does not support several comment types, we don't
    /// have anything to compare with.
62 63
    /// std::cout << "Original text:" << txt << endl;
    /// std::cout << "Parsed text  :" << test_json->str() << endl;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
64 65
}

66
TEST(ParserTest, mapInMap) {
67
    string txt = "{ \"xyzzy\": { \"foo\": 123, \"baz\": 456 } }";
68
    testParser(txt, Parser6Context::PARSER_JSON);
69
}
70

71
TEST(ParserTest, listInList) {
72 73
    string txt = "[ [ \"Britain\", \"Wales\", \"Scotland\" ], "
                 "[ \"Pomorze\", \"Wielkopolska\", \"Tatry\"] ]";
74
    testParser(txt, Parser6Context::PARSER_JSON);
75 76 77 78
}

TEST(ParserTest, nestedMaps) {
    string txt = "{ \"europe\": { \"UK\": { \"London\": { \"street\": \"221B Baker\" }}}}";
79
    testParser(txt, Parser6Context::PARSER_JSON);
80
}
81

82
TEST(ParserTest, nestedLists) {
83
    string txt = "[ \"half\", [ \"quarter\", [ \"eighth\", [ \"sixteenth\" ]]]]";
84
    testParser(txt, Parser6Context::PARSER_JSON);
85
}
86

87 88 89
TEST(ParserTest, listsInMaps) {
    string txt = "{ \"constellations\": { \"orion\": [ \"rigel\", \"betelguese\" ], "
                    "\"cygnus\": [ \"deneb\", \"albireo\"] } }";
90
    testParser(txt, Parser6Context::PARSER_JSON);
91 92 93
}

TEST(ParserTest, mapsInLists) {
94 95
    string txt = "[ { \"body\": \"earth\", \"gravity\": 1.0 },"
                 " { \"body\": \"mars\", \"gravity\": 0.376 } ]";
96
    testParser(txt, Parser6Context::PARSER_JSON);
97
}
98

99 100 101 102 103 104 105
TEST(ParserTest, types) {
    string txt = "{ \"string\": \"foo\","
                   "\"integer\": 42,"
                   "\"boolean\": true,"
                   "\"map\": { \"foo\": \"bar\" },"
                   "\"list\": [ 1, 2, 3 ],"
                   "\"null\": null }";
106
    testParser(txt, Parser6Context::PARSER_JSON);
107 108
}

109 110 111 112 113
TEST(ParserTest, keywordJSON) {
    string txt = "{ \"name\": \"user\","
                   "\"type\": \"password\","
                   "\"user\": \"name\","
                   "\"password\": \"type\" }";
114
    testParser(txt, Parser6Context::PARSER_JSON);
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
}

TEST(ParserTest, keywordDhcp6) {
     string txt = "{ \"Dhcp6\": { \"interfaces-config\": {"
                  " \"interfaces\": [ \"type\", \"htype\" ] },\n"
                  "\"preferred-lifetime\": 3000,\n"
                  "\"rebind-timer\": 2000, \n"
                  "\"renew-timer\": 1000, \n"
                  "\"subnet6\": [ { "
                  "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
                  "    \"subnet\": \"2001:db8:1::/48\", "
                  "    \"interface\": \"test\" } ],\n"
                   "\"valid-lifetime\": 4000 } }";
     testParser2(txt, Parser6Context::PARSER_DHCP6);
}

Tomek Mrugalski's avatar
Tomek Mrugalski committed
131
TEST(ParserTest, bashComments) {
132
    string txt= "{ \"Dhcp6\": { \"interfaces-config\": {"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
133 134 135 136 137 138 139 140 141 142 143 144 145
                "  \"interfaces\": [ \"*\" ]"
                "},\n"
                "\"preferred-lifetime\": 3000,\n"
                "# this is a comment\n"
                "\"rebind-timer\": 2000, \n"
                "# lots of comments here\n"
                "# and here\n"
                "\"renew-timer\": 1000, \n"
                "\"subnet6\": [ { "
                "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
                "    \"subnet\": \"2001:db8:1::/48\", "
                "    \"interface\": \"eth0\""
                " } ],"
146 147
                "\"valid-lifetime\": 4000 } }";
    testParser2(txt, Parser6Context::PARSER_DHCP6);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
148 149 150
}

TEST(ParserTest, cComments) {
151
    string txt= "{ \"Dhcp6\": { \"interfaces-config\": {"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
152 153 154 155 156 157 158 159 160 161
                "  \"interfaces\": [ \"*\" ]"
                "},\n"
                "\"preferred-lifetime\": 3000, // this is a comment \n"
                "\"rebind-timer\": 2000, // everything after // is ignored\n"
                "\"renew-timer\": 1000, // this will be ignored, too\n"
                "\"subnet6\": [ { "
                "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
                "    \"subnet\": \"2001:db8:1::/48\", "
                "    \"interface\": \"eth0\""
                " } ],"
162 163
                "\"valid-lifetime\": 4000 } }";
    testParser2(txt, Parser6Context::PARSER_DHCP6);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
164 165
}

166 167
TEST(ParserTest, bashCommentsInline) {
    string txt= "{ \"Dhcp6\": { \"interfaces-config\": {"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
168 169 170 171 172 173 174 175 176 177
                "  \"interfaces\": [ \"*\" ]"
                "},\n"
                "\"preferred-lifetime\": 3000, # this is a comment \n"
                "\"rebind-timer\": 2000, # everything after # is ignored\n"
                "\"renew-timer\": 1000, # this will be ignored, too\n"
                "\"subnet6\": [ { "
                "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
                "    \"subnet\": \"2001:db8:1::/48\", "
                "    \"interface\": \"eth0\""
                " } ],"
178 179
                "\"valid-lifetime\": 4000 } }";
    testParser2(txt, Parser6Context::PARSER_DHCP6);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
180 181 182
}

TEST(ParserTest, multilineComments) {
183
    string txt= "{ \"Dhcp6\": { \"interfaces-config\": {"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
184 185 186 187 188 189 190 191 192 193 194
                "  \"interfaces\": [ \"*\" ]"
                "},\n"
                "\"preferred-lifetime\": 3000, /* this is a C style comment\n"
                "that\n can \n span \n multiple \n lines */ \n"
                "\"rebind-timer\": 2000,\n"
                "\"renew-timer\": 1000, \n"
                "\"subnet6\": [ { "
                "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
                "    \"subnet\": \"2001:db8:1::/48\", "
                "    \"interface\": \"eth0\""
                " } ],"
195 196
                "\"valid-lifetime\": 4000 } }";
    testParser2(txt, Parser6Context::PARSER_DHCP6);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
197 198
}

199

200
void testFile(const std::string& fname, bool print) {
201 202 203
    ElementPtr reference_json;
    ConstElementPtr test_json;

204
    cout << "Attempting to load file " << fname << endl;
205 206

    EXPECT_NO_THROW(reference_json = Element::fromJSONFile(fname, true));
207

208
    EXPECT_NO_THROW(
209
    try {
210
        Parser6Context ctx;
211
        test_json = ctx.parseFile(fname, Parser6Context::PARSER_DHCP6);
212 213
    } catch (const std::exception &x) {
        cout << "EXCEPTION: " << x.what() << endl;
214 215
        throw;
    });
216 217 218 219

    ASSERT_TRUE(reference_json);
    ASSERT_TRUE(test_json);

220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247
    compareJSON(reference_json, test_json, print);


}

// This test loads all available existing files. Each config is loaded
// twice: first with the existing Element::fromJSONFile() and then
// the second time with Parser6. Both JSON trees are then compared.
TEST(ParserTest, file) {
    vector<string> configs;
    configs.push_back("advanced.json");
    configs.push_back("backends.json");
    configs.push_back("classify.json");
    configs.push_back("dhcpv4-over-dhcpv6.json");
    configs.push_back("duid.json");
    configs.push_back("hooks.json");
    configs.push_back("leases-expiration.json");
    configs.push_back("multiple-options.json");
    configs.push_back("mysql-reservations.json");
    configs.push_back("pgsql-reservations.json");
    configs.push_back("reservations.json");
    configs.push_back("several-subnets.json");
    configs.push_back("simple.json");
    configs.push_back("stateless.json");

    for (int i = 0; i<configs.size(); i++) {
        testFile(string(CFG_EXAMPLES) + "/" + configs[i], false);
    }
248 249
}

250 251 252 253 254 255 256
void testError(const std::string& txt,
               Parser6Context::ParserType parser_type,
               const std::string& msg)
{
    try {
        Parser6Context ctx;
        ConstElementPtr parsed = ctx.parseString(txt, parser_type);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
257 258
        FAIL() << "Expected Dhcp6ParseError but nothing was raised (expected: "
               << msg << ")";
259 260 261 262 263 264 265 266 267
    }
    catch (const Dhcp6ParseError& ex) {
        EXPECT_EQ(msg, ex.what());
    }
    catch (...) {
        FAIL() << "Expected Dhcp6ParseError but something else was raised";
    }
}

268 269 270
// Check errors
TEST(ParserTest, errors) {
    // no input
271
    testError("", Parser6Context::PARSER_JSON,
Tomek Mrugalski's avatar
Tomek Mrugalski committed
272
              "<string>:1.1: syntax error, unexpected end of file");
273
    testError(" ", Parser6Context::PARSER_JSON,
Tomek Mrugalski's avatar
Tomek Mrugalski committed
274
              "<string>:1.2: syntax error, unexpected end of file");
275
    testError("\n", Parser6Context::PARSER_JSON,
Tomek Mrugalski's avatar
Tomek Mrugalski committed
276
              "<string>:2.1: syntax error, unexpected end of file");
277 278

    // comments
279
    testError("# nothing\n",
280
              Parser6Context::PARSER_JSON,
Tomek Mrugalski's avatar
Tomek Mrugalski committed
281
              "<string>:2.1: syntax error, unexpected end of file");
282
    testError(" #\n",
283
              Parser6Context::PARSER_JSON,
Tomek Mrugalski's avatar
Tomek Mrugalski committed
284
              "<string>:2.1: syntax error, unexpected end of file");
285
    testError("// nothing\n",
286
              Parser6Context::PARSER_JSON,
Tomek Mrugalski's avatar
Tomek Mrugalski committed
287
              "<string>:2.1: syntax error, unexpected end of file");
288
    testError("/* nothing */\n",
289
              Parser6Context::PARSER_JSON,
Tomek Mrugalski's avatar
Tomek Mrugalski committed
290
              "<string>:2.1: syntax error, unexpected end of file");
291
    testError("/* no\nthing */\n",
292
              Parser6Context::PARSER_JSON,
Tomek Mrugalski's avatar
Tomek Mrugalski committed
293
              "<string>:3.1: syntax error, unexpected end of file");
294
    testError("/* no\nthing */\n\n",
295
              Parser6Context::PARSER_JSON,
Tomek Mrugalski's avatar
Tomek Mrugalski committed
296
              "<string>:4.1: syntax error, unexpected end of file");
297
    testError("/* nothing\n",
298
              Parser6Context::PARSER_JSON,
299
              "Comment not closed. (/* in line 1");
300
    testError("\n\n\n/* nothing\n",
301
              Parser6Context::PARSER_JSON,
302
              "Comment not closed. (/* in line 4");
303
    testError("{ /* */*/ }\n",
304
              Parser6Context::PARSER_JSON,
305 306
              "<string>:1.3-8: Invalid character: *");
    testError("{ /* // *// }\n",
307
              Parser6Context::PARSER_JSON,
308 309
              "<string>:1.3-11: Invalid character: /");
    testError("{ /* // *///  }\n",
310
              Parser6Context::PARSER_JSON,
311 312 313 314
              "<string>:2.1: syntax error, unexpected end of file, "
              "expecting }");

    // includes
315
    testError("<?\n",
316
              Parser6Context::PARSER_JSON,
317 318
              "Directive not closed.");
    testError("<?include\n",
319
              Parser6Context::PARSER_JSON,
320 321 322
              "Directive not closed.");
    string file = string(CFG_EXAMPLES) + "/" + "stateless.json";
    testError("<?include \"" + file + "\"\n",
323
              Parser6Context::PARSER_JSON,
324
              "Directive not closed.");
325
    testError("<?include \"/foo/bar\" ?>/n",
326
              Parser6Context::PARSER_JSON,
327 328 329 330
              "Can't open include file /foo/bar");

    // numbers
    testError("123",
Tomek Mrugalski's avatar
Tomek Mrugalski committed
331
              Parser6Context::PARSER_DHCP6,
332 333 334
              "<string>:1.1-3: syntax error, unexpected integer, "
              "expecting {");
    testError("-456",
Tomek Mrugalski's avatar
Tomek Mrugalski committed
335
              Parser6Context::PARSER_DHCP6,
336 337 338
              "<string>:1.1-4: syntax error, unexpected integer, "
              "expecting {");
    testError("-0001",
Tomek Mrugalski's avatar
Tomek Mrugalski committed
339
              Parser6Context::PARSER_DHCP6,
340 341 342
              "<string>:1.1-5: syntax error, unexpected integer, "
              "expecting {");
    testError("1234567890123456789012345678901234567890",
343
              Parser6Context::PARSER_JSON,
344 345 346 347
              "<string>:1.1-40: Failed to convert "
              "1234567890123456789012345678901234567890"
              " to an integer.");
    testError("-3.14e+0",
Tomek Mrugalski's avatar
Tomek Mrugalski committed
348
              Parser6Context::PARSER_DHCP6,
349 350 351
              "<string>:1.1-8: syntax error, unexpected floating point, "
              "expecting {");
    testError("1e50000",
352
              Parser6Context::PARSER_JSON,
353 354 355 356 357
              "<string>:1.1-7: Failed to convert 1e50000 "
              "to a floating point.");

    // strings
    testError("\"aabb\"",
Tomek Mrugalski's avatar
Tomek Mrugalski committed
358
              Parser6Context::PARSER_DHCP6,
359 360 361
              "<string>:1.1-6: syntax error, unexpected constant string, "
              "expecting {");
    testError("{ \"aabb\"err",
362
              Parser6Context::PARSER_JSON,
363 364
              "<string>:1.9: Invalid character: e");
    testError("{ err\"aabb\"",
365
              Parser6Context::PARSER_JSON,
366 367
              "<string>:1.3: Invalid character: e");
    testError("\"a\n\tb\"",
368
              Parser6Context::PARSER_JSON,
369
              "<string>:1.1-6: Invalid control in \"a\n\tb\"");
370
    testError("\"a\\n\\tb\"",
Tomek Mrugalski's avatar
Tomek Mrugalski committed
371
              Parser6Context::PARSER_DHCP6,
372 373 374
              "<string>:1.1-8: syntax error, unexpected constant string, "
              "expecting {");
    testError("\"a\\x01b\"",
375
              Parser6Context::PARSER_JSON,
376
              "<string>:1.1-8: Bad escape in \"a\\x01b\"");
377
    testError("\"a\\u0062\"",
378
              Parser6Context::PARSER_JSON,
379
              "<string>:1.1-9: Unsupported unicode escape in \"a\\u0062\"");
380
    testError("\"a\\u062z\"",
381
              Parser6Context::PARSER_JSON,
382 383
              "<string>:1.1-9: Bad escape in \"a\\u062z\"");
    testError("\"abc\\\"",
384
              Parser6Context::PARSER_JSON,
385 386 387 388
              "<string>:1.1-6: Overflow escape in \"abc\\\"");

    // from data_unittest.c
    testError("\\a",
389
              Parser6Context::PARSER_JSON,
390 391
              "<string>:1.1: Invalid character: \\");
    testError("\\",
392
              Parser6Context::PARSER_JSON,
393 394
              "<string>:1.1: Invalid character: \\");
    testError("\\\"\\\"",
395
              Parser6Context::PARSER_JSON,
396
              "<string>:1.1: Invalid character: \\");
397 398 399

    // want a map
    testError("[]\n",
Tomek Mrugalski's avatar
Tomek Mrugalski committed
400
              Parser6Context::PARSER_DHCP6,
401 402 403 404 405 406 407
              "<string>:1.1: syntax error, unexpected [, "
              "expecting {");
    testError("[]\n",
              Parser6Context::PARSER_DHCP6,
              "<string>:1.1: syntax error, unexpected [, "
              "expecting {");
    testError("{ 123 }\n",
408
              Parser6Context::PARSER_JSON,
409 410 411 412
              "<string>:1.3-5: syntax error, unexpected integer, "
              "expecting }");
    testError("{ 123 }\n",
              Parser6Context::PARSER_DHCP6,
413
              "<string>:1.3-5: syntax error, unexpected integer");
414
    testError("{ \"foo\" }\n",
415
              Parser6Context::PARSER_JSON,
416 417 418 419
              "<string>:1.9: syntax error, unexpected }, "
              "expecting :");
    testError("{ \"foo\" }\n",
              Parser6Context::PARSER_DHCP6,
420 421 422 423 424
              "<string>:1.9: syntax error, unexpected }, expecting :");
    testError("{ \"foo\":null }\n",
              Parser6Context::PARSER_DHCP6,
              "<string>:1.3-7: got unexpected keyword "
              "\"foo\" in toplevel map.");
425 426 427 428
    testError("{ \"Dhcp6\" }\n",
              Parser6Context::PARSER_DHCP6,
              "<string>:1.11: syntax error, unexpected }, "
              "expecting :");
429 430 431 432 433
    testError("{ \"Dhcp4\":[]\n",
              Parser6Context::PARSER_DHCP6,
              "<string>:2.1: syntax error, unexpected end of file, "
              "expecting \",\" or }");
    testError("{}{}\n",
434
              Parser6Context::PARSER_JSON,
435 436 437 438 439
              "<string>:1.3: syntax error, unexpected {, "
              "expecting end of file");

    // bad commas
    testError("{ , }\n",
440
              Parser6Context::PARSER_JSON,
441 442 443
              "<string>:1.3: syntax error, unexpected \",\", "
              "expecting }");
    testError("{ , \"foo\":true }\n",
444
              Parser6Context::PARSER_JSON,
445 446 447
              "<string>:1.3: syntax error, unexpected \",\", "
              "expecting }");
    testError("{ \"foo\":true, }\n",
448
              Parser6Context::PARSER_JSON,
449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464
              "<string>:1.15: syntax error, unexpected }, "
              "expecting constant string");

    // bad type
    testError("{ \"Dhcp6\":{\n"
              "  \"preferred-lifetime\":false }}\n",
              Parser6Context::PARSER_DHCP6,
              "<string>:2.24-28: syntax error, unexpected boolean, "
              "expecting integer");

    // unknown keyword
    testError("{ \"Dhcp6\":{\n"
              " \"preferred_lifetime\":600 }}\n",
              Parser6Context::PARSER_DHCP6,
              "<string>:2.2-21: got unexpected keyword "
              "\"preferred_lifetime\" in Dhcp6 map.");
465 466
}

467
};