diff options
author | Abseil Team <absl-team@google.com> | 2024-05-16 01:34:16 -0700 |
---|---|---|
committer | Copybara-Service <copybara-worker@google.com> | 2024-05-16 01:35:22 -0700 |
commit | 93ac3a4f9ee7792af399cebd873ee99ce15aed08 (patch) | |
tree | fc58844a9aab59d56d937ecd2f63c3a8fd31c973 | |
parent | cbfe51b2c01da330ff292b145de91346a5950163 (diff) | |
download | abseil-93ac3a4f9ee7792af399cebd873ee99ce15aed08.tar.gz abseil-93ac3a4f9ee7792af399cebd873ee99ce15aed08.tar.bz2 abseil-93ac3a4f9ee7792af399cebd873ee99ce15aed08.zip |
Support `AbslStringify` with `DCHECK_EQ`.
`AbslStringify` is the [recommended](abseil.io/tips/215) way to make a type printable. However, the simple expression `DCHECK_EQ(x, y)` fails when either argument implements it, and not `operator<<`.
PiperOrigin-RevId: 634261367
Change-Id: Ic42666c286cf172c9482abbd28194da828706c71
-rw-r--r-- | absl/log/BUILD.bazel | 2 | ||||
-rw-r--r-- | absl/log/CMakeLists.txt | 4 | ||||
-rw-r--r-- | absl/log/check_test_impl.inc | 158 | ||||
-rw-r--r-- | absl/log/internal/check_op.cc | 20 | ||||
-rw-r--r-- | absl/log/internal/check_op.h | 70 |
5 files changed, 240 insertions, 14 deletions
diff --git a/absl/log/BUILD.bazel b/absl/log/BUILD.bazel index dd192a10..b13cf4d4 100644 --- a/absl/log/BUILD.bazel +++ b/absl/log/BUILD.bazel @@ -357,6 +357,8 @@ cc_library( "//absl/base:core_headers", "//absl/log/internal:test_helpers", "//absl/status", + "//absl/strings", + "//absl/strings:string_view", "@com_google_googletest//:gtest", ], ) diff --git a/absl/log/CMakeLists.txt b/absl/log/CMakeLists.txt index 59aa19f3..43844651 100644 --- a/absl/log/CMakeLists.txt +++ b/absl/log/CMakeLists.txt @@ -789,6 +789,8 @@ absl_cc_test( absl::core_headers absl::log_internal_test_helpers absl::status + absl::strings + absl::string_view GTest::gmock_main ) @@ -832,6 +834,8 @@ absl_cc_test( absl::core_headers absl::log_internal_test_helpers absl::status + absl::strings + absl::string_view GTest::gmock_main ) diff --git a/absl/log/check_test_impl.inc b/absl/log/check_test_impl.inc index d5c0aee4..64318108 100644 --- a/absl/log/check_test_impl.inc +++ b/absl/log/check_test_impl.inc @@ -31,6 +31,8 @@ #include "absl/base/config.h" #include "absl/log/internal/test_helpers.h" #include "absl/status/status.h" +#include "absl/strings/string_view.h" +#include "absl/strings/substitute.h" // NOLINTBEGIN(misc-definitions-in-headers) @@ -521,6 +523,162 @@ TEST(CHECKDeathTest, TestUserDefinedStreaming) { "Check failed: v1 == v2 (ComparableType{1} vs. ComparableType{2})")); } +// A type that can be printed using AbslStringify. +struct StringifiableType { + int x = 0; + explicit StringifiableType(int x) : x(x) {} + friend bool operator==(const StringifiableType& lhs, + const StringifiableType& rhs) { + return lhs.x == rhs.x; + } + friend bool operator!=(const StringifiableType& lhs, + const StringifiableType& rhs) { + return lhs.x != rhs.x; + } + friend bool operator<(const StringifiableType& lhs, + const StringifiableType& rhs) { + return lhs.x < rhs.x; + } + friend bool operator>(const StringifiableType& lhs, + const StringifiableType& rhs) { + return lhs.x > rhs.x; + } + friend bool operator<=(const StringifiableType& lhs, + const StringifiableType& rhs) { + return lhs.x <= rhs.x; + } + friend bool operator>=(const StringifiableType& lhs, + const StringifiableType& rhs) { + return lhs.x >= rhs.x; + } + template <typename Sink> + friend void AbslStringify(Sink& sink, const StringifiableType& obj) { + absl::Format(&sink, "StringifiableType{%d}", obj.x); + } + + // Make sure no unintended copy happens. + StringifiableType(const StringifiableType&) = delete; +}; + +TEST(CHECKTest, TestUserDefinedAbslStringify) { + const StringifiableType v1(1); + const StringifiableType v2(2); + + ABSL_TEST_CHECK_EQ(v1, v1); + ABSL_TEST_CHECK_NE(v1, v2); + ABSL_TEST_CHECK_LT(v1, v2); + ABSL_TEST_CHECK_LE(v1, v2); + ABSL_TEST_CHECK_GT(v2, v1); + ABSL_TEST_CHECK_GE(v2, v1); +} + +TEST(CHECKDeathTest, TestUserDefinedAbslStringify) { + const StringifiableType v1(1); + const StringifiableType v2(2); + + // Returns a matcher for the expected check failure message when comparing two + // values. + auto expected_output = [](int lhs, absl::string_view condition, int rhs) { + return HasSubstr( + absl::Substitute("Check failed: v$0 $1 v$2 (StringifiableType{$0} vs. " + "StringifiableType{$2})", + lhs, condition, rhs)); + }; + // Test comparisons where the check fails. + EXPECT_DEATH(ABSL_TEST_CHECK_EQ(v1, v2), expected_output(1, "==", 2)); + EXPECT_DEATH(ABSL_TEST_CHECK_NE(v1, v1), expected_output(1, "!=", 1)); + EXPECT_DEATH(ABSL_TEST_CHECK_LT(v2, v1), expected_output(2, "<", 1)); + EXPECT_DEATH(ABSL_TEST_CHECK_LE(v2, v1), expected_output(2, "<=", 1)); + EXPECT_DEATH(ABSL_TEST_CHECK_GT(v1, v2), expected_output(1, ">", 2)); + EXPECT_DEATH(ABSL_TEST_CHECK_GE(v1, v2), expected_output(1, ">=", 2)); +} + +// A type that can be printed using both AbslStringify and operator<<. +struct StringifiableStreamableType { + int x = 0; + explicit StringifiableStreamableType(int x) : x(x) {} + + friend bool operator==(const StringifiableStreamableType& lhs, + const StringifiableStreamableType& rhs) { + return lhs.x == rhs.x; + } + friend bool operator!=(const StringifiableStreamableType& lhs, + const StringifiableStreamableType& rhs) { + return lhs.x != rhs.x; + } + template <typename Sink> + friend void AbslStringify(Sink& sink, + const StringifiableStreamableType& obj) { + absl::Format(&sink, "Strigified{%d}", obj.x); + } + friend std::ostream& operator<<(std::ostream& out, + const StringifiableStreamableType& obj) { + return out << "Streamed{" << obj.x << "}"; + } + + // Avoid unintentional copy. + StringifiableStreamableType(const StringifiableStreamableType&) = delete; +}; + +TEST(CHECKDeathTest, TestStreamingPreferredOverAbslStringify) { + StringifiableStreamableType v1(1); + StringifiableStreamableType v2(2); + + EXPECT_DEATH( + ABSL_TEST_CHECK_EQ(v1, v2), + HasSubstr("Check failed: v1 == v2 (Streamed{1} vs. Streamed{2})")); +} + +// A type whose pointer can be passed to AbslStringify. +struct PointerIsStringifiable {}; +template <typename Sink> +void AbslStringify(Sink& sink, const PointerIsStringifiable* var) { + sink.Append("PointerIsStringifiable"); +} + +// Verifies that a pointer is printed as a number despite having AbslStringify +// defined. Users may implement AbslStringify that dereferences the pointer, and +// doing so as part of DCHECK would not be good. +TEST(CHECKDeathTest, TestPointerPrintedAsNumberDespiteAbslStringify) { + const auto* p = reinterpret_cast<const PointerIsStringifiable*>(0x1234); + +#ifdef _MSC_VER + EXPECT_DEATH( + ABSL_TEST_CHECK_EQ(p, nullptr), + HasSubstr("Check failed: p == nullptr (0000000000001234 vs. (null))")); +#else // _MSC_VER + EXPECT_DEATH(ABSL_TEST_CHECK_EQ(p, nullptr), + HasSubstr("Check failed: p == nullptr (0x1234 vs. (null))")); +#endif // _MSC_VER +} + +// An uncopyable object with operator<<. +struct Uncopyable { + int x; + explicit Uncopyable(int x) : x(x) {} + Uncopyable(const Uncopyable&) = delete; + friend bool operator==(const Uncopyable& lhs, const Uncopyable& rhs) { + return lhs.x == rhs.x; + } + friend bool operator!=(const Uncopyable& lhs, const Uncopyable& rhs) { + return lhs.x != rhs.x; + } + friend std::ostream& operator<<(std::ostream& os, const Uncopyable& obj) { + return os << "Uncopyable{" << obj.x << "}"; + } +}; + +// Test that an uncopyable object can be used. +// Will catch us if implementation has an unintended copy. +TEST(CHECKDeathTest, TestUncopyable) { + const Uncopyable v1(1); + const Uncopyable v2(2); + + EXPECT_DEATH( + ABSL_TEST_CHECK_EQ(v1, v2), + HasSubstr("Check failed: v1 == v2 (Uncopyable{1} vs. Uncopyable{2})")); +} + } // namespace absl_log_internal // NOLINTEND(misc-definitions-in-headers) diff --git a/absl/log/internal/check_op.cc b/absl/log/internal/check_op.cc index f4b67647..23c4a3b2 100644 --- a/absl/log/internal/check_op.cc +++ b/absl/log/internal/check_op.cc @@ -16,6 +16,10 @@ #include <string.h> +#include <ostream> + +#include "absl/strings/string_view.h" + #ifdef _MSC_VER #define strcasecmp _stricmp #else @@ -113,6 +117,22 @@ DEFINE_CHECK_STROP_IMPL(CHECK_STRCASEEQ, strcasecmp, true) DEFINE_CHECK_STROP_IMPL(CHECK_STRCASENE, strcasecmp, false) #undef DEFINE_CHECK_STROP_IMPL +namespace detect_specialization { + +StringifySink::StringifySink(std::ostream& os) : os_(os) {} + +void StringifySink::Append(absl::string_view text) { os_ << text; } + +void StringifySink::Append(size_t length, char ch) { + for (size_t i = 0; i < length; ++i) os_.put(ch); +} + +void AbslFormatFlush(StringifySink* sink, absl::string_view text) { + sink->Append(text); +} + +} // namespace detect_specialization + } // namespace log_internal ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/log/internal/check_op.h b/absl/log/internal/check_op.h index 0d24f4d1..21592207 100644 --- a/absl/log/internal/check_op.h +++ b/absl/log/internal/check_op.h @@ -24,9 +24,11 @@ #include <stdint.h> +#include <cstddef> #include <ostream> #include <sstream> #include <string> +#include <type_traits> #include <utility> #include "absl/base/attributes.h" @@ -35,6 +37,8 @@ #include "absl/log/internal/nullguard.h" #include "absl/log/internal/nullstream.h" #include "absl/log/internal/strip.h" +#include "absl/strings/has_absl_stringify.h" +#include "absl/strings/string_view.h" // `ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL` wraps string literals that // should be stripped when `ABSL_MIN_LOG_LEVEL` exceeds `kFatal`. @@ -287,6 +291,44 @@ decltype(detect_specialization::operator<<(std::declval<std::ostream&>(), std::declval<const T&>())) Detect(char); +// A sink for AbslStringify which redirects everything to a std::ostream. +class StringifySink { + public: + explicit StringifySink(std::ostream& os ABSL_ATTRIBUTE_LIFETIME_BOUND); + + void Append(absl::string_view text); + void Append(size_t length, char ch); + friend void AbslFormatFlush(StringifySink* sink, absl::string_view text); + + private: + std::ostream& os_; +}; + +// Wraps a type implementing AbslStringify, and implements operator<<. +template <typename T> +class StringifyToStreamWrapper { + public: + explicit StringifyToStreamWrapper(const T& v ABSL_ATTRIBUTE_LIFETIME_BOUND) + : v_(v) {} + + friend std::ostream& operator<<(std::ostream& os, + const StringifyToStreamWrapper& wrapper) { + StringifySink sink(os); + AbslStringify(sink, wrapper.v_); + return os; + } + + private: + const T& v_; +}; + +// This overload triggers when T implements AbslStringify. +// StringifyToStreamWrapper is used to allow MakeCheckOpString to use +// operator<<. +template <typename T> +std::enable_if_t<HasAbslStringify<T>::value, + StringifyToStreamWrapper<T>> +Detect(...); // Ellipsis has lowest preference when int passed. } // namespace detect_specialization template <typename T> @@ -342,20 +384,20 @@ ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(const void*); // `(int, int)` override works around the issue that the compiler will not // instantiate the template version of the function on values of unnamed enum // type. -#define ABSL_LOG_INTERNAL_CHECK_OP_IMPL(name, op) \ - template <typename T1, typename T2> \ - inline constexpr ::std::string* name##Impl(const T1& v1, const T2& v2, \ - const char* exprtext) { \ - using U1 = CheckOpStreamType<T1>; \ - using U2 = CheckOpStreamType<T2>; \ - return ABSL_PREDICT_TRUE(v1 op v2) \ - ? nullptr \ - : ABSL_LOG_INTERNAL_CHECK_OP_IMPL_RESULT(U1, U2, v1, v2, \ - exprtext); \ - } \ - inline constexpr ::std::string* name##Impl(int v1, int v2, \ - const char* exprtext) { \ - return name##Impl<int, int>(v1, v2, exprtext); \ +#define ABSL_LOG_INTERNAL_CHECK_OP_IMPL(name, op) \ + template <typename T1, typename T2> \ + inline constexpr ::std::string* name##Impl(const T1& v1, const T2& v2, \ + const char* exprtext) { \ + using U1 = CheckOpStreamType<T1>; \ + using U2 = CheckOpStreamType<T2>; \ + return ABSL_PREDICT_TRUE(v1 op v2) \ + ? nullptr \ + : ABSL_LOG_INTERNAL_CHECK_OP_IMPL_RESULT(U1, U2, U1(v1), \ + U2(v2), exprtext); \ + } \ + inline constexpr ::std::string* name##Impl(int v1, int v2, \ + const char* exprtext) { \ + return name##Impl<int, int>(v1, v2, exprtext); \ } ABSL_LOG_INTERNAL_CHECK_OP_IMPL(Check_EQ, ==) |