diff options
Diffstat (limited to 'src/lib/cc/tests/simple_parser_unittest.cc')
-rw-r--r-- | src/lib/cc/tests/simple_parser_unittest.cc | 364 |
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); +} |