summaryrefslogtreecommitdiffstats
path: root/vespalib
diff options
context:
space:
mode:
authorHÃ¥vard Pettersen <3535158+havardpe@users.noreply.github.com>2021-03-30 10:48:52 +0200
committerGitHub <noreply@github.com>2021-03-30 10:48:52 +0200
commitfe03be062c4ce87aab521237f975b375e54b2906 (patch)
tree30cd7af71d5df366b929237be028f7150f26a24e /vespalib
parent6306fa0fd0f615694edc2a91265f193070edffd9 (diff)
parentb192769ea4c2114c5bb93143e51347aed3929a57 (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.cpp71
-rw-r--r--vespalib/src/vespa/vespalib/util/approx.h2
-rw-r--r--vespalib/src/vespa/vespalib/util/require.h86
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), \