From afffb11f42ea15c4f58bd1010178b73d26fb4f79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20=C3=89corchard?= Date: Tue, 5 Dec 2023 14:10:20 +0100 Subject: [PATCH 1/4] Add some failing tests for Any MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gaël Écorchard --- tests/CMakeLists.txt | 1 + tests/gtest_any.cpp | 247 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 248 insertions(+) create mode 100644 tests/gtest_any.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6a2f67b1b..9fc305e1d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -5,6 +5,7 @@ set(BT_TESTS src/action_test_node.cpp src/condition_test_node.cpp + gtest_any.cpp gtest_blackboard.cpp gtest_coroutines.cpp gtest_decorator.cpp diff --git a/tests/gtest_any.cpp b/tests/gtest_any.cpp new file mode 100644 index 000000000..ba4f3b0da --- /dev/null +++ b/tests/gtest_any.cpp @@ -0,0 +1,247 @@ +/* Copyright (C) 2023 Gaël Écorchard, KM Robotics s.r.o. - All Rights Reserved +* +* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), +* to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include // std::{from_chars,from_chars_result}, +#include +#include // std::errc. + +#include + +#include + +using namespace BT; + +TEST(Any, Empty) +{ + { + Any a; + EXPECT_TRUE(a.empty()); + } + + { + Any a(42); + EXPECT_FALSE(a.empty()); + } +} + +TEST(Any, IsType) +{ + // Boolean. + { + Any a(true); + EXPECT_TRUE(a.isType()); + EXPECT_FALSE(a.isType()); + EXPECT_FALSE(a.isType()); + EXPECT_FALSE(a.isType()); + EXPECT_FALSE(a.isType()); + } + + // Signed int. + { + Any a(42); + EXPECT_FALSE(a.isType()); + EXPECT_TRUE(a.isType()); + EXPECT_FALSE(a.isType()); + EXPECT_FALSE(a.isType()); + EXPECT_FALSE(a.isType()); + } + + // unsigned int. + { + Any a(42u); + EXPECT_FALSE(a.isType()); + EXPECT_FALSE(a.isType()); + EXPECT_TRUE(a.isType()); + EXPECT_FALSE(a.isType()); + EXPECT_FALSE(a.isType()); + } + + // Double. + { + Any a(42.0); + EXPECT_FALSE(a.isType()); + EXPECT_FALSE(a.isType()); + EXPECT_FALSE(a.isType()); + EXPECT_TRUE(a.isType()); + EXPECT_FALSE(a.isType()); + } + + // String. + { + Any a(std::string{"forty-two"}); + EXPECT_FALSE(a.isType()); + EXPECT_FALSE(a.isType()); + EXPECT_FALSE(a.isType()); + EXPECT_FALSE(a.isType()); + EXPECT_TRUE(a.isType()); + } +} + +TEST(Any, Cast) +{ + // Boolean. + { + Any a(true); + EXPECT_TRUE(a.cast()); + EXPECT_EQ(typeid(a.cast()), typeid(bool)); + EXPECT_EQ(a.cast(), 1); + EXPECT_EQ(a.cast(), 1.0); + EXPECT_EQ(a.cast(), "1"); + + Any b(false); + EXPECT_FALSE(b.cast()); + EXPECT_EQ(typeid(b.cast()), typeid(bool)); + EXPECT_EQ(b.cast(), 0); + EXPECT_EQ(b.cast(), 0.0); + EXPECT_EQ(b.cast(), "0"); + } + + // Signed int. + { + Any a(42); + EXPECT_ANY_THROW(a.cast()); + EXPECT_EQ(a.cast(), 42); + EXPECT_EQ(a.cast(), 42.0); + EXPECT_EQ(a.cast(), "42"); + + Any b(-43); + EXPECT_ANY_THROW(b.cast()); + EXPECT_EQ(b.cast(), -43); + EXPECT_EQ(b.cast(), -43.0); + EXPECT_EQ(b.cast(), "-43"); + + Any c(0); + EXPECT_FALSE(c.cast()); + EXPECT_EQ(typeid(c.cast()), typeid(bool)); + EXPECT_EQ(c.cast(), 0); + EXPECT_EQ(c.cast(), 0.0); + EXPECT_EQ(c.cast(), "0"); + + Any d(1); + EXPECT_TRUE(d.cast()); + EXPECT_EQ(typeid(d.cast()), typeid(bool)); + EXPECT_EQ(d.cast(), 1); + EXPECT_EQ(d.cast(), 1.0); + EXPECT_EQ(d.cast(), "1"); + } + + // unsigned int. + { + Any a(43u); + EXPECT_ANY_THROW(a.cast()); + EXPECT_EQ(a.cast(), 43u); + EXPECT_EQ(a.cast(), 43); + EXPECT_EQ(a.cast(), 43.0); + EXPECT_EQ(a.cast(), "43"); + + Any b(0u); + EXPECT_FALSE(b.cast()); + EXPECT_EQ(typeid(b.cast()), typeid(bool)); + EXPECT_EQ(b.cast(), 0u); + EXPECT_EQ(b.cast(), 0); + EXPECT_EQ(b.cast(), 0.0); + EXPECT_EQ(b.cast(), "0"); + + Any c(1u); + EXPECT_TRUE(c.cast()); + EXPECT_EQ(typeid(c.cast()), typeid(bool)); + EXPECT_EQ(c.cast(), 1u); + EXPECT_EQ(c.cast(), 1); + EXPECT_EQ(c.cast(), 1.0); + EXPECT_EQ(c.cast(), "1"); + } + + // Double. + { + Any a(44.0); + EXPECT_TRUE(a.cast()); + EXPECT_EQ(typeid(a.cast()), typeid(bool)); + EXPECT_EQ(a.cast(), 44); + EXPECT_EQ(a.cast(), 44.0); + const std::string a_str = a.cast(); + double a_val; + const auto res = std::from_chars(a_str.data(), a_str.data() + a_str.size(), a_val, std::chars_format::general); + EXPECT_TRUE(res.ec == std::errc{}); + EXPECT_DOUBLE_EQ(a_val, 44.0); + + Any b(44.1); + EXPECT_TRUE(b.cast()); + EXPECT_EQ(typeid(b.cast()), typeid(bool)); + // EXPECT_EQ(b.cast(), 44);? + EXPECT_ANY_THROW(b.cast()); + EXPECT_EQ(b.cast(), 44.1); + const std::string b_str = b.cast(); + double b_val; + const auto res2 = std::from_chars(b_str.data(), b_str.data() + b_str.size(), b_val, std::chars_format::general); + EXPECT_TRUE(res2.ec == std::errc{}); + EXPECT_DOUBLE_EQ(b_val, 44.1); + + Any c(44.9); + EXPECT_TRUE(c.cast()); + // EXPECT_EQ(c.cast(), 44);? + EXPECT_ANY_THROW(c.cast()); + EXPECT_EQ(c.cast(), 44.9); + + Any d(-45.0); + EXPECT_TRUE(c.cast()); + // EXPECT_EQ(c.cast(), -45);? + EXPECT_ANY_THROW(c.cast()); + + Any e(0.0); + EXPECT_FALSE(e.cast()); + EXPECT_EQ(e.cast(), 0); + } + + // String. + { + Any a(std::string{"fifty"}); + EXPECT_ANY_THROW(a.cast()); + EXPECT_ANY_THROW(a.cast()); + EXPECT_ANY_THROW(a.cast()); + EXPECT_EQ(a.cast(), "fifty"); + + Any b(std::string{"true"}); + // EXPECT_TRUE(b.cast());? + EXPECT_ANY_THROW(b.cast()); + + Any c(std::string{"false"}); + // EXPECT_FALSE(c.cast());? + EXPECT_ANY_THROW(c.cast()); + + Any d(std::string{"0"}); + // EXPECT_FALSE(d.cast());? + EXPECT_EQ(d.cast(), 0); + EXPECT_EQ(d.cast(), 0.0); + + Any e(std::string{"1"}); + // EXPECT_TRUE(e.cast());? + EXPECT_EQ(e.cast(), 1); + EXPECT_EQ(e.cast(), 1.0); + + Any f(std::string{"51"}); + EXPECT_ANY_THROW(f.cast()); + EXPECT_EQ(f.cast(), 51); + EXPECT_EQ(f.cast(), 51.0); + + Any g(std::string{"51.1"}); + EXPECT_ANY_THROW(g.cast()); + EXPECT_EQ(g.cast(), 51); + EXPECT_DOUBLE_EQ(g.cast(), 51.1); + } + + { + std::vector v{1, 2, 3}; + Any a(v); + EXPECT_EQ(a.cast>(), v); + } +} + From 013abbd2ee1e8041d15afc0549fd00cb14945dcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20=C3=89corchard?= Date: Tue, 5 Dec 2023 14:15:23 +0100 Subject: [PATCH 2/4] Fix Any::isType() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now compares the original type, not the casted one. Signed-off-by: Gaël Écorchard --- .../behaviortree_cpp/scripting/operators.hpp | 22 +++++++------- include/behaviortree_cpp/utils/safe_any.hpp | 29 ++++++++++++------- src/blackboard.cpp | 2 +- tests/script_parser_test.cpp | 1 + 4 files changed, 31 insertions(+), 23 deletions(-) diff --git a/include/behaviortree_cpp/scripting/operators.hpp b/include/behaviortree_cpp/scripting/operators.hpp index 16d9b3837..087b64604 100644 --- a/include/behaviortree_cpp/scripting/operators.hpp +++ b/include/behaviortree_cpp/scripting/operators.hpp @@ -101,17 +101,17 @@ struct ExprUnaryArithmetic : ExprBase Any evaluate(Environment& env) const override { auto rhs_v = rhs->evaluate(env); - if (rhs_v.isNumber()) + if (rhs_v.isCastedAsNumber()) { - double rv = rhs_v.cast(); + const double rv = rhs_v.cast(); switch (op) { case negate: return Any(-rv); case complement: - return Any(double(~static_cast(rv))); + return Any(static_cast(~static_cast(rv))); case logical_not: - return Any(double(!static_cast(rv))); + return Any(static_cast(!static_cast(rv))); } } else if (rhs_v.isString()) @@ -185,7 +185,7 @@ struct ExprBinaryArithmetic : ExprBase throw RuntimeError(ErrorNotInit("right", opStr())); } - if (rhs_v.isNumber() && lhs_v.isNumber()) + if (rhs_v.isCastedAsNumber() && lhs_v.isCastedAsNumber()) { auto lv = lhs_v.cast(); auto rv = rhs_v.cast(); @@ -252,7 +252,7 @@ struct ExprBinaryArithmetic : ExprBase } } } - else if (rhs_v.isType() && lhs_v.isType() && op == plus) + else if (rhs_v.isString() && lhs_v.isString() && op == plus) { return Any(lhs_v.cast() + rhs_v.cast()); } @@ -362,7 +362,7 @@ struct ExprComparison : ExprBase } const Any False(0.0); - if (lhs_v.isNumber() && rhs_v.isNumber()) + if (lhs_v.isCastedAsNumber() && rhs_v.isCastedAsNumber()) { auto lv = lhs_v.cast(); auto rv = rhs_v.cast(); @@ -380,8 +380,8 @@ struct ExprComparison : ExprBase return False; } } - else if ((lhs_v.isString() && rhs_v.isNumber()) || - (lhs_v.isNumber() && rhs_v.isString())) + else if ((lhs_v.isString() && rhs_v.isCastedAsNumber()) || + (lhs_v.isCastedAsNumber() && rhs_v.isString())) { auto lv = lhs_v.cast(); auto rv = lhs_v.cast(); @@ -562,9 +562,9 @@ struct ExprAssignment : ExprBase // temporary use Any temp_variable = *dst_ptr; - if (value.isNumber()) + if (value.isCastedAsNumber()) { - if (!temp_variable.isNumber()) + if (!temp_variable.isCastedAsNumber()) { throw RuntimeError("This Assignment operator can't be used " "with a non-numeric type"); diff --git a/include/behaviortree_cpp/utils/safe_any.hpp b/include/behaviortree_cpp/utils/safe_any.hpp index 0a25d719a..d579bfa17 100644 --- a/include/behaviortree_cpp/utils/safe_any.hpp +++ b/include/behaviortree_cpp/utils/safe_any.hpp @@ -141,7 +141,14 @@ class Any template bool isType() const { - return _any.type() == typeid(T); + return type() == typeid(T); + } + + bool isCastedAsNumber() const + { + return _any.type() == typeid(int64_t) || + _any.type() == typeid(uint64_t) || + _any.type() == typeid(double); } // copy the value (casting into dst). We preserve the destination type. @@ -204,7 +211,7 @@ class Any std::string errorMsg() const { return StrCat("[Any::convert]: no known safe conversion between [", - demangle( _any.type() ), "] and [", demangle( typeid(T) ),"]"); + demangle( type() ), "] and [", demangle( typeid(T) ),"]"); } }; @@ -269,15 +276,15 @@ inline Any &Any::operator =(const Any &other) inline bool Any::isNumber() const { - return _any.type() == typeid(int64_t) || - _any.type() == typeid(uint64_t) || - _any.type() == typeid(double); + return type() == typeid(int64_t) || + type() == typeid(uint64_t) || + type() == typeid(double); } inline bool Any::isIntegral() const { - return _any.type() == typeid(int64_t) || - _any.type() == typeid(uint64_t); + return type() == typeid(int64_t) || + type() == typeid(uint64_t); } inline void Any::copyInto(Any &dst) @@ -290,11 +297,11 @@ inline void Any::copyInto(Any &dst) const auto& dst_type = dst.castedType(); - if ((type() == dst_type) || (isString() && dst.isString()) ) + if ((castedType() == dst_type) || (isString() && dst.isString()) ) { dst._any = _any; } - else if(isNumber() && dst.isNumber()) + else if(isCastedAsNumber() && dst.isCastedAsNumber()) { if (dst_type == typeid(int64_t)) { @@ -440,7 +447,7 @@ nonstd::expected Any::tryCast() const throw std::runtime_error("Any::cast failed because it is empty"); } - if (_any.type() == typeid(T)) + if (castedType() == typeid(T)) { return linb::any_cast(_any); } @@ -449,7 +456,7 @@ nonstd::expected Any::tryCast() const // We will try first a int convertion if constexpr(std::is_enum_v) { - if(isNumber()) + if(isCastedAsNumber()) { return static_cast( convert().value() ); } diff --git a/src/blackboard.cpp b/src/blackboard.cpp index 8f77cbd3b..ce14d4a7c 100644 --- a/src/blackboard.cpp +++ b/src/blackboard.cpp @@ -182,7 +182,7 @@ Blackboard::createEntryImpl(const std::string& key, const TypeInfo& info) auto msg = StrCat("Blackboard entry [", key, "]: once declared, the type of a port" " shall not change. Previously declared type [", BT::demangle(prev_info.type()), - "], current type [", + "], attempt type [", BT::demangle(info.type()), "]"); throw LogicError(msg); diff --git a/tests/script_parser_test.cpp b/tests/script_parser_test.cpp index e95a5c0ba..a9559c821 100644 --- a/tests/script_parser_test.cpp +++ b/tests/script_parser_test.cpp @@ -216,6 +216,7 @@ TEST(ParserTest, Equations) EXPECT_EQ(variables->get("v1"), 1); EXPECT_EQ(variables->get("v2"), 0); + EXPECT_EQ(GetResult(" v2 = true ").cast(), 1); EXPECT_EQ(GetResult(" v2 = !false ").cast(), 1); EXPECT_EQ(GetResult(" v2 = !v2 ").cast(), 0); From 5817812a12ed0570691806e2b8833bf596fde6ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20=C3=89corchard?= Date: Tue, 5 Dec 2023 14:54:08 +0100 Subject: [PATCH 3/4] Fix compilation for older than C++17 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gaël Écorchard --- tests/gtest_any.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/gtest_any.cpp b/tests/gtest_any.cpp index ba4f3b0da..0ea6b581c 100644 --- a/tests/gtest_any.cpp +++ b/tests/gtest_any.cpp @@ -167,11 +167,13 @@ TEST(Any, Cast) EXPECT_EQ(typeid(a.cast()), typeid(bool)); EXPECT_EQ(a.cast(), 44); EXPECT_EQ(a.cast(), 44.0); +#if __cpp_lib_to_chars >= 201611L const std::string a_str = a.cast(); double a_val; const auto res = std::from_chars(a_str.data(), a_str.data() + a_str.size(), a_val, std::chars_format::general); EXPECT_TRUE(res.ec == std::errc{}); EXPECT_DOUBLE_EQ(a_val, 44.0); +#endif Any b(44.1); EXPECT_TRUE(b.cast()); @@ -179,11 +181,13 @@ TEST(Any, Cast) // EXPECT_EQ(b.cast(), 44);? EXPECT_ANY_THROW(b.cast()); EXPECT_EQ(b.cast(), 44.1); +#if __cpp_lib_to_chars >= 201611L const std::string b_str = b.cast(); double b_val; const auto res2 = std::from_chars(b_str.data(), b_str.data() + b_str.size(), b_val, std::chars_format::general); EXPECT_TRUE(res2.ec == std::errc{}); EXPECT_DOUBLE_EQ(b_val, 44.1); +#endif Any c(44.9); EXPECT_TRUE(c.cast()); From 38a14a86d8c7745f204c005e87bb6071701cd310 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20=C3=89corchard?= Date: Thu, 7 Dec 2023 09:12:23 +0100 Subject: [PATCH 4/4] Extend `isIntegral()` to all integral types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also extend `isNumber()` to all number types. Signed-off-by: Gaël Écorchard --- include/behaviortree_cpp/utils/safe_any.hpp | 63 ++++++++++++++------- 1 file changed, 42 insertions(+), 21 deletions(-) diff --git a/include/behaviortree_cpp/utils/safe_any.hpp b/include/behaviortree_cpp/utils/safe_any.hpp index d579bfa17..95e1b06a4 100644 --- a/include/behaviortree_cpp/utils/safe_any.hpp +++ b/include/behaviortree_cpp/utils/safe_any.hpp @@ -74,23 +74,43 @@ class Any ~Any() = default; - Any(const Any& other) : _any(other._any), _original_type( other._original_type ) + Any(const Any& other) : + _any(other._any), + _original_type( other._original_type ), + _is_number( other._is_number ), + _is_integral( other._is_integral ) { } - Any(Any&& other) : _any( std::move(other._any) ), _original_type( other._original_type ) + Any(Any&& other) : + _any( std::move(other._any) ), + _original_type( other._original_type ), + _is_number( other._is_number ), + _is_integral( other._is_integral ) { } - explicit Any(const double& value) : _any(value), _original_type( typeid(double) ) + explicit Any(const double& value) : + _any(value), + _original_type( typeid(double) ), + _is_number(true), + _is_integral(false) { } - explicit Any(const uint64_t& value) : _any(value), _original_type( typeid(uint64_t) ) + explicit Any(const uint64_t& value) : + _any(value), + _original_type( typeid(uint64_t) ), + _is_number(true), + _is_integral(true) { } - explicit Any(const float& value) : _any(double(value)), _original_type( typeid(float) ) + explicit Any(const float& value) : + _any(double(value)), + _original_type( typeid(float) ), + _is_number(true), + _is_integral(false) { } @@ -112,7 +132,11 @@ class Any // all the other integrals are casted to int64_t template - explicit Any(const T& value, EnableIntegral = 0) : _any(int64_t(value)), _original_type( typeid(T) ) + explicit Any(const T& value, EnableIntegral = 0) : + _any(int64_t(value)), + _original_type( typeid(T) ), + _is_number(true), + _is_integral(true) { } @@ -129,9 +153,15 @@ class Any Any& operator = (const Any& other); - bool isNumber() const; + inline bool isNumber() const + { + return _is_number; + } - bool isIntegral() const; + inline bool isIntegral() const + { + return _is_integral; + } bool isString() const { @@ -189,6 +219,8 @@ class Any private: linb::any _any; std::type_index _original_type; + bool _is_number = false; + bool _is_integral = false; //---------------------------- @@ -271,22 +303,11 @@ inline Any &Any::operator =(const Any &other) { this->_any = other._any; this->_original_type = other._original_type; + this->_is_number = other._is_number; + this->_is_integral = other._is_integral; return *this; } -inline bool Any::isNumber() const -{ - return type() == typeid(int64_t) || - type() == typeid(uint64_t) || - type() == typeid(double); -} - -inline bool Any::isIntegral() const -{ - return type() == typeid(int64_t) || - type() == typeid(uint64_t); -} - inline void Any::copyInto(Any &dst) { if(dst.empty())