From 4a905809afb51ec4a29e567319320da77ff965fe Mon Sep 17 00:00:00 2001 From: Noah Attwood Date: Tue, 9 May 2023 23:54:25 -0300 Subject: [PATCH 1/5] adds clone function to circumvent object slicing --- include/attwoodn/expression_tree.hpp | 94 ++++++++++++++++++++++++++-- tests/expression_tree_op_node.cpp | 4 +- tests/test_utils.hpp | 2 +- 3 files changed, 92 insertions(+), 8 deletions(-) diff --git a/include/attwoodn/expression_tree.hpp b/include/attwoodn/expression_tree.hpp index cafcb31..b09e80d 100644 --- a/include/attwoodn/expression_tree.hpp +++ b/include/attwoodn/expression_tree.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include namespace attwoodn::expression_tree { @@ -53,13 +54,13 @@ namespace attwoodn::expression_tree { } } - enum class boolean_op { - AND, - OR - }; - namespace node { + enum class boolean_op { + AND, + OR + }; + template class expression_tree_op_node; @@ -84,6 +85,16 @@ namespace attwoodn::expression_tree { * nodes under this node in the expression tree. */ virtual bool evaluate(const Obj& obj) = 0; + + /** + * @brief Performs a deep clone of pointers to this base class to avoid object slicing. + */ + auto clone() { + return std::unique_ptr>(clone_impl()); + } + + protected: + virtual expression_tree_node* clone_impl() const = 0; }; /** @@ -209,6 +220,11 @@ namespace attwoodn::expression_tree { boolean_op bool_op_; LeftChild* left_ { nullptr }; RightChild* right_ { nullptr }; + + protected: + virtual expression_tree_op_node* clone_impl() const override { + return new expression_tree_op_node(*this); + } }; /** @@ -332,6 +348,11 @@ namespace attwoodn::expression_tree { const CompValue Obj::* member_var_ = nullptr; Op logical_op_; CompValue comp_value_; + + protected: + virtual expression_tree_leaf_node* clone_impl() const override { + return new expression_tree_leaf_node(*this); + } }; } @@ -362,4 +383,67 @@ namespace attwoodn::expression_tree { node::expression_tree_leaf_node* make_expr( CompValue (Obj::* member_func)() const, Op op, CompValue comp_value ) { return new node::expression_tree_leaf_node( member_func, op, comp_value ); } + + template + class expression_tree { + public: + expression_tree() = delete; + + expression_tree(node::expression_tree_node* expr) { + if(!expr) { + throw std::runtime_error("Attempted to construct an expression_tree with a null root expression node"); + } + + expr_ = expr->clone().release(); + delete expr; + } + + expression_tree(const expression_tree& other) + : expr_(other.expr_->clone().release()) {} + + expression_tree(expression_tree&& other) + : expr_(other.expr_) { + other.expr_ = nullptr; + } + + expression_tree& operator=(const expression_tree& other) { + delete expr_; + expr_ = other.expr_->clone().release(); + return *this; + } + + expression_tree& operator=(const expression_tree&& other) { + if(this != &other) { + delete expr_; + expr_ = other.expr_; + other.expr_ = nullptr; + } + return *this; + } + + ~expression_tree() { + delete expr_; + } + + /** + * @brief Evaluates the given object to determine if it satisfies the expressions defined in this expression tree. + * + * @returns True if the given object satisfied the expression tree conditions; + * False if the given object did not satisfy the expression tree conditions. + */ + bool evaluate(const Obj& obj) { + if(!expr_) { + throw std::runtime_error("expression_tree has a null root expression node"); + } + + try { + return expr_->evaluate(obj); + } catch(std::exception& e) { + return false; + } + } + + private: + node::expression_tree_node* expr_ = nullptr; + }; } \ No newline at end of file diff --git a/tests/expression_tree_op_node.cpp b/tests/expression_tree_op_node.cpp index 66323e5..93e5146 100644 --- a/tests/expression_tree_op_node.cpp +++ b/tests/expression_tree_op_node.cpp @@ -21,7 +21,7 @@ void test_AND_op_node_evaluation() { auto child_expr1 = make_expr(&test_fixture::some_string, op::equals, std::string("hello, world!")); auto child_expr2 = make_expr(&test_fixture::some_uint, op::less_than, (uint16_t) 500); - auto expr = make_op_node(child_expr1, boolean_op::AND, child_expr2); + auto expr = make_op_node(child_expr1, node::boolean_op::AND, child_expr2); test_fixture fixture; fixture.some_string = "hello, world!"; @@ -72,7 +72,7 @@ void test_OR_op_node_evaluation() { auto child_expr1 = make_expr(&test_fixture::some_string, op::equals, std::string("hello, world!")); auto child_expr2 = make_expr(&test_fixture::some_uint, op::less_than, (uint16_t) 500); - auto expr = make_op_node(child_expr1, boolean_op::OR, child_expr2); + auto expr = make_op_node(child_expr1, node::boolean_op::OR, child_expr2); test_fixture fixture; fixture.some_string = "hello, world!"; diff --git a/tests/test_utils.hpp b/tests/test_utils.hpp index 2cfe300..f915f87 100644 --- a/tests/test_utils.hpp +++ b/tests/test_utils.hpp @@ -21,7 +21,7 @@ struct test_fixture { * Helper function for creating inner expression tree nodes. I'm undecided if this should be included in the public API. */ template -std::unique_ptr> make_op_node(LeftChild* left, et::boolean_op op, RightChild* right) { +std::unique_ptr> make_op_node(LeftChild* left, et::node::boolean_op op, RightChild* right) { auto node = new et::node::expression_tree_op_node(op); node->set_left(left); node->set_right(right); From c4382865f80a006146b5e4a30ae81431991dc1af Mon Sep 17 00:00:00 2001 From: Noah Attwood Date: Wed, 10 May 2023 00:16:48 -0300 Subject: [PATCH 2/5] modifies AND/OR functions for easier reading through auto return types, removes deprecated test helper function --- include/attwoodn/expression_tree.hpp | 64 ++++++++++++++-------------- tests/expression_tree_op_node.cpp | 16 +++---- tests/test_utils.hpp | 13 +----- 3 files changed, 41 insertions(+), 52 deletions(-) diff --git a/include/attwoodn/expression_tree.hpp b/include/attwoodn/expression_tree.hpp index b09e80d..65bcdbe 100644 --- a/include/attwoodn/expression_tree.hpp +++ b/include/attwoodn/expression_tree.hpp @@ -162,10 +162,10 @@ namespace attwoodn::expression_tree { * and the other node that was AND'ed with this node. This node becomes the left child. The other node becomes * the right child. */ - template>::value>* = nullptr> - expression_tree_op_node* AND (OtherLeafNode* other) { - auto* op_node = new expression_tree_op_node(boolean_op::AND); + template + auto* AND (expression_tree_leaf_node* other) { + using ret = expression_tree_op_node>; + ret* op_node = new ret(boolean_op::AND); op_node->set_left(this); op_node->set_right(other); return op_node; @@ -177,10 +177,10 @@ namespace attwoodn::expression_tree { * and the other node that was OR'ed with this node. This node becomes the left child. The other node becomes * the right child. */ - template>::value>* = nullptr> - expression_tree_op_node* OR (OtherLeafNode* other) { - auto* op_node = new expression_tree_op_node(boolean_op::OR); + template + auto* OR (expression_tree_leaf_node* other) { + using ret = expression_tree_op_node>; + ret* op_node = new ret(boolean_op::OR); op_node->set_left(this); op_node->set_right(other); return op_node; @@ -192,10 +192,10 @@ namespace attwoodn::expression_tree { * and the other node that was AND'ed with this node. This node becomes the left child. The other node becomes * the right child. */ - template>::value>* = nullptr> - expression_tree_op_node* AND (OtherOpNode* other) { - auto* op_node = new expression_tree_op_node(boolean_op::AND); + template + auto* AND (expression_tree_op_node* other) { + using ret = expression_tree_op_node>; + ret* op_node = new ret(boolean_op::AND); op_node->set_left(this); op_node->set_right(other); return op_node; @@ -207,10 +207,10 @@ namespace attwoodn::expression_tree { * and the other node that was OR'ed with this node. This node becomes the left child. The other node becomes * the right child. */ - template>::value>* = nullptr> - expression_tree_op_node* OR (OtherOpNode* other) { - auto* op_node = new expression_tree_op_node(boolean_op::OR); + template + auto* OR (expression_tree_op_node* other) { + using ret = expression_tree_op_node>; + ret* op_node = new ret(boolean_op::OR); op_node->set_left(this); op_node->set_right(other); return op_node; @@ -289,10 +289,10 @@ namespace attwoodn::expression_tree { * and the other node that was AND'ed with this node. This node becomes the left child. The other node becomes * the right child. */ - template>::value>* = nullptr> - expression_tree_op_node* AND (OtherLeafNode* other) { - auto* op_node = new expression_tree_op_node(boolean_op::AND); + template + auto* AND (expression_tree_leaf_node* other) { + using ret = expression_tree_op_node>; + ret* op_node = new ret(boolean_op::AND); op_node->set_left(this); op_node->set_right(other); return op_node; @@ -304,10 +304,10 @@ namespace attwoodn::expression_tree { * and the other node that was OR'ed with this node. This node becomes the left child. The other node becomes * the right child. */ - template>::value>* = nullptr> - expression_tree_op_node* OR (OtherLeafNode* other) { - auto* op_node = new expression_tree_op_node(boolean_op::OR); + template + auto* OR (expression_tree_leaf_node* other) { + using ret = expression_tree_op_node>; + ret* op_node = new ret(boolean_op::OR); op_node->set_left(this); op_node->set_right(other); return op_node; @@ -319,10 +319,10 @@ namespace attwoodn::expression_tree { * and the other node that was AND'ed with this node. This node becomes the left child. The other node becomes * the right child. */ - template>::value>* = nullptr> - expression_tree_op_node* AND (OtherOpNode* other) { - auto* op_node = new expression_tree_op_node(boolean_op::AND); + template + auto* AND (expression_tree_op_node* other) { + using ret = expression_tree_op_node>; + ret* op_node = new ret(boolean_op::AND); op_node->set_left(this); op_node->set_right(other); return op_node; @@ -334,10 +334,10 @@ namespace attwoodn::expression_tree { * and the other node that was OR'ed with this node. This node becomes the left child. The other node becomes * the right child. */ - template>::value>* = nullptr> - expression_tree_op_node* OR (OtherOpNode* other) { - auto* op_node = new expression_tree_op_node(boolean_op::OR); + template + auto* OR (expression_tree_op_node* other) { + using ret = expression_tree_op_node>; + ret* op_node = new ret(boolean_op::OR); op_node->set_left(this); op_node->set_right(other); return op_node; diff --git a/tests/expression_tree_op_node.cpp b/tests/expression_tree_op_node.cpp index 93e5146..1cd2b74 100644 --- a/tests/expression_tree_op_node.cpp +++ b/tests/expression_tree_op_node.cpp @@ -18,10 +18,10 @@ int main(int argc, char** argv) { } void test_AND_op_node_evaluation() { - auto child_expr1 = make_expr(&test_fixture::some_string, op::equals, std::string("hello, world!")); - auto child_expr2 = make_expr(&test_fixture::some_uint, op::less_than, (uint16_t) 500); - - auto expr = make_op_node(child_expr1, node::boolean_op::AND, child_expr2); + auto expr = std::unique_ptr>( + make_expr(&test_fixture::some_string, op::equals, std::string("hello, world!")) + ->AND(make_expr(&test_fixture::some_uint, op::less_than, (uint16_t) 500)) + ); test_fixture fixture; fixture.some_string = "hello, world!"; @@ -69,10 +69,10 @@ void test_AND_op_node_evaluation() { } void test_OR_op_node_evaluation() { - auto child_expr1 = make_expr(&test_fixture::some_string, op::equals, std::string("hello, world!")); - auto child_expr2 = make_expr(&test_fixture::some_uint, op::less_than, (uint16_t) 500); - - auto expr = make_op_node(child_expr1, node::boolean_op::OR, child_expr2); + auto expr = std::unique_ptr>( + make_expr(&test_fixture::some_string, op::equals, std::string("hello, world!")) + ->OR(make_expr(&test_fixture::some_uint, op::less_than, (uint16_t) 500)) + ); test_fixture fixture; fixture.some_string = "hello, world!"; diff --git a/tests/test_utils.hpp b/tests/test_utils.hpp index f915f87..fb4a6a0 100644 --- a/tests/test_utils.hpp +++ b/tests/test_utils.hpp @@ -15,15 +15,4 @@ struct test_fixture { bool is_some_uint_greater_than_zero() const { return some_uint; } -}; - -/** - * Helper function for creating inner expression tree nodes. I'm undecided if this should be included in the public API. -*/ -template -std::unique_ptr> make_op_node(LeftChild* left, et::node::boolean_op op, RightChild* right) { - auto node = new et::node::expression_tree_op_node(op); - node->set_left(left); - node->set_right(right); - return std::unique_ptr>(node); -} \ No newline at end of file +}; \ No newline at end of file From b6d1a9f758e8c73a10efd63bb61d51a2679cb5af Mon Sep 17 00:00:00 2001 From: Noah Attwood Date: Wed, 10 May 2023 01:38:39 -0300 Subject: [PATCH 3/5] adds new test suite for expression_tree class, cleans up some duplicate include directives, and improves various constructors and assignment operators --- include/attwoodn/expression_tree.hpp | 56 ++++++++--- tests/CMakeLists.txt | 5 + tests/expression_tree.cpp | 140 +++++++++++++++++++++++++++ tests/expression_tree_leaf_node.cpp | 1 - tests/expression_tree_op_node.cpp | 2 - 5 files changed, 188 insertions(+), 16 deletions(-) create mode 100644 tests/expression_tree.cpp diff --git a/include/attwoodn/expression_tree.hpp b/include/attwoodn/expression_tree.hpp index 65bcdbe..2e79536 100644 --- a/include/attwoodn/expression_tree.hpp +++ b/include/attwoodn/expression_tree.hpp @@ -84,12 +84,12 @@ namespace attwoodn::expression_tree { * False if the given object did not satisfy the expression in this node and the expressions of all * nodes under this node in the expression tree. */ - virtual bool evaluate(const Obj& obj) = 0; + virtual bool evaluate(const Obj& obj) const = 0; /** * @brief Performs a deep clone of pointers to this base class to avoid object slicing. */ - auto clone() { + auto clone() const { return std::unique_ptr>(clone_impl()); } @@ -106,6 +106,11 @@ namespace attwoodn::expression_tree { public: using this_type = expression_tree_op_node; + expression_tree_op_node() = delete; + expression_tree_op_node(expression_tree_op_node&& other) = delete; + expression_tree_op_node& operator=(const expression_tree_op_node& other) = delete; + expression_tree_op_node& operator=(expression_tree_op_node&& other) = delete; + expression_tree_op_node(boolean_op bool_op) : bool_op_(bool_op) {} @@ -134,7 +139,7 @@ namespace attwoodn::expression_tree { delete l; } - bool evaluate(const Obj& obj) override { + bool evaluate(const Obj& obj) const override { if(!left_ || !right_) { throw std::runtime_error("expression_tree_op_node has a missing child node"); } @@ -237,6 +242,13 @@ namespace attwoodn::expression_tree { public: using this_type = expression_tree_leaf_node; + expression_tree_leaf_node() = delete; + + expression_tree_leaf_node(const expression_tree_leaf_node& other) = default; + expression_tree_leaf_node(expression_tree_leaf_node&& other) = default; + expression_tree_leaf_node& operator=(const expression_tree_leaf_node& other) = default; + expression_tree_leaf_node& operator=(expression_tree_leaf_node&& other) = default; + /** * @brief Constructor that accepts a reference to a member variable of Obj */ @@ -255,7 +267,7 @@ namespace attwoodn::expression_tree { ~expression_tree_leaf_node() override {}; - bool evaluate(const Obj& obj) override { + bool evaluate(const Obj& obj) const override { if (member_func_ && member_var_) { throw std::runtime_error("expression_tree_leaf_node has both a member function reference " + std::string("and member variable reference. Only one is permitted")); @@ -391,28 +403,46 @@ namespace attwoodn::expression_tree { expression_tree(node::expression_tree_node* expr) { if(!expr) { - throw std::runtime_error("Attempted to construct an expression_tree with a null root expression node"); + throw std::runtime_error("Attempted to construct an expression_tree using a null expression"); } - expr_ = expr->clone().release(); delete expr; } - expression_tree(const expression_tree& other) - : expr_(other.expr_->clone().release()) {} + expression_tree(std::unique_ptr> expr) + : expression_tree(expr.release()) {} + + expression_tree(const expression_tree& other) { + if(!other.expr_) { + throw std::runtime_error("Attempted to copy construct an expression_tree " + + std::string("from an expression_tree with a null expression")); + } + expr_ = other.expr_->clone().release(); + } - expression_tree(expression_tree&& other) - : expr_(other.expr_) { + expression_tree(expression_tree&& other) { + if(!other.expr_) { + throw std::runtime_error("Attempted to move construct an expression_tree " + + std::string("from an expression_tree with a null expression")); + } + expr_ = other.expr_; other.expr_ = nullptr; } expression_tree& operator=(const expression_tree& other) { + if(!other.expr_) { + throw std::runtime_error("Attempted copy assignment from an expression_tree with a null expression"); + } delete expr_; expr_ = other.expr_->clone().release(); return *this; } - expression_tree& operator=(const expression_tree&& other) { + expression_tree& operator=(expression_tree&& other) { + if(!other.expr_) { + throw std::runtime_error("Attempted move assignment from an expression_tree with a null expression"); + } + if(this != &other) { delete expr_; expr_ = other.expr_; @@ -431,7 +461,7 @@ namespace attwoodn::expression_tree { * @returns True if the given object satisfied the expression tree conditions; * False if the given object did not satisfy the expression tree conditions. */ - bool evaluate(const Obj& obj) { + bool evaluate(const Obj& obj) const { if(!expr_) { throw std::runtime_error("expression_tree has a null root expression node"); } @@ -446,4 +476,4 @@ namespace attwoodn::expression_tree { private: node::expression_tree_node* expr_ = nullptr; }; -} \ No newline at end of file +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b692197..e5182a1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -15,4 +15,9 @@ if(BUILD_TESTING) target_compile_options( expression_tree_op_node_test PRIVATE -fsanitize=address ) add_test( expression_tree_op_node_test ${EXECUTABLE_OUTPUT_PATH}/expression_tree_op_node_test ) + add_executable( expression_tree_test expression_tree.cpp ) + target_link_libraries( expression_tree_test "-fsanitize=address" ) + target_compile_options( expression_tree_test PRIVATE -fsanitize=address ) + add_test( expression_tree_test ${EXECUTABLE_OUTPUT_PATH}/expression_tree_test ) + endif() \ No newline at end of file diff --git a/tests/expression_tree.cpp b/tests/expression_tree.cpp new file mode 100644 index 0000000..e135294 --- /dev/null +++ b/tests/expression_tree.cpp @@ -0,0 +1,140 @@ +#include +#include "test_utils.hpp" +#include + +using namespace attwoodn::expression_tree; + +void test_simple_expression_tree(); +void test_complex_expression_tree(); +void test_moved_expression_tree(); +void test_copied_expression_tree(); + +int main(int argc, char** argv) { + test_simple_expression_tree(); + test_complex_expression_tree(); + test_moved_expression_tree(); + test_copied_expression_tree(); + + return EXIT_SUCCESS; +} + +void test_simple_expression_tree() { + auto test_procedure = [](const expression_tree& expr) { + test_fixture fixture; + fixture.some_string = "hello!"; + assert(!expr.evaluate(fixture)); + + fixture.some_string = "hello world!"; + assert(!expr.evaluate(fixture)); + + fixture.some_string = "hello,world!"; + assert(!expr.evaluate(fixture)); + + fixture.some_string = "hello, world!"; + assert(expr.evaluate(fixture)); + }; + + // test raw pointer creation of expression_tree + { + expression_tree expr { + make_expr(&test_fixture::some_string, op::equals, std::string("hello, world!")) + }; + + test_procedure(expr); + } + + // test unique_ptr creation of expression_tree + { + expression_tree expr { + std::unique_ptr>( + make_expr(&test_fixture::some_string, op::equals, std::string("hello, world!")) + ) + }; + + test_procedure(expr); + } +} + +void test_complex_expression_tree() { + +} + +void test_moved_expression_tree() { + auto test_procedure = [](const expression_tree& expr) { + test_fixture fixture; + + fixture.some_uint = 345; + assert(expr.evaluate(fixture)); + + fixture.some_uint = 123; + assert(!expr.evaluate(fixture)); + + fixture.some_uint = 543; + assert(!expr.evaluate(fixture)); + + fixture.some_uint = 0x04; + assert(!expr.evaluate(fixture)); + + fixture.some_uint = -1; + assert(!expr.evaluate(fixture)); + + fixture.some_uint = 0; + assert(!expr.evaluate(fixture)); + + fixture.some_uint = 3; + assert(!expr.evaluate(fixture)); + }; + + expression_tree expr { + make_expr(&test_fixture::some_uint, op::equals, (uint16_t) 345) + }; + test_procedure(expr); + + expression_tree move_constructed_expr( std::move(expr) ); + test_procedure(move_constructed_expr); + + expression_tree move_assigned_expr = std::move(move_constructed_expr); + test_procedure(move_assigned_expr); +} + +void test_copied_expression_tree() { + auto test_procedure = [](const expression_tree& expr) { + test_fixture fixture; + + fixture.some_uint = 345; + assert(expr.evaluate(fixture)); + + fixture.some_uint = 123; + assert(!expr.evaluate(fixture)); + + fixture.some_uint = 543; + assert(!expr.evaluate(fixture)); + + fixture.some_uint = 0x04; + assert(!expr.evaluate(fixture)); + + fixture.some_uint = -1; + assert(!expr.evaluate(fixture)); + + fixture.some_uint = 0; + assert(!expr.evaluate(fixture)); + + fixture.some_uint = 3; + assert(!expr.evaluate(fixture)); + }; + + expression_tree expr { + make_expr(&test_fixture::some_uint, op::equals, (uint16_t) 345) + }; + test_procedure(expr); + + expression_tree copy_constructed_expr( expr ); + test_procedure(copy_constructed_expr); + + expression_tree copy_assigned_expr = copy_constructed_expr; + test_procedure(copy_assigned_expr); + + // ensure both of the old copied versions still work after copying + test_procedure(expr); + test_procedure(copy_constructed_expr); +} \ No newline at end of file diff --git a/tests/expression_tree_leaf_node.cpp b/tests/expression_tree_leaf_node.cpp index 147a1b7..8356937 100644 --- a/tests/expression_tree_leaf_node.cpp +++ b/tests/expression_tree_leaf_node.cpp @@ -1,7 +1,6 @@ #include #include "test_utils.hpp" #include -#include #include using namespace attwoodn::expression_tree; diff --git a/tests/expression_tree_op_node.cpp b/tests/expression_tree_op_node.cpp index 1cd2b74..3987fbb 100644 --- a/tests/expression_tree_op_node.cpp +++ b/tests/expression_tree_op_node.cpp @@ -1,8 +1,6 @@ #include #include "test_utils.hpp" #include -#include -#include #include using namespace attwoodn::expression_tree; From 0216ed4f3e32beaae7d52fa936ead50a9b2b5197 Mon Sep 17 00:00:00 2001 From: Noah Attwood Date: Wed, 10 May 2023 10:19:38 -0300 Subject: [PATCH 4/5] adds unit test for complex expression tree --- tests/expression_tree.cpp | 144 +++++++++++++++++++++++++++++++++++++- tests/test_utils.hpp | 14 ++++ 2 files changed, 156 insertions(+), 2 deletions(-) diff --git a/tests/expression_tree.cpp b/tests/expression_tree.cpp index e135294..66148bb 100644 --- a/tests/expression_tree.cpp +++ b/tests/expression_tree.cpp @@ -8,12 +8,14 @@ void test_simple_expression_tree(); void test_complex_expression_tree(); void test_moved_expression_tree(); void test_copied_expression_tree(); +void test_user_defined_operator(); int main(int argc, char** argv) { test_simple_expression_tree(); test_complex_expression_tree(); test_moved_expression_tree(); test_copied_expression_tree(); + test_user_defined_operator(); return EXIT_SUCCESS; } @@ -57,6 +59,140 @@ void test_simple_expression_tree() { void test_complex_expression_tree() { + // expression: (uint > 0 AND (some_string > "a" AND some_string < "z")) OR (some_string > "0" AND some_string < "9") + expression_tree expr { + (make_expr(&test_fixture::is_some_uint_greater_than_zero, op::equals, true) + ->AND((make_expr(&test_fixture::some_string, op::greater_than, std::string("a")) + ->AND(make_expr(&test_fixture::some_string, op::less_than, std::string("z"))) + )) + ) + ->OR((make_expr(&test_fixture::some_string, op::greater_than, std::string("0")) + ->AND(make_expr(&test_fixture::some_string, op::less_than, std::string("9"))) + ) + ) + }; + + test_fixture fixture; + + fixture.some_uint = 0; + + // test left-hand side of the expression tree when some_uint is 0 + { + fixture.some_string = "a"; + assert(!expr.evaluate(fixture)); + + fixture.some_string = "aaa"; + assert(!expr.evaluate(fixture)); + + fixture.some_string = "c"; + assert(!expr.evaluate(fixture)); + + fixture.some_string = "yyyyy"; + assert(!expr.evaluate(fixture)); + + fixture.some_string = "B"; + assert(!expr.evaluate(fixture)); + + fixture.some_string = "ZZZ"; + assert(!expr.evaluate(fixture)); + + fixture.some_string = "/"; + assert(!expr.evaluate(fixture)); + + fixture.some_string = "red"; + assert(!expr.evaluate(fixture)); + + fixture.some_string = " "; + assert(!expr.evaluate(fixture)); + + fixture.some_string = "tree fiddy"; + assert(!expr.evaluate(fixture)); + } + + // test right-hand side of the expression tree when some_uint is 0 + { + fixture.some_string = "000"; + assert(expr.evaluate(fixture)); + + fixture.some_string = "00"; + assert(expr.evaluate(fixture)); + + fixture.some_string = "0"; + assert(!expr.evaluate(fixture)); + + fixture.some_string = "12345"; + assert(expr.evaluate(fixture)); + + fixture.some_string = "7"; + assert(expr.evaluate(fixture)); + + fixture.some_string = "89999"; + assert(expr.evaluate(fixture)); + + fixture.some_string = "9"; + assert(!expr.evaluate(fixture)); + } + + fixture.some_uint = 1; + + // test left-hand side of the expression tree when some_uint is 0 + { + fixture.some_string = "a"; + assert(!expr.evaluate(fixture)); + + fixture.some_string = "aaa"; + assert(expr.evaluate(fixture)); + + fixture.some_string = "c"; + assert(expr.evaluate(fixture)); + + fixture.some_string = "yyyyy"; + assert(expr.evaluate(fixture)); + + fixture.some_string = "B"; + assert(!expr.evaluate(fixture)); + + fixture.some_string = "ZZZ"; + assert(!expr.evaluate(fixture)); + + fixture.some_string = "/"; + assert(!expr.evaluate(fixture)); + + fixture.some_string = "red"; + assert(expr.evaluate(fixture)); + + fixture.some_string = " "; + assert(!expr.evaluate(fixture)); + + fixture.some_string = "tree fiddy"; + assert(expr.evaluate(fixture)); + } + + // test right-hand side of the expression tree when some_uint is 1 + { + fixture.some_string = "000"; + assert(expr.evaluate(fixture)); + + fixture.some_string = "00"; + assert(expr.evaluate(fixture)); + + fixture.some_string = "0"; + assert(!expr.evaluate(fixture)); + + fixture.some_string = "12345"; + assert(expr.evaluate(fixture)); + + fixture.some_string = "7"; + assert(expr.evaluate(fixture)); + + fixture.some_string = "89999"; + assert(expr.evaluate(fixture)); + + fixture.some_string = "9"; + assert(!expr.evaluate(fixture)); + } + + } void test_moved_expression_tree() { @@ -90,7 +226,7 @@ void test_moved_expression_tree() { }; test_procedure(expr); - expression_tree move_constructed_expr( std::move(expr) ); + expression_tree move_constructed_expr(std::move(expr)); test_procedure(move_constructed_expr); expression_tree move_assigned_expr = std::move(move_constructed_expr); @@ -128,7 +264,7 @@ void test_copied_expression_tree() { }; test_procedure(expr); - expression_tree copy_constructed_expr( expr ); + expression_tree copy_constructed_expr(expr); test_procedure(copy_constructed_expr); expression_tree copy_assigned_expr = copy_constructed_expr; @@ -137,4 +273,8 @@ void test_copied_expression_tree() { // ensure both of the old copied versions still work after copying test_procedure(expr); test_procedure(copy_constructed_expr); +} + +void test_user_defined_operator() { + } \ No newline at end of file diff --git a/tests/test_utils.hpp b/tests/test_utils.hpp index fb4a6a0..e813c15 100644 --- a/tests/test_utils.hpp +++ b/tests/test_utils.hpp @@ -15,4 +15,18 @@ struct test_fixture { bool is_some_uint_greater_than_zero() const { return some_uint; } +}; + +struct packet_payload { + const uint16_t error_code; + std::string data; + + uint64_t payload_size() const { + return data.size(); + } +}; + +class data_packet { + const uint32_t sender_id; + packet_payload payload; }; \ No newline at end of file From 2537a19742b7504e1effb1fc3265e61d5085adc3 Mon Sep 17 00:00:00 2001 From: Noah Attwood Date: Wed, 10 May 2023 11:19:54 -0300 Subject: [PATCH 5/5] adds test for user-defined operator --- tests/expression_tree.cpp | 44 +++++++++++++++++++++++++++++++++++++++ tests/test_utils.hpp | 8 ++++--- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/tests/expression_tree.cpp b/tests/expression_tree.cpp index 66148bb..c6c32c3 100644 --- a/tests/expression_tree.cpp +++ b/tests/expression_tree.cpp @@ -1,5 +1,6 @@ #include #include "test_utils.hpp" +#include #include using namespace attwoodn::expression_tree; @@ -276,5 +277,48 @@ void test_copied_expression_tree() { } void test_user_defined_operator() { + auto is_small_packet_payload = [](const packet_payload& incoming, const packet_payload&) -> bool { + if(incoming.error_code == 0 && incoming.checksum_ok && incoming.payload_size() <= 10) { + return true; + } + return false; + }; + + // only accept small, non-errored data packets from Jim. + // evaluate packet contents using the user-defined lambda operator defined above + expression_tree expr { + make_expr(&data_packet::sender_name, op::equals, std::string("Jim")) + ->AND(make_expr(&data_packet::payload, is_small_packet_payload, packet_payload())) + }; + + data_packet incoming_packet; + + // Jim sends a small, non-errored data packet + incoming_packet.sender_name = "Jim"; + incoming_packet.payload.checksum_ok = true; + incoming_packet.payload.data = "hello!"; + incoming_packet.payload.error_code = 0; + assert(expr.evaluate(incoming_packet)); // passes evaluation + + // Pam sends the same packet payload + incoming_packet.sender_name = "Pam"; + assert(!expr.evaluate(incoming_packet)); // fails evaluation. No messages from Pam are accepted (sorry Pam) + + // Jim sends a packet with a bad checksum + incoming_packet.sender_name = "Jim"; + incoming_packet.payload.checksum_ok = false; + assert(!expr.evaluate(incoming_packet)); // fails evaluation. The packet was from Jim, but the checksum was bad + + // Jim sends a packet whose payload is too big + incoming_packet.payload.checksum_ok = true; + incoming_packet.payload.data = "Boy do I have a long story for you - so I was talking to Pam ..."; + assert(!expr.evaluate(incoming_packet)); // fails evaluation. The packet's payload was too big. Give me the TLDR next time, Jim + + // Jim sends a small, rude packet + incoming_packet.payload.data = "Dwight sux"; + assert(expr.evaluate(incoming_packet)); // passes evaluation. The packet's payload was the right size this time + // Jim sends a packet has an error code + incoming_packet.payload.error_code = 404; + assert(!expr.evaluate(incoming_packet)); // fails evaluation. The packet's payload had an error code } \ No newline at end of file diff --git a/tests/test_utils.hpp b/tests/test_utils.hpp index e813c15..69ce030 100644 --- a/tests/test_utils.hpp +++ b/tests/test_utils.hpp @@ -18,8 +18,9 @@ struct test_fixture { }; struct packet_payload { - const uint16_t error_code; + uint16_t error_code; std::string data; + bool checksum_ok; uint64_t payload_size() const { return data.size(); @@ -27,6 +28,7 @@ struct packet_payload { }; class data_packet { - const uint32_t sender_id; - packet_payload payload; + public: + std::string sender_name; + packet_payload payload; }; \ No newline at end of file