diff --git a/absl/log/check_test_impl.inc b/absl/log/check_test_impl.inc index 37226a36..5a7caf47 100644 --- a/absl/log/check_test_impl.inc +++ b/absl/log/check_test_impl.inc @@ -265,6 +265,26 @@ TEST(CHECKTest, TestBinaryChecksWithNullptr) { ABSL_TEST_CHECK_NE(nullptr, p_not_null); } +struct ExampleTypeThatHasNoStreamOperator { + bool x; + + bool operator==(const ExampleTypeThatHasNoStreamOperator& other) const { + return x == other.x; + } + bool operator==(const bool& other) const { return x == other; } +}; + +TEST(CHECKDeathTest, TestBinaryChecksWithUnprintable) { + ExampleTypeThatHasNoStreamOperator a{true}; + ExampleTypeThatHasNoStreamOperator b{false}; + ABSL_TEST_CHECK_EQ(a, a); + EXPECT_DEATH(ABSL_TEST_CHECK_EQ(a, b), + "Check failed: a == b \\(UNPRINTABLE vs. UNPRINTABLE\\)"); + ABSL_TEST_CHECK_EQ(a, true); + EXPECT_DEATH(ABSL_TEST_CHECK_EQ(a, false), + "Check failed: a == false \\(UNPRINTABLE vs. 0\\)"); +} + #if GTEST_HAS_DEATH_TEST // Test logging of various char-typed values by failing CHECK*(). diff --git a/absl/log/internal/check_op.cc b/absl/log/internal/check_op.cc index 23db63bf..5db98dd9 100644 --- a/absl/log/internal/check_op.cc +++ b/absl/log/internal/check_op.cc @@ -101,6 +101,8 @@ void MakeCheckOpValueString(std::ostream& os, const void* p) { } } +void MakeCheckOpUnprintableString(std::ostream& os) { os << "UNPRINTABLE"; } + // Helper functions for string comparisons. #define DEFINE_CHECK_STROP_IMPL(name, func, expected) \ const char* absl_nullable Check##func##expected##Impl( \ diff --git a/absl/log/internal/check_op.h b/absl/log/internal/check_op.h index d7b55f6f..4554475d 100644 --- a/absl/log/internal/check_op.h +++ b/absl/log/internal/check_op.h @@ -224,6 +224,19 @@ void MakeCheckOpValueString(std::ostream& os, signed char v); void MakeCheckOpValueString(std::ostream& os, unsigned char v); void MakeCheckOpValueString(std::ostream& os, const void* absl_nullable p); +void MakeCheckOpUnprintableString(std::ostream& os); + +// A wrapper for types that have no operator<<. +struct UnprintableWrapper { + template + explicit UnprintableWrapper(const T&) {} + + friend std::ostream& operator<<(std::ostream& os, const UnprintableWrapper&) { + MakeCheckOpUnprintableString(os); + return os; + } +}; + namespace detect_specialization { // MakeCheckOpString is being specialized for every T and U pair that is being @@ -353,6 +366,15 @@ struct is_streamable() << std::declval())>> : std::true_type {}; +// This overload triggers when T is neither possible to print nor an enum. +template +std::enable_if_t, std::is_enum, + std::is_pointer, std::is_same, + is_streamable, HasAbslStringify>>, + UnprintableWrapper> +Detect(...); + // This overload triggers when T is a scoped enum that has not defined an output // stream operator (operator<<) or AbslStringify. It causes the enum value to be // converted to a type that can be streamed. For consistency with other enums, a