From: Francis Dupont Date: Tue, 10 Jul 2018 12:16:07 +0000 (+0200) Subject: [5685] Added dependency methods and tests X-Git-Tag: 66-authoritative-flag-in-kea_base~27 X-Git-Url: http://git.ipfire.org/gitweb/gitweb.cgi?a=commitdiff_plain;h=0c0ae4697a1050cbdc64ec31545166148718c711;p=thirdparty%2Fkea.git [5685] Added dependency methods and tests --- diff --git a/src/lib/dhcpsrv/client_class_def.cc b/src/lib/dhcpsrv/client_class_def.cc index 18e2b3efc5..aeabd96d0f 100644 --- a/src/lib/dhcpsrv/client_class_def.cc +++ b/src/lib/dhcpsrv/client_class_def.cc @@ -6,6 +6,7 @@ #include +#include #include #include #include @@ -136,6 +137,11 @@ ClientClassDef::setCfgOption(const CfgOptionPtr& cfg_option) { cfg_option_ = cfg_option; } +bool +ClientClassDef::dependOnClass(const std::string& name) const { + return (isc::dhcp::dependOnClass(match_expr_, name)); +} + bool ClientClassDef::equals(const ClientClassDef& other) const { return ((name_ == other.name_) && @@ -279,6 +285,27 @@ ClientClassDictionary::getClasses() const { return (list_); } +bool +ClientClassDictionary::dependOnClass(const std::string& name, + std::string& depend) const { + // Skip previous classes as they should not depend on name. + bool found = false; + for (ClientClassDefList::iterator this_class = list_->begin(); + this_class != list_->end(); ++this_class) { + if (found) { + if ((*this_class)->dependOnClass(name)) { + depend = (*this_class)->getName(); + return (true); + } + } else { + if ((*this_class)->getName() == name) { + found = true; + } + } + } + return (false); +} + bool ClientClassDictionary::equals(const ClientClassDictionary& other) const { if (list_->size() != other.list_->size()) { diff --git a/src/lib/dhcpsrv/client_class_def.h b/src/lib/dhcpsrv/client_class_def.h index a20a36ee09..111458aedc 100644 --- a/src/lib/dhcpsrv/client_class_def.h +++ b/src/lib/dhcpsrv/client_class_def.h @@ -116,6 +116,13 @@ public: /// @param cfg_option the option collection to assign the class void setCfgOption(const CfgOptionPtr& cfg_option); + /// @brief Checks direct dependency. + /// + /// @param name The client class name. + /// + /// @return true if the definition depends on the class name, false if not. + bool dependOnClass(const std::string& name) const; + /// @brief Compares two @c ClientClassDef objects for equality. /// /// @param other Other client class definition to compare to. @@ -323,6 +330,14 @@ public: /// @return ClientClassDefListPtr to the list of classes const ClientClassDefListPtr& getClasses() const; + /// @brief Checks direct dependency. + /// + /// @param name The client class name. + /// @param depend Set to the name of the first depending class. + /// + /// @return true if a definition depends on the class name, false if none. + bool dependOnClass(const std::string& name, std::string& depend) const; + /// @brief Compares two @c ClientClassDictionary objects for equality. /// /// @param other Other client class definition to compare to. diff --git a/src/lib/dhcpsrv/tests/client_class_def_unittest.cc b/src/lib/dhcpsrv/tests/client_class_def_unittest.cc index 2fe8267705..e5f1c810bc 100644 --- a/src/lib/dhcpsrv/tests/client_class_def_unittest.cc +++ b/src/lib/dhcpsrv/tests/client_class_def_unittest.cc @@ -50,6 +50,10 @@ TEST(ClientClassDef, construction) { cfg_option = cclass->getCfgOption(); ASSERT_TRUE(cfg_option); EXPECT_TRUE(cfg_option->empty()); + + // Verify we don't depend on something. + EXPECT_FALSE(cclass->dependOnClass("foobar")); + EXPECT_FALSE(cclass->dependOnClass("")); } // Tests options operations. Note we just do the basics @@ -222,6 +226,81 @@ TEST(ClientClassDef, copyAndEquality) { EXPECT_TRUE(*cclass != *cclass2); } +// Verifies assignment. +TEST(ClientClassDef, assign) { + + boost::scoped_ptr cclass; + ExpressionPtr expr; + CfgOptionPtr test_options; + OptionPtr opt; + + // Make an expression + expr.reset(new Expression()); + TokenPtr token(new TokenString("boo")); + expr->push_back(token); + + // Create an option container with an option + OptionPtr option; + test_options.reset(new CfgOption()); + option.reset(new Option(Option::V4, 17, OptionBuffer(10, 0xFF))); + ASSERT_NO_THROW(test_options->add(option, false, DHCP4_OPTION_SPACE)); + + // Now remake the client class with cfg_option + ASSERT_NO_THROW(cclass.reset(new ClientClassDef("class_one", expr, + test_options))); + + // Now lets assign a fresh definition to it; + boost::scoped_ptr cclass2; + ExpressionPtr expr_empty; + ASSERT_NO_THROW(cclass2.reset(new ClientClassDef("another", expr_empty))); + ASSERT_NO_THROW(*cclass2 = *cclass); + + // The allocated Expression pointers should match + EXPECT_TRUE(cclass->getMatchExpr().get() == + cclass2->getMatchExpr().get()); + + // The allocated CfgOption pointers should match + EXPECT_TRUE(cclass->getCfgOption().get() == + cclass2->getCfgOption().get()); + + // Verify the equality tools reflect that the classes are equal. + EXPECT_TRUE(cclass->equals(*cclass2)); + EXPECT_TRUE(*cclass == *cclass2); + EXPECT_FALSE(*cclass != *cclass2); + + // Verify the required flag is enough to make classes not equal. + EXPECT_FALSE(cclass->getRequired()); + cclass2->setRequired(true); + EXPECT_TRUE(cclass2->getRequired()); + EXPECT_FALSE(*cclass == *cclass2); + EXPECT_TRUE(*cclass != *cclass2); + cclass2->setRequired(false); + EXPECT_TRUE(*cclass == *cclass2); + + // Verify the depend on known flag is enough to make classes not equal. + EXPECT_FALSE(cclass->getDependOnKnown()); + cclass2->setDependOnKnown(true); + EXPECT_TRUE(cclass2->getDependOnKnown()); + EXPECT_FALSE(*cclass == *cclass2); + EXPECT_TRUE(*cclass != *cclass2); +} + +// Tests dependency. +TEST(ClientClassDef, dependency) { + boost::scoped_ptr cclass; + + ExpressionPtr expr; + + // Make an expression + expr.reset(new Expression()); + TokenPtr token(new TokenMember("foo")); + expr->push_back(token); + + ASSERT_NO_THROW(cclass.reset(new ClientClassDef("class", expr))); + EXPECT_TRUE(cclass->dependOnClass("foo")); + EXPECT_FALSE(cclass->dependOnClass("bar")); +} + // Tests the basic operation of ClientClassDictionary // This includes adding, finding, and removing classes @@ -346,6 +425,58 @@ TEST(ClientClassDictionary, copyAndEquality) { EXPECT_TRUE(*dictionary != *dictionary2); } +// Tests dependency. +TEST(ClientClassDictionary, dependency) { + ClientClassDictionaryPtr dictionary(new ClientClassDictionary()); + ExpressionPtr expr; + CfgOptionPtr cfg_option; + + // Make an expression depending on forward class. + ExpressionPtr expr1; + expr1.reset(new Expression()); + TokenPtr token1(new TokenMember("cc2")); + expr1->push_back(token1); + + ASSERT_NO_THROW(dictionary->addClass("cc1", expr1, "", false, + false, cfg_option)); + + // Make an expression depending on first class. + ExpressionPtr expr2; + expr2.reset(new Expression()); + TokenPtr token2(new TokenMember("cc1")); + expr2->push_back(token2); + + ASSERT_NO_THROW(dictionary->addClass("cc2", expr2, "", false, + false, cfg_option)); + + // Make expression with dependency. + ASSERT_NO_THROW(dictionary->addClass("cc3", expr, "", false, + false, cfg_option)); + + ExpressionPtr expr3; + expr3.reset(new Expression()); + TokenPtr token3(new TokenMember("cc3")); + expr3->push_back(token3); + + ASSERT_NO_THROW(dictionary->addClass("cc4", expr3, "", false, + false, cfg_option)); + + // Not matching dependency does not match. + string depend; + EXPECT_FALSE(dictionary->dependOnClass("foobar", depend)); + EXPECT_TRUE(depend.empty()); + + // Forward dependency is ignored. + depend = ""; + EXPECT_FALSE(dictionary->dependOnClass("cc2", depend)); + EXPECT_TRUE(depend.empty()); + + // Backward dependency is detected. + depend = ""; + EXPECT_TRUE(dictionary->dependOnClass("cc3", depend)); + EXPECT_EQ("cc4", depend); +} + // Tests the default constructor regarding fixed fields TEST(ClientClassDef, fixedFieldsDefaults) { boost::scoped_ptr cclass; diff --git a/src/lib/eval/Makefile.am b/src/lib/eval/Makefile.am index 1942b255c4..b649005d9b 100644 --- a/src/lib/eval/Makefile.am +++ b/src/lib/eval/Makefile.am @@ -13,6 +13,7 @@ AM_CXXFLAGS += $(WARNING_GCC_44_STRICT_ALIASING_CFLAG) lib_LTLIBRARIES = libkea-eval.la libkea_eval_la_SOURCES = +libkea_eval_la_SOURCES += dependency.cc dependency.h libkea_eval_la_SOURCES += eval_log.cc eval_log.h libkea_eval_la_SOURCES += evaluate.cc evaluate.h libkea_eval_la_SOURCES += token.cc token.h diff --git a/src/lib/eval/dependency.cc b/src/lib/eval/dependency.cc new file mode 100644 index 0000000000..66e36007ec --- /dev/null +++ b/src/lib/eval/dependency.cc @@ -0,0 +1,37 @@ +// Copyright (C) 2018 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 + +#include +#include + +namespace isc { +namespace dhcp { + +bool dependOnClass(const TokenPtr& token, const std::string& name) { + boost::shared_ptr member; + member = boost::dynamic_pointer_cast(token); + if (!member) { + return (false); + } + return (member->getClientClass() == name); +} + +bool dependOnClass(const ExpressionPtr& expr, const std::string& name) { + if (!expr) { + return (false); + } + for (auto it = expr->cbegin(); it != expr->cend(); ++it) { + if (dependOnClass(*it, name)) { + return (true); + } + } + return (false); +} + +}; // end of isc::dhcp namespace +}; // end of isc namespace diff --git a/src/lib/eval/dependency.h b/src/lib/eval/dependency.h new file mode 100644 index 0000000000..001bed77bf --- /dev/null +++ b/src/lib/eval/dependency.h @@ -0,0 +1,37 @@ +// Copyright (C) 2018 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/. + +#ifndef DEPENDENCY_H +#define DEPENDENCY_H + +#include +#include + +namespace isc { +namespace dhcp { + +/// @brief Checks dependency on a token. +/// +/// It checks if the token is a TokenMember for the given class. +/// +/// @param token A pointer to the token. +/// @param name The client class name. +/// @return true if token points to a TokenMember of name, false if not. +bool dependOnClass(const TokenPtr& token, const std::string& name); + +/// @brief Checks dependency on an expression. +/// +/// It checks if a member of the expression depends on the given class. +/// +/// @param expr An expression. +/// @param name The client class name. +/// @return true if a member of expr depends on name, false if not. +bool dependOnClass(const ExpressionPtr& expr, const std::string& name); + +}; // end of isc::dhcp namespace +}; // end of isc namespace + +#endif diff --git a/src/lib/eval/tests/Makefile.am b/src/lib/eval/tests/Makefile.am index d0c3e58089..e29351271d 100644 --- a/src/lib/eval/tests/Makefile.am +++ b/src/lib/eval/tests/Makefile.am @@ -22,6 +22,7 @@ TESTS += libeval_unittests libeval_unittests_SOURCES = boolean_unittest.cc libeval_unittests_SOURCES += context_unittest.cc +libeval_unittests_SOURCES += dependency_unittest.cc libeval_unittests_SOURCES += evaluate_unittest.cc libeval_unittests_SOURCES += token_unittest.cc libeval_unittests_SOURCES += run_unittests.cc diff --git a/src/lib/eval/tests/dependency_unittest.cc b/src/lib/eval/tests/dependency_unittest.cc new file mode 100644 index 0000000000..6eaee1da4a --- /dev/null +++ b/src/lib/eval/tests/dependency_unittest.cc @@ -0,0 +1,98 @@ +// Copyright (C) 2018 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace std; +using namespace isc::dhcp; + +namespace { + +/// @brief Test fixture for testing dependency. +/// +/// This class provides several convenience objects to be used during testing +/// of the dependcy of classification expressions. +class DependencyTest : public ::testing::Test { +public: + + /// @brief Reset expression and result. + ~DependencyTest() { + e_.reset(); + result_ = false; + } + + ExpressionPtr e_; ///< An expression + + bool result_; ///< A decision +}; + +// This checks the null expression: it should return false. +TEST_F(DependencyTest, nullExpr) { + TokenPtr token; + ASSERT_NO_THROW(result_ = dependOnClass(token, "foobar")); + EXPECT_FALSE(result_); + ASSERT_NO_THROW(result_ = dependOnClass(e_, "foobar")); + EXPECT_FALSE(result_); +} + +// This checks the empty expression: it should return false. +TEST_F(DependencyTest, emptyExpr) { + e_.reset(new Expression()); + ASSERT_NO_THROW(result_ = dependOnClass(e_, "foobar")); + EXPECT_FALSE(result_); +} + +// This checks the { "true" } expression: it should return false. +TEST_F(DependencyTest, trueExpr) { + TokenPtr ttrue; + ASSERT_NO_THROW(ttrue.reset(new TokenString("true"))); + ASSERT_NO_THROW(result_ = dependOnClass(ttrue, "foobar")); + EXPECT_FALSE(result_); + e_.reset(new Expression()); + e_->push_back(ttrue); + ASSERT_NO_THROW(result_ = dependOnClass(e_, "foobar")); + EXPECT_FALSE(result_); +} + +// This checks the { member('not-matching') } expression: +// it should return false. +TEST_F(DependencyTest, notMatching) { + TokenPtr notmatching; + ASSERT_NO_THROW(notmatching.reset(new TokenMember("not-matching"))); + ASSERT_NO_THROW(result_ = dependOnClass(notmatching, "foobar")); + EXPECT_FALSE(result_); + e_.reset(new Expression()); + e_->push_back(notmatching); + ASSERT_NO_THROW(result_ = dependOnClass(e_, "foobar")); + EXPECT_FALSE(result_); +} + +// This checks the { member('foobar') } expression: it should return true. +TEST_F(DependencyTest, matching) { + TokenPtr matching; + ASSERT_NO_THROW(matching.reset(new TokenMember("foobar"))); + ASSERT_NO_THROW(result_ = dependOnClass(matching, "foobar")); + EXPECT_TRUE(result_); + e_.reset(new Expression()); + e_->push_back(matching); + result_ = false; + ASSERT_NO_THROW(result_ = dependOnClass(e_, "foobar")); + EXPECT_TRUE(result_); +} + +};