diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 11:36:04 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 11:36:04 +0000 |
commit | 040eee1aa49b49df4698d83a05af57c220127fd1 (patch) | |
tree | f635435954e6ccde5eee9893889e24f30ca68346 /src/lib/eval/tests/evaluate_unittest.cc | |
parent | Initial commit. (diff) | |
download | isc-kea-040eee1aa49b49df4698d83a05af57c220127fd1.tar.xz isc-kea-040eee1aa49b49df4698d83a05af57c220127fd1.zip |
Adding upstream version 2.2.0.upstream/2.2.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib/eval/tests/evaluate_unittest.cc')
-rw-r--r-- | src/lib/eval/tests/evaluate_unittest.cc | 515 |
1 files changed, 515 insertions, 0 deletions
diff --git a/src/lib/eval/tests/evaluate_unittest.cc b/src/lib/eval/tests/evaluate_unittest.cc new file mode 100644 index 0000000..37c4f83 --- /dev/null +++ b/src/lib/eval/tests/evaluate_unittest.cc @@ -0,0 +1,515 @@ +// Copyright (C) 2015-2018,2021 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 <eval/evaluate.h> +#include <eval/eval_context.h> +#include <eval/token.h> +#include <dhcp/pkt4.h> +#include <dhcp/pkt6.h> +#include <dhcp/dhcp4.h> +#include <dhcp/dhcp6.h> +#include <dhcp/option_string.h> + +#include <boost/shared_ptr.hpp> +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +using namespace std; +using namespace isc::dhcp; + +namespace { + +/// @brief Test fixture for testing evaluation. +/// +/// This class provides several convenience objects to be used during testing +/// of the evaluation of classification expressions. +class EvaluateTest : public ::testing::Test { +public: + + /// @brief Initializes Pkt4,Pkt6 and options that can be useful for + /// evaluation tests. + EvaluateTest() { + e_.clear(); + + result_ = false; + + pkt4_.reset(new Pkt4(DHCPDISCOVER, 12345)); + pkt6_.reset(new Pkt6(DHCPV6_SOLICIT, 12345)); + + // Add options with easily identifiable strings in them + option_str4_.reset(new OptionString(Option::V4, 100, "hundred4")); + option_str6_.reset(new OptionString(Option::V6, 100, "hundred6")); + + pkt4_->addOption(option_str4_); + pkt6_->addOption(option_str6_); + } + + Expression e_; ///< An expression + + bool result_; ///< A decision + + Pkt4Ptr pkt4_; ///< A stub DHCPv4 packet + Pkt6Ptr pkt6_; ///< A stub DHCPv6 packet + + OptionPtr option_str4_; ///< A string option for DHCPv4 + OptionPtr option_str6_; ///< A string option for DHCPv6 + + /// @todo: Add more option types here +}; + +// This checks the empty expression: it should raise EvalBadStack +// when evaluated with a Pkt4. (The actual packet is not used) +TEST_F(EvaluateTest, empty4) { + ASSERT_THROW(evaluateBool(e_, *pkt4_), EvalBadStack); +} + +// This checks the empty expression: it should raise EvalBadStack +// when evaluated with a Pkt6. (The actual packet is not used) +TEST_F(EvaluateTest, empty6) { + ASSERT_THROW(evaluateBool(e_, *pkt6_), EvalBadStack); +} + +// This checks the { "false" } expression: it should return false +// when evaluated with a Pkt4. (The actual packet is not used) +TEST_F(EvaluateTest, false4) { + TokenPtr tfalse; + ASSERT_NO_THROW(tfalse.reset(new TokenString("false"))); + e_.push_back(tfalse); + ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_)); + EXPECT_FALSE(result_); +} + +// This checks the { "false" } expression: it should return false +// when evaluated with a Pkt6. (The actual packet is not used) +TEST_F(EvaluateTest, false6) { + TokenPtr tfalse; + ASSERT_NO_THROW(tfalse.reset(new TokenString("false"))); + e_.push_back(tfalse); + ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_)); + EXPECT_FALSE(result_); +} + +// This checks the { "true" } expression: it should return true +// when evaluated with a Pkt4. (The actual packet is not used) +TEST_F(EvaluateTest, true4) { + TokenPtr ttrue; + ASSERT_NO_THROW(ttrue.reset(new TokenString("true"))); + e_.push_back(ttrue); + ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_)); + EXPECT_TRUE(result_); +} + +// This checks the { "true" } expression: it should return true +// when evaluated with a Pkt6. (The actual packet is not used) +TEST_F(EvaluateTest, true6) { + TokenPtr ttrue; + ASSERT_NO_THROW(ttrue.reset(new TokenString("true"))); + e_.push_back(ttrue); + ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_)); + EXPECT_TRUE(result_); +} + +// This checks the evaluation must lead to "false" or "true" +// with a Pkt4. (The actual packet is not used) +TEST_F(EvaluateTest, bad4) { + TokenPtr bad; + ASSERT_NO_THROW(bad.reset(new TokenString("bad"))); + e_.push_back(bad); + ASSERT_THROW(evaluateBool(e_, *pkt4_), EvalTypeError); +} + +// This checks the evaluation must lead to "false" or "true" +// with a Pkt6. (The actual packet is not used) +TEST_F(EvaluateTest, bad6) { + TokenPtr bad; + ASSERT_NO_THROW(bad.reset(new TokenString("bad"))); + e_.push_back(bad); + ASSERT_THROW(evaluateBool(e_, *pkt6_), EvalTypeError); +} + +// This checks the evaluation must leave only one value on the stack +// with a Pkt4. (The actual packet is not used) +TEST_F(EvaluateTest, two4) { + TokenPtr ttrue; + ASSERT_NO_THROW(ttrue.reset(new TokenString("true"))); + e_.push_back(ttrue); + e_.push_back(ttrue); + ASSERT_THROW(evaluateBool(e_, *pkt4_), EvalBadStack); +} + +// This checks the evaluation must leave only one value on the stack +// with a Pkt6. (The actual packet is not used) +TEST_F(EvaluateTest, two6) { + TokenPtr ttrue; + ASSERT_NO_THROW(ttrue.reset(new TokenString("true"))); + e_.push_back(ttrue); + e_.push_back(ttrue); + ASSERT_THROW(evaluateBool(e_, *pkt6_), EvalBadStack); +} + +// A more complex test evaluated with a Pkt4. (The actual packet is not used) +TEST_F(EvaluateTest, compare4) { + TokenPtr tfoo; + TokenPtr tbar; + TokenPtr tequal; + + ASSERT_NO_THROW(tfoo.reset(new TokenString("foo"))); + e_.push_back(tfoo); + ASSERT_NO_THROW(tbar.reset(new TokenString("bar"))); + e_.push_back(tbar); + ASSERT_NO_THROW(tequal.reset(new TokenEqual())); + e_.push_back(tequal); + + ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_)); + EXPECT_FALSE(result_); +} + +// A more complex test evaluated with a Pkt6. (The actual packet is not used) +TEST_F(EvaluateTest, compare6) { + TokenPtr tfoo; + TokenPtr tbar; + TokenPtr tequal; + + ASSERT_NO_THROW(tfoo.reset(new TokenString("foo"))); + e_.push_back(tfoo); + ASSERT_NO_THROW(tbar.reset(new TokenString("bar"))); + e_.push_back(tbar); + ASSERT_NO_THROW(tequal.reset(new TokenEqual())); + e_.push_back(tequal); + + ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_)); + EXPECT_FALSE(result_); +} + +// A test using option existence +TEST_F(EvaluateTest, exists) { + TokenPtr toption; + + ASSERT_NO_THROW(toption.reset(new TokenOption(100, TokenOption::EXISTS))); + e_.push_back(toption); + + ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_)); + EXPECT_TRUE(result_); + ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_)); + EXPECT_TRUE(result_); +} + +// A test using option non-existence +TEST_F(EvaluateTest, dontExists) { + TokenPtr toption; + + ASSERT_NO_THROW(toption.reset(new TokenOption(101, TokenOption::EXISTS))); + e_.push_back(toption); + + ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_)); + EXPECT_FALSE(result_); + ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_)); + EXPECT_FALSE(result_); +} + +// A test using packets. +TEST_F(EvaluateTest, packet) { + TokenPtr toption; + TokenPtr tstring; + TokenPtr tequal; + + ASSERT_NO_THROW(toption.reset(new TokenOption(100, TokenOption::TEXTUAL))); + e_.push_back(toption); + ASSERT_NO_THROW(tstring.reset(new TokenString("hundred4"))); + e_.push_back(tstring); + ASSERT_NO_THROW(tequal.reset(new TokenEqual())); + e_.push_back(tequal); + + ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_)); + EXPECT_TRUE(result_); + ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_)); + EXPECT_FALSE(result_); +} + +// A test which compares option value represented in hexadecimal format. +TEST_F(EvaluateTest, optionHex) { + TokenPtr toption; + TokenPtr tstring; + TokenPtr tequal; + + ASSERT_NO_THROW(toption.reset(new TokenOption(100, TokenOption::HEXADECIMAL))); + e_.push_back(toption); + ASSERT_NO_THROW(tstring.reset(new TokenString("hundred4"))); + e_.push_back(tstring); + ASSERT_NO_THROW(tequal.reset(new TokenEqual())); + e_.push_back(tequal); + + ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_)); + EXPECT_TRUE(result_); + ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_)); + EXPECT_FALSE(result_); +} + +// A test using substring on an option. +TEST_F(EvaluateTest, complex) { + TokenPtr toption; + TokenPtr tstart; + TokenPtr tlength; + TokenPtr tsubstring; + TokenPtr tstring; + TokenPtr tequal; + + // Get the option, i.e., "hundred[46]" + ASSERT_NO_THROW(toption.reset(new TokenOption(100, TokenOption::TEXTUAL))); + e_.push_back(toption); + + // Get substring("hundred[46]", 0, 7), i.e., "hundred" + ASSERT_NO_THROW(tstart.reset(new TokenString("0"))); + e_.push_back(tstart); + ASSERT_NO_THROW(tlength.reset(new TokenString("7"))); + e_.push_back(tlength); + ASSERT_NO_THROW(tsubstring.reset(new TokenSubstring())); + e_.push_back(tsubstring); + + // Compare with "hundred" + ASSERT_NO_THROW(tstring.reset(new TokenString("hundred"))); + e_.push_back(tstring); + ASSERT_NO_THROW(tequal.reset(new TokenEqual())); + e_.push_back(tequal); + + // Should return true for v4 and v6 packets + ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_)); + EXPECT_TRUE(result_); + ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_)); + EXPECT_TRUE(result_); +} + +/// @brief Generic class for parsing expressions and evaluating them. +/// +/// The main purpose of this class is to provide a generic interface to the +/// eval library, so everything (expression parsing and then evaluation for +/// given packets) can be done in one simple call. +/// +/// These tests may be somewhat redundant to other more specialized tests, but +/// the idea here is to mass produce tests that are trivial to write. +class ExpressionsTest : public EvaluateTest { +public: + + /// @brief Checks if expression can be parsed and evaluated to bool + /// + /// There are skeleton packets created in pkt4_ and pkt6_. Make sure you + /// tweak them as needed before calling this method. + /// + /// @param u universe (V4 or V6) + /// @param expr expression to be parsed + /// @param exp_result expected result (true or false) + void testExpression(const Option::Universe& u, const std::string& expr, + const bool exp_result) { + + EvalContext eval(u); + bool result = false; + bool parsed = false; + + EXPECT_NO_THROW(parsed = eval.parseString(expr)) + << " while parsing expression " << expr; + EXPECT_TRUE(parsed) << " for expression " << expr; + + switch (u) { + case Option::V4: + ASSERT_NO_THROW(result = evaluateBool(eval.expression, *pkt4_)) + << " for expression " << expr; + break; + case Option::V6: + ASSERT_NO_THROW(result = evaluateBool(eval.expression, *pkt6_)) + << " for expression " << expr; + break; + } + + EXPECT_EQ(exp_result, result) << " for expression " << expr; + } + + /// @brief Checks if expression can be parsed and evaluated to string + /// + /// There are skeleton packets created in pkt4_ and pkt6_. Make sure you + /// tweak them as needed before calling this method. + /// + /// @param u universe (V4 or V6) + /// @param expr expression to be parsed + /// @param exp_result expected result (string) + void testExpressionString(const Option::Universe& u, const std::string& expr, + const std::string& exp_result) { + + EvalContext eval(u); + string result; + bool parsed = false; + + EXPECT_NO_THROW(parsed = eval.parseString(expr, EvalContext::PARSER_STRING)) + << " while parsing expression " << expr; + EXPECT_TRUE(parsed) << " for expression " << expr; + + switch (u) { + case Option::V4: + ASSERT_NO_THROW(result = evaluateString(eval.expression, *pkt4_)) + << " for expression " << expr; + break; + case Option::V6: + ASSERT_NO_THROW(result = evaluateString(eval.expression, *pkt6_)) + << " for expression " << expr; + break; + } + + EXPECT_EQ(exp_result, result) << " for expression " << expr; + } + + /// @brief Checks that specified expression throws expected exception. + /// + /// @tparam ex exception type expected to be thrown + /// @param expr expression to be evaluated + template<typename ex> + void testExpressionNegative(const std::string& expr, + const Option::Universe& u = Option::V4, + EvalContext::ParserType type = EvalContext::PARSER_BOOL) { + EvalContext eval(u); + + EXPECT_THROW(eval.parseString(expr, type), ex) << "while parsing expression " + << expr; + } +}; + +// This is a quick way to check if certain expressions are valid or not and +// whether the whole expression makes sense. This particular test checks if +// integers can be used properly in expressions. There are many places where +// integers are used. This particular test checks if pkt6.msgtype returns +// something that can be compared with integers. +// +// For basic things we can take advantage of the skeleton packets created in +// EvaluateTest constructors: The packet type is DISCOVER in DHCPv4 and +// SOLICIT in DHCPv6. There is one option added with code 100 and content +// being either "hundred4" or "hundred6" depending on the universe. + +// Tests if pkt6.msgtype returns something that can be compared with integers. +TEST_F(ExpressionsTest, expressionsInteger1) { + testExpression(Option::V6, "pkt6.msgtype == 1", true); + testExpression(Option::V6, "pkt6.msgtype == 2", false); + + testExpression(Option::V6, "pkt6.msgtype == 0x00000001", true); + testExpression(Option::V6, "pkt6.msgtype == 0x00000002", false); +} + +// Tests if pkt6.transid returns something that can be compared with integers. +TEST_F(ExpressionsTest, expressionsInteger2) { + testExpression(Option::V6, "pkt6.transid == 0", false); + testExpression(Option::V6, "pkt6.transid == 12345", true); + testExpression(Option::V6, "pkt6.transid == 12346", false); +} + +// Tests if pkt4.transid returns something that can be compared with integers. +TEST_F(ExpressionsTest, expressionsInteger3) { + testExpression(Option::V4, "pkt4.transid == 0", false); + testExpression(Option::V4, "pkt4.transid == 12345", true); + testExpression(Option::V4, "pkt4.transid == 12346", false); +} + +// Tests if integers can be compared with integers. +TEST_F(ExpressionsTest, expressionsInteger4) { + testExpression(Option::V6, "0 == 0", true); + testExpression(Option::V6, "2 == 3", false); +} + +// Tests if pkt4.hlen and pkt4.htype return values that can be compared with integers. +TEST_F(ExpressionsTest, expressionsPkt4Hlen) { + + // By default there's no hardware set up. The default Pkt4 constructor + // creates HWAddr(), which has hlen=0 and htype set to HTYPE_ETHER. + testExpression(Option::V4, "pkt4.hlen == 0", true); + testExpression(Option::V4, "pkt4.htype == 1", true); + + // Ok, let's initialize the hardware address to something plausible. + const size_t hwaddr_len = 6; + const uint16_t expected_htype = 123; + std::vector<uint8_t> hw(hwaddr_len,0); + for (int i = 0; i < hwaddr_len; i++) { + hw[i] = i + 1; + } + pkt4_->setHWAddr(expected_htype, hwaddr_len, hw); + + testExpression(Option::V4, "pkt4.hlen == 0", false); + testExpression(Option::V4, "pkt4.hlen == 5", false); + testExpression(Option::V4, "pkt4.hlen == 6", true); + testExpression(Option::V4, "pkt4.hlen == 7", false); + + testExpression(Option::V4, "pkt4.htype == 0", false); + testExpression(Option::V4, "pkt4.htype == 122", false); + testExpression(Option::V4, "pkt4.htype == 123", true); + testExpression(Option::V4, "pkt4.htype == 124", false); + + testExpression(Option::V4, "pkt4.mac == 0x010203040506", true); +} + +// Test if expressions message type can be detected in Pkt4. +// It also doubles as a check for integer comparison here. +TEST_F(ExpressionsTest, expressionsPkt4type) { + + // We can inspect the option content directly, but + // it requires knowledge of the option type and its format. + testExpression(Option::V4, "option[53].hex == 0x0", false); + testExpression(Option::V4, "option[53].hex == 0x1", true); + testExpression(Option::V4, "option[53].hex == 0x2", false); + + // It's easier to simply use the pkt4.msgtype + testExpression(Option::V4, "pkt4.msgtype == 0", false); + testExpression(Option::V4, "pkt4.msgtype == 1", true); + testExpression(Option::V4, "pkt4.msgtype == 2", false); +} + +// This tests if inappropriate values (negative, too large) are +// rejected, but extreme values still allowed for uint32_t are ok. +TEST_F(ExpressionsTest, invalidIntegers) { + + // These are the extreme uint32_t values that still should be accepted. + testExpression(Option::V4, "4294967295 == 0", false); + + // Negative integers should be rejected. + testExpressionNegative<EvalParseError>("4294967295 == -1"); + + // Oops, one too much. + testExpressionNegative<EvalParseError>("4294967296 == 0"); +} + +// Tests whether expressions can be evaluated to a string. +TEST_F(ExpressionsTest, evaluateString) { + + // Check that content of the options is returned properly. + testExpressionString(Option::V4, "option[100].hex", "hundred4"); + testExpressionString(Option::V6, "option[100].hex", "hundred6"); + + // Check that content of non-existing option returns empty string. + testExpressionString(Option::V4, "option[200].hex", ""); + testExpressionString(Option::V6, "option[200].hex", ""); + + testExpressionNegative<EvalParseError>("pkt4.msgtype == 1", Option::V4, + EvalContext::PARSER_STRING); + testExpressionNegative<EvalParseError>("pkt6.msgtype == 1", Option::V6, + EvalContext::PARSER_STRING); + + // Check that ifelse works as expecting (it was added explicitly for + // the string evaluation). + testExpressionString(Option::V4, + "ifelse(option[100].exists,'foo','bar')", "foo"); + testExpressionString(Option::V4, + "ifelse(option[200].exists,'foo','bar')", "bar"); + + // Check that ifelse can be chained. + testExpressionString(Option::V4, + "ifelse(option[200].exists,option[200].hex," + "ifelse(option[100].exists," + "option[100].hex,'none?'))", + "hundred4"); + + // Check that hexstring works as expecting. + testExpressionString(Option::V4, "hexstring(0x1234,':')", "12:34"); + testExpressionString(Option::V4, "hexstring(0x56789a,'-')", "56-78-9a"); + testExpressionString(Option::V4, "hexstring(0xbcde,'')", "bcde"); + testExpressionString(Option::V4, "hexstring(0xf01234,'..')", "f0..12..34"); +} + +}; |