diff options
author | HÃ¥vard Pettersen <3535158+havardpe@users.noreply.github.com> | 2021-03-30 10:48:52 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-03-30 10:48:52 +0200 |
commit | fe03be062c4ce87aab521237f975b375e54b2906 (patch) | |
tree | 30cd7af71d5df366b929237be028f7150f26a24e /vespalib | |
parent | 6306fa0fd0f615694edc2a91265f193070edffd9 (diff) | |
parent | b192769ea4c2114c5bb93143e51347aed3929a57 (diff) |
Merge pull request #17227 from vespa-engine/havardpe/more-flexible-require-eq
more flexible REQUIRE_EQ
Diffstat (limited to 'vespalib')
-rw-r--r-- | vespalib/src/tests/require/require_test.cpp | 71 | ||||
-rw-r--r-- | vespalib/src/vespa/vespalib/util/approx.h | 2 | ||||
-rw-r--r-- | vespalib/src/vespa/vespalib/util/require.h | 86 |
3 files changed, 153 insertions, 6 deletions
diff --git a/vespalib/src/tests/require/require_test.cpp b/vespalib/src/tests/require/require_test.cpp index a97e7a362cc..65f4d049843 100644 --- a/vespalib/src/tests/require/require_test.cpp +++ b/vespalib/src/tests/require/require_test.cpp @@ -3,6 +3,8 @@ #include <vespa/vespalib/util/require.h> #include <vespa/vespalib/gtest/gtest.h> +using E = vespalib::RequireFailedException; + //----------------------------------------------------------------------------- void pass_require() { @@ -38,7 +40,6 @@ void fail_require_eq() { } TEST(RequireTest, require_can_fail) { - using E = vespalib::RequireFailedException; EXPECT_THROW( { try { fail_require(); } @@ -52,7 +53,6 @@ TEST(RequireTest, require_can_fail) { } TEST(RequireTest, require_eq_can_fail) { - using E = vespalib::RequireFailedException; EXPECT_THROW( { try { fail_require_eq(); } @@ -89,4 +89,71 @@ TEST(RequireTest, require_eq_can_be_constexpr) { //----------------------------------------------------------------------------- +TEST(RequireTest, require_eq_implicit_approx_for_double) { + double foo = 1.0; + double bar = 1.0 + 1e-9; + REQUIRE(foo != bar); + REQUIRE_EQ(foo, bar); +} + +//----------------------------------------------------------------------------- + +struct MyA { + int a; + int b; + template <typename T> + bool operator==(const T &rhs) const { + return (a == rhs.a) && (b == rhs.b); + } +}; +std::ostream &operator<<(std::ostream &out, const MyA &a) { + out << "MyA { a: " << a.a << ", b: " << a.b << " }"; + return out; +} + +struct MyB { + int a; + int b; +}; + +struct MyC { + char a; + ssize_t b; +}; + +TEST(RequireTest, explicit_compare_and_print) { + MyA x{5, 7}; + MyA y{5, 6}; + REQUIRE_EQ(x, x); + EXPECT_THROW(REQUIRE_EQ(x, y), E); +} + +TEST(RequireTest, implicit_compare_and_print) { + MyB x{5, 7}; + MyB y{5, 6}; + REQUIRE_EQ(x, x); + EXPECT_THROW(REQUIRE_EQ(x, y), E); +} + +TEST(RequireTest, comparable_but_unprintable) { + MyA x{5, 7}; + MyC y{5, 6}; + REQUIRE_EQ(x, x); + EXPECT_THROW(REQUIRE_EQ(x, y), E); +} + +// manual test for uncompilable code (uncomparable values) +TEST(RequireTest, uncomment_to_manually_check_uncompilable_code) { + MyA a{5, 7}; + MyB b{5, 7}; + MyC c{5, 7}; + (void) a; + (void) b; + (void) c; + // REQUIRE_EQ(b, a); + // REQUIRE_EQ(c, c); +} + +//----------------------------------------------------------------------------- + GTEST_MAIN_RUN_ALL_TESTS() diff --git a/vespalib/src/vespa/vespalib/util/approx.h b/vespalib/src/vespa/vespalib/util/approx.h index 6afe8041476..8c6baa648ea 100644 --- a/vespalib/src/vespa/vespalib/util/approx.h +++ b/vespalib/src/vespa/vespalib/util/approx.h @@ -12,7 +12,7 @@ namespace vespalib { * step 1 unit in the last place towards the other number. This means the * two numbers must be equal to 23 bits precision. **/ -inline bool approx_equal(double a, double b) +constexpr bool approx_equal(double a, double b) { if (a == b) return true; if (a > 1.0 || a < -1.0) { diff --git a/vespalib/src/vespa/vespalib/util/require.h b/vespalib/src/vespa/vespalib/util/require.h index c666a06c8d6..a4283520314 100644 --- a/vespalib/src/vespa/vespalib/util/require.h +++ b/vespalib/src/vespa/vespalib/util/require.h @@ -3,11 +3,87 @@ #pragma once #include "macro.h" +#include "approx.h" +#include "classname.h" #include <iostream> +#include <type_traits> #include <vespa/vespalib/util/exception.h> +#include <vespa/vespalib/objects/hexdump.h> namespace vespalib { +namespace require_impl { + +//----------------------------------------------------------------------------- + +template<typename A, typename B> +using comparability = decltype(std::declval<const A &>() == std::declval<const B &>()); + +template<typename A, typename B, class = void> +struct are_comparable : std::false_type {}; + +template<typename A, typename B> +struct are_comparable<A,B,std::void_t<comparability<A,B>>> : std::true_type{}; + +//----------------------------------------------------------------------------- + +template<typename S, typename V> +using streamability = decltype(std::declval<S &>() << std::declval<const V &>()); + +template<typename S, typename V, class = void> +struct is_streamable : std::false_type {}; + +template<typename S, typename V> +struct is_streamable<S,V,std::void_t<streamability<S,V>>> : std::true_type{}; + +//----------------------------------------------------------------------------- + +// memcmp is not constexpr, so we need an alternative +constexpr bool mem_eq(const char *a, const char *b, size_t len) { + for (size_t i = 0; i < len; ++i) { + if (a[i] != b[i]) { + return false; + } + } + return true; +} + +template <class A, class B> +constexpr bool eq(const A &a, const B &b) { + if constexpr (are_comparable<A,B>()) { + return (a == b); + } else if constexpr (std::is_same_v<A,B> && + std::has_unique_object_representations_v<A>) + { + static_assert(sizeof(A) == sizeof(B)); + return mem_eq(reinterpret_cast<const char *>(std::addressof(a)), + reinterpret_cast<const char *>(std::addressof(b)), + sizeof(A)); + } else { + static_assert(are_comparable<A,B>(), "values are not comparable"); + return false; + } +} + +constexpr bool eq(double a, double b) { return approx_equal(a, b); } + +//----------------------------------------------------------------------------- + +template <typename S, typename V> +void print(S &os, const V &value) { + if constexpr (is_streamable<S,V>()) { + os << value; + } else if constexpr (std::has_unique_object_representations_v<V>) { + os << "(" << getClassName<V>() << ") " << HexDump(std::addressof(value), sizeof(V)); + } else { + os << "not printable (type: " << getClassName<V>() << ")"; + } +} + +//----------------------------------------------------------------------------- + +} // namespace require_impl + VESPA_DEFINE_EXCEPTION(RequireFailedException, Exception); constexpr void handle_require_success() {} @@ -22,8 +98,12 @@ void handle_require_eq_failure [[noreturn]] (const A& a, const B& b, const char { std::cerr << file << ":" << line << ": error: "; std::cerr << "expected (" << a_desc << " == " << b_desc << ")\n"; - std::cerr << " lhs (" << a_desc << ") is: " << a << "\n"; - std::cerr << " rhs (" << b_desc << ") is: " << b << "\n"; + std::cerr << " lhs (" << a_desc << ") is: "; + require_impl::print(std::cerr, a); + std::cerr << "\n"; + std::cerr << " rhs (" << b_desc << ") is: "; + require_impl::print(std::cerr, b); + std::cerr << "\n"; throw_require_failed(description, file, line); } @@ -45,7 +125,7 @@ void handle_require_eq_failure [[noreturn]] (const A& a, const B& b, const char * for the value types. **/ #define REQUIRE_EQ(a, b) \ - (a == b) ? vespalib::handle_require_success() : \ + vespalib::require_impl::eq(a, b) ? vespalib::handle_require_success() : \ vespalib::handle_require_eq_failure(a, b, \ VESPA_STRINGIZE(a), VESPA_STRINGIZE(b), \ VESPA_STRINGIZE(a) " == " VESPA_STRINGIZE(b), \ |