summaryrefslogtreecommitdiffstats
path: root/src/lib/cc/tests/simple_parser_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/cc/tests/simple_parser_unittest.cc')
-rw-r--r--src/lib/cc/tests/simple_parser_unittest.cc364
1 files changed, 364 insertions, 0 deletions
diff --git a/src/lib/cc/tests/simple_parser_unittest.cc b/src/lib/cc/tests/simple_parser_unittest.cc
new file mode 100644
index 0000000..76e5ffc
--- /dev/null
+++ b/src/lib/cc/tests/simple_parser_unittest.cc
@@ -0,0 +1,364 @@
+// Copyright (C) 2016-2023 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 <config.h>
+
+#include <stdint.h>
+#include <cc/simple_parser.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::asiolink;
+using isc::dhcp::DhcpConfigError;
+
+/// This list defines required keywords.
+const SimpleRequiredKeywords REQUIRED_KEYWORDS = { "foobar" };
+
+/// This table defines keywords and types.
+const SimpleKeywords KEYWORDS = {
+ { "id", Element::integer },
+ { "prefix", Element::string },
+ { "map", Element::map },
+ { "any", Element::any }
+};
+
+/// This table defines sample default values. Although these are DHCPv6
+/// specific, the mechanism is generic and can be used by any other component.
+const SimpleDefaults SAMPLE_DEFAULTS = {
+ { "renew-timer", Element::integer, "900" },
+ { "rebind-timer", Element::integer, "1800" },
+ { "preferred-lifetime", Element::integer, "3600" },
+ { "valid-lifetime", Element::integer, "7200" }
+};
+
+/// This list defines parameters that can be inherited from one scope
+/// to another. Although these are DHCPv6 specific, the mechanism is generic and
+/// can be used by any other component.
+const ParamsList SAMPLE_INHERITS = {
+ "renew-timer",
+ "rebind-timer",
+ "preferred-lifetime",
+ "valid-lifetime"
+};
+
+/// @brief Simple Parser test fixture class
+class SimpleParserTest : public ::testing::Test {
+public:
+ /// @brief Checks if specified map has an integer parameter with expected value
+ ///
+ /// @param map map to be checked
+ /// @param param_name name of the parameter to be checked
+ /// @param exp_value expected value of the parameter.
+ void checkIntegerValue(const ConstElementPtr& map, const std::string& param_name,
+ int64_t exp_value) {
+
+ // First check if the passed element is a map.
+ ASSERT_EQ(Element::map, map->getType());
+
+ // Now try to get the element being checked
+ ConstElementPtr elem = map->get(param_name);
+ ASSERT_TRUE(elem);
+
+ // Now check if it's indeed integer
+ ASSERT_EQ(Element::integer, elem->getType());
+
+ // Finally, check if its value meets expectation.
+ EXPECT_EQ(exp_value, elem->intValue());
+ }
+};
+
+class SimpleParserClassTest : public SimpleParser {
+public:
+ /// @brief Instantiation of getAndConvert
+ ///
+ /// @param scope specified parameter will be extracted from this scope
+ /// @param name name of the parameter for error report
+ /// @return a bool value
+ bool getAsBool(ConstElementPtr scope, const std::string& name) {
+ return (getAndConvert<bool, toBool>(scope, name, "boolean"));
+ }
+
+ /// @brief Convert to boolean
+ ///
+ /// @param str the string "false" or "true"
+ /// @return false for "false" and true for "true"
+ /// @thrown isc::OutOfRange if not "false" or "true'
+ static bool toBool(const std::string& str) {
+ if (str == "false") {
+ return (false);
+ } else if (str == "true") {
+ return (true);
+ } else {
+ isc_throw(TypeError, "not a boolean: " << str);
+ }
+ }
+};
+
+// This test checks if the checkRequired method works as expected.
+TEST_F(SimpleParserTest, checkRequired) {
+ ConstElementPtr empty = Element::fromJSON("{ }");
+ EXPECT_THROW(SimpleParser::checkRequired(REQUIRED_KEYWORDS, empty),
+ DhcpConfigError);
+ ConstElementPtr other = Element::fromJSON("{ \"foo\": 1, \"bar\": 2 }");
+ EXPECT_THROW(SimpleParser::checkRequired(REQUIRED_KEYWORDS, other),
+ DhcpConfigError);
+ ConstElementPtr good = Element::fromJSON("{ \"foobar\": 2 }");
+ EXPECT_NO_THROW(SimpleParser::checkRequired(REQUIRED_KEYWORDS, good));
+}
+
+// This test checks if the checkKeywords method works as expected.
+TEST_F(SimpleParserTest, checkKeywords) {
+ ConstElementPtr empty = Element::fromJSON("{ }");
+ EXPECT_NO_THROW(SimpleParser::checkKeywords(KEYWORDS, empty));
+ ConstElementPtr id = Element::fromJSON("{ \"id\": 1 }");
+ EXPECT_NO_THROW(SimpleParser::checkKeywords(KEYWORDS, id));
+ ConstElementPtr any = Element::fromJSON("{ \"any\": 1 }");
+ EXPECT_NO_THROW(SimpleParser::checkKeywords(KEYWORDS, any));
+ ConstElementPtr bad_id = Element::fromJSON("{ \"id\": true }");
+ EXPECT_THROW(SimpleParser::checkKeywords(KEYWORDS, bad_id),
+ DhcpConfigError);
+ ConstElementPtr bad_prefix = Element::fromJSON("{ \"prefix\": 12 }");
+ EXPECT_THROW(SimpleParser::checkKeywords(KEYWORDS, bad_prefix),
+ DhcpConfigError);
+ ConstElementPtr bad_map = Element::fromJSON("{ \"map\": [ ] }");
+ EXPECT_THROW(SimpleParser::checkKeywords(KEYWORDS, bad_map),
+ DhcpConfigError);
+ ConstElementPtr spurious = Element::fromJSON("{ \"spurious\": 1 }");
+ EXPECT_THROW(SimpleParser::checkKeywords(KEYWORDS, spurious),
+ DhcpConfigError);
+
+ // Bad type has precedence.
+ ConstElementPtr bad = Element::fromJSON("{ \"spurious\": 1, \"id\": true }");
+ try {
+ SimpleParser::checkKeywords(KEYWORDS, bad);
+ ADD_FAILURE() << "expect exception";
+ } catch (const DhcpConfigError& ex) {
+ EXPECT_EQ("'id' parameter is not an integer", std::string(ex.what()));
+ } catch (...) {
+ ADD_FAILURE() << "expect DhcpConfigError";
+ }
+}
+
+// This test checks if the parameters can be inherited from the global
+// scope to the subnet scope.
+TEST_F(SimpleParserTest, deriveParams) {
+ ElementPtr global = Element::fromJSON("{ \"renew-timer\": 1,"
+ " \"rebind-timer\": 2,"
+ " \"preferred-lifetime\": 3,"
+ " \"valid-lifetime\": 4"
+ "}");
+ ElementPtr subnet = Element::fromJSON("{ \"renew-timer\": 100 }");
+
+ // we should inherit 3 parameters. Renew-timer should remain intact,
+ // as it was already defined in the subnet scope.
+ size_t num;
+ EXPECT_NO_THROW(num = SimpleParser::deriveParams(global, subnet,
+ SAMPLE_INHERITS));
+ EXPECT_EQ(3, num);
+
+ // Check the values. 3 of them are inherited, while the fourth one
+ // was already defined in the subnet, so should not be inherited.
+ checkIntegerValue(subnet, "renew-timer", 100);
+ checkIntegerValue(subnet, "rebind-timer", 2);
+ checkIntegerValue(subnet, "preferred-lifetime", 3);
+ checkIntegerValue(subnet, "valid-lifetime", 4);
+}
+
+// This test checks if global defaults are properly set for DHCPv6.
+TEST_F(SimpleParserTest, setDefaults) {
+
+ ElementPtr empty = Element::fromJSON("{ }");
+ size_t num = 0;
+
+ EXPECT_NO_THROW(num = SimpleParser::setDefaults(empty, SAMPLE_DEFAULTS));
+
+ // We expect at least 4 parameters to be inserted.
+ EXPECT_GE(num, 3);
+
+ checkIntegerValue(empty, "valid-lifetime", 7200);
+ checkIntegerValue(empty, "preferred-lifetime", 3600);
+ checkIntegerValue(empty, "rebind-timer", 1800);
+ checkIntegerValue(empty, "renew-timer", 900);
+}
+
+// This test checks if global defaults are properly set for DHCPv6.
+TEST_F(SimpleParserTest, setListDefaults) {
+
+ ElementPtr empty = Element::fromJSON("[{}, {}, {}]");
+ size_t num;
+
+ EXPECT_NO_THROW(num = SimpleParser::setListDefaults(empty, SAMPLE_DEFAULTS));
+
+ // We expect at least 12 parameters to be inserted (3 entries, with
+ // 4 parameters inserted in each)
+ EXPECT_EQ(12, num);
+
+ ASSERT_EQ(Element::list, empty->getType());
+ ASSERT_EQ(3, empty->size());
+
+ ConstElementPtr first = empty->get(0);
+ ConstElementPtr second = empty->get(1);
+ ConstElementPtr third = empty->get(2);
+
+ checkIntegerValue(first, "valid-lifetime", 7200);
+ checkIntegerValue(first, "preferred-lifetime", 3600);
+ checkIntegerValue(first, "rebind-timer", 1800);
+ checkIntegerValue(first, "renew-timer", 900);
+
+ checkIntegerValue(second, "valid-lifetime", 7200);
+ checkIntegerValue(second, "preferred-lifetime", 3600);
+ checkIntegerValue(second, "rebind-timer", 1800);
+ checkIntegerValue(second, "renew-timer", 900);
+
+ checkIntegerValue(third, "valid-lifetime", 7200);
+ checkIntegerValue(third, "preferred-lifetime", 3600);
+ checkIntegerValue(third, "rebind-timer", 1800);
+ checkIntegerValue(third, "renew-timer", 900);
+}
+
+// This test exercises the getIntType template
+TEST_F(SimpleParserTest, getIntType) {
+
+ SimpleParserClassTest parser;
+
+ // getIntType checks it can be found
+ ElementPtr not_found = Element::fromJSON("{ \"bar\": 1 }");
+ EXPECT_THROW(parser.getUint8(not_found, "foo"), DhcpConfigError);
+
+ // getIntType checks if it is an integer
+ ElementPtr not_int = Element::fromJSON("{ \"foo\": \"xyz\" }");
+ EXPECT_THROW(parser.getUint8(not_int, "foo"), DhcpConfigError);
+
+ // getIntType checks bounds
+ ElementPtr negative = Element::fromJSON("{ \"foo\": -1 }");
+ EXPECT_THROW(parser.getUint8(negative, "foo"), DhcpConfigError);
+ ElementPtr too_large = Element::fromJSON("{ \"foo\": 1024 }");
+ EXPECT_THROW(parser.getUint8(too_large, "foo"), DhcpConfigError);
+
+ // checks if getIntType can return the expected value
+ ElementPtr hundred = Element::fromJSON("{ \"foo\": 100 }");
+ uint8_t val = 0;
+ EXPECT_NO_THROW(val = parser.getUint8(hundred, "foo"));
+ EXPECT_EQ(100, val);
+}
+
+// This test exercises the getInteger with range checking
+TEST_F(SimpleParserTest, getInteger) {
+
+ // The value specified is 100.
+ ElementPtr json = Element::fromJSON("{ \"bar\": 100 }");
+ int64_t x = -1;
+
+ // Positive case: we expect value in range 0..200. All ok.
+ EXPECT_NO_THROW(x = SimpleParser::getInteger(json, "bar", 0, 200));
+ EXPECT_EQ(100, x);
+
+ // Border checks: 100 for 100..200 range is still ok.
+ EXPECT_NO_THROW(x = SimpleParser::getInteger(json, "bar", 100, 200));
+ // Border checks: 100 for 1..100 range is still ok.
+ EXPECT_NO_THROW(x = SimpleParser::getInteger(json, "bar", 1, 100));
+
+ // Out of expected range. Should throw.
+ EXPECT_THROW(x = SimpleParser::getInteger(json, "bar", 101, 200), OutOfRange);
+ EXPECT_THROW(x = SimpleParser::getInteger(json, "bar", 1, 99), OutOfRange);
+}
+
+// This test exercises the getAndConvert template
+TEST_F(SimpleParserTest, getAndConvert) {
+
+ SimpleParserClassTest parser;
+
+ // getAndConvert checks it can be found
+ ElementPtr not_found = Element::fromJSON("{ \"bar\": \"true\" }");
+ EXPECT_THROW(parser.getAsBool(not_found, "foo"), DhcpConfigError);
+
+ // getAndConvert checks if it is a string
+ ElementPtr not_bool = Element::fromJSON("{ \"foo\": 1 }");
+ EXPECT_THROW(parser.getAsBool(not_bool, "foo"), DhcpConfigError);
+
+ // checks if getAndConvert can return the expected value
+ ElementPtr a_bool = Element::fromJSON("{ \"foo\": \"false\" }");
+ bool val = true;
+ EXPECT_NO_THROW(val = parser.getAsBool(a_bool, "foo"));
+ EXPECT_FALSE(val);
+
+ // getAndConvert checks conversion
+ ElementPtr bad_bool = Element::fromJSON("{ \"foo\": \"bar\" }");
+ EXPECT_THROW(parser.getAsBool(bad_bool, "bar"), DhcpConfigError);
+}
+
+// This test exercises the getIOAddress
+TEST_F(SimpleParserTest, getIOAddress) {
+
+ SimpleParserClassTest parser;
+
+ // getAddress checks it can be found
+ ElementPtr not_found = Element::fromJSON("{ \"bar\": 1 }");
+ EXPECT_THROW(parser.getAddress(not_found, "foo"), DhcpConfigError);
+
+ // getAddress checks if it is a string
+ ElementPtr not_addr = Element::fromJSON("{ \"foo\": 1234 }");
+ EXPECT_THROW(parser.getAddress(not_addr, "foo"), DhcpConfigError);
+
+ // checks if getAddress can return the expected value of v4 address
+ ElementPtr v4 = Element::fromJSON("{ \"foo\": \"192.0.2.1\" }");
+ IOAddress val("::");
+ EXPECT_NO_THROW(val = parser.getAddress(v4, "foo"));
+ EXPECT_EQ("192.0.2.1" , val.toText());
+
+ // checks if getAddress can return the expected value of v4 address
+ ElementPtr v6 = Element::fromJSON("{ \"foo\": \"2001:db8::1\" }");
+ EXPECT_NO_THROW(val = parser.getAddress(v6, "foo"));
+ EXPECT_EQ("2001:db8::1" , val.toText());
+}
+
+// This test exercises getDouble()
+TEST_F(SimpleParserTest, getDouble) {
+
+ SimpleParserClassTest parser;
+ std::string json =
+ "{\n"
+ " \"string\" : \"12.3\",\n"
+ " \"bool\" : true, \n"
+ " \"int\" : 777, \n"
+ " \"map\" : {}, \n"
+ " \"list\" : [], \n"
+ " \"zero\" : 0.0, \n"
+ " \"fraction\" : .75, \n"
+ " \"negative\" : -1.45, \n"
+ " \"positive\" : 346.7 \n"
+ "}\n";
+
+ // Create our test set of parameters.
+ ElementPtr elems;
+ ASSERT_NO_THROW(elems = Element::fromJSON(json)) << " invalid JSON, test is broken";
+
+ // Verify that a non-existant element is caught.
+ EXPECT_THROW(parser.getDouble(elems, "not-there"), DhcpConfigError);
+
+ // Verify that wrong element types are caught.
+ EXPECT_THROW(parser.getDouble(elems, "string"), DhcpConfigError);
+ EXPECT_THROW(parser.getDouble(elems, "int"), DhcpConfigError);
+ EXPECT_THROW(parser.getDouble(elems, "bool"), DhcpConfigError);
+ EXPECT_THROW(parser.getDouble(elems, "map"), DhcpConfigError);
+ EXPECT_THROW(parser.getDouble(elems, "list"), DhcpConfigError);
+
+ // Verify valid values are correct.
+ double value;
+
+ EXPECT_NO_THROW(value = parser.getDouble(elems, "zero"));
+ EXPECT_EQ(0.0, value);
+
+ EXPECT_NO_THROW(value = parser.getDouble(elems, "fraction"));
+ EXPECT_EQ(.75, value);
+
+ EXPECT_NO_THROW(value = parser.getDouble(elems, "negative"));
+ EXPECT_EQ(-1.45, value);
+
+ EXPECT_NO_THROW(value = parser.getDouble(elems, "positive"));
+ EXPECT_EQ(346.7, value);
+}