aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAbseil Team <absl-team@google.com>2024-05-16 01:34:16 -0700
committerCopybara-Service <copybara-worker@google.com>2024-05-16 01:35:22 -0700
commit93ac3a4f9ee7792af399cebd873ee99ce15aed08 (patch)
treefc58844a9aab59d56d937ecd2f63c3a8fd31c973
parentcbfe51b2c01da330ff292b145de91346a5950163 (diff)
downloadabseil-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.bazel2
-rw-r--r--absl/log/CMakeLists.txt4
-rw-r--r--absl/log/check_test_impl.inc158
-rw-r--r--absl/log/internal/check_op.cc20
-rw-r--r--absl/log/internal/check_op.h70
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, ==)