// Copyright 2025 The Abseil Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "absl/strings/internal/generic_printer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/base/attributes.h" #include "absl/base/config.h" #include "absl/cleanup/cleanup.h" #include "absl/container/flat_hash_map.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/str_cat.h" #include "absl/strings/substitute.h" namespace generic_logging_test { struct NotStreamable {}; } // namespace generic_logging_test static std::ostream& operator<<(std::ostream& os, const generic_logging_test::NotStreamable&) { return os << "This overload should NOT be found by GenericPrint."; } // Types to test selection logic for streamable and non-streamable types. namespace generic_logging_test { struct Streamable { int x; friend std::ostream& operator<<(std::ostream& os, const Streamable& l) { return os << "Streamable{" << l.x << "}"; } }; } // namespace generic_logging_test namespace absl { ABSL_NAMESPACE_BEGIN namespace strings_internal { namespace { using ::testing::AllOf; using ::testing::AnyOf; using ::testing::ContainsRegex; using ::testing::EndsWith; using ::testing::Eq; using ::testing::HasSubstr; using ::testing::MatchesRegex; struct AbslStringifiable { template friend void AbslStringify(S& sink, const AbslStringifiable&) { sink.Append("AbslStringifiable!"); } }; auto IsUnprintable() { #ifdef GTEST_USES_SIMPLE_RE return HasSubstr("unprintable value of size"); #else return ContainsRegex( "\\[unprintable value of size [0-9]+ @(0x)?[0-9a-fA-F]+\\]"); #endif } auto HasExactlyNInstancesOf(int n, absl::string_view me) { #ifdef GTEST_USES_SIMPLE_RE (void)n; return HasSubstr(me); #else absl::string_view value_m_times = "(.*$0){$1}.*"; return AllOf(MatchesRegex(absl::Substitute(value_m_times, me, n)), Not(MatchesRegex(absl::Substitute(value_m_times, me, n + 1)))); #endif } template std::string GenericPrintToString(const T& v) { std::stringstream ss; ss << GenericPrint(v); { std::stringstream ss2; ss2 << GenericPrint() << v; EXPECT_EQ(ss.str(), ss2.str()); } return ss.str(); } TEST(GenericPrinterTest, Bool) { EXPECT_EQ("true", GenericPrintToString(true)); EXPECT_EQ("false", GenericPrintToString(false)); } TEST(GenericPrinterTest, VectorOfBool) { std::vector v{true, false, true}; const auto& cv = v; EXPECT_EQ("[true, false, true]", GenericPrintToString(v)); EXPECT_EQ("true", GenericPrintToString(v[0])); EXPECT_EQ("true", GenericPrintToString(cv[0])); } TEST(GenericPrinterTest, CharLiterals) { EXPECT_EQ(R"(a"\b)", GenericPrintToString(R"(a"\b)")); } TEST(GenericPrinterTest, Builtin) { EXPECT_EQ("123", GenericPrintToString(123)); } TEST(GenericPrinterTest, AbslStringifiable) { EXPECT_EQ("AbslStringifiable!", GenericPrintToString(AbslStringifiable{})); } TEST(GenericPrinterTest, Nullptr) { EXPECT_EQ("nullptr", GenericPrintToString(nullptr)); } TEST(GenericPrinterTest, Chars) { EXPECT_EQ(R"('\x0a' (0x0a 10))", GenericPrintToString('\x0a')); EXPECT_EQ(R"(' ' (0x20 32))", GenericPrintToString(' ')); EXPECT_EQ(R"('~' (0x7e 126))", GenericPrintToString('~')); EXPECT_EQ(R"('\'' (0x27 39))", GenericPrintToString('\'')); } TEST(GenericPrinterTest, SignedChars) { EXPECT_EQ(R"('\x0a' (0x0a 10))", GenericPrintToString(static_cast('\x0a'))); EXPECT_EQ(R"(' ' (0x20 32))", GenericPrintToString(static_cast(' '))); EXPECT_EQ(R"('~' (0x7e 126))", GenericPrintToString(static_cast('~'))); EXPECT_EQ(R"('\'' (0x27 39))", GenericPrintToString(static_cast('\''))); } TEST(GenericPrinterTest, UnsignedChars) { EXPECT_EQ(R"('\x0a' (0x0a 10))", GenericPrintToString(static_cast('\x0a'))); EXPECT_EQ(R"(' ' (0x20 32))", GenericPrintToString(static_cast(' '))); EXPECT_EQ(R"('~' (0x7e 126))", GenericPrintToString(static_cast('~'))); EXPECT_EQ(R"('\'' (0x27 39))", GenericPrintToString(static_cast('\''))); } TEST(GenericPrinterTest, Bytes) { EXPECT_EQ("0x00", GenericPrintToString(static_cast(0))); EXPECT_EQ("0x7f", GenericPrintToString(static_cast(0x7F))); EXPECT_EQ("0xff", GenericPrintToString(static_cast(0xFF))); } TEST(GenericPrinterTest, Strings) { const std::string expected_quotes = R"("a\"\\b")"; EXPECT_EQ(expected_quotes, GenericPrintToString(std::string(R"(a"\b)"))); const std::string expected_nonprintable = R"("\x00\xcd\n\xab")"; EXPECT_EQ(expected_nonprintable, GenericPrintToString(absl::string_view("\0\315\n\xAB", 4))); } TEST(GenericPrinterTest, PreciseFloat) { // Instead of testing exactly how the values are formatted, just check that // they are distinct. // Ensure concise output for exact values: EXPECT_EQ("1f", GenericPrintToString(1.f)); EXPECT_EQ("1.1f", GenericPrintToString(1.1f)); // Plausible real-world values: float f = 10.0000095f; EXPECT_NE(GenericPrintToString(f), GenericPrintToString(10.0000105f)); // Smallest increment for a real-world value: EXPECT_NE(GenericPrintToString(f), GenericPrintToString(std::nextafter(f, 11))); // The two smallest (finite) values possible: EXPECT_NE(GenericPrintToString(std::numeric_limits::lowest()), GenericPrintToString( std::nextafter(std::numeric_limits::lowest(), 1))); // Ensure the value has the correct type suffix: EXPECT_THAT(GenericPrintToString(0.f), EndsWith("f")); } TEST(GenericPrinterTest, PreciseDouble) { EXPECT_EQ("1", GenericPrintToString(1.)); EXPECT_EQ("1.1", GenericPrintToString(1.1)); double d = 10.000000000000002; EXPECT_NE(GenericPrintToString(d), GenericPrintToString(10.000000000000004)); EXPECT_NE(GenericPrintToString(d), GenericPrintToString(std::nextafter(d, 11))); EXPECT_NE(GenericPrintToString(std::numeric_limits::lowest()), GenericPrintToString( std::nextafter(std::numeric_limits::lowest(), 1))); EXPECT_THAT(GenericPrintToString(0.), EndsWith("0")); } TEST(GenericPrinterTest, PreciseLongDouble) { EXPECT_EQ("1L", GenericPrintToString(1.L)); EXPECT_EQ("1.1L", GenericPrintToString(1.1L)); long double ld = 10.0000000000000000000000000000002; EXPECT_NE(GenericPrintToString(ld), GenericPrintToString(10.0000000000000000000000000000004)); EXPECT_NE(GenericPrintToString(ld), GenericPrintToString(std::nextafter(ld, 11))); EXPECT_NE(GenericPrintToString(std::numeric_limits::lowest()), GenericPrintToString( std::nextafter(std::numeric_limits::lowest(), 1))); EXPECT_THAT(GenericPrintToString(0.L), EndsWith("L")); } TEST(GenericPrinterTest, PreciseFPUnderCommaRadixLocale) { // The values are formatted with locale-independent absl::StrFormat (a '.' // radix), so the round-trip shortening must not depend on LC_NUMERIC. Under a // comma-radix locale a locale-sensitive reader stops at the '.', which used // to defeat the shortened output. const char* saved = std::setlocale(LC_NUMERIC, nullptr); std::string saved_locale = saved ? saved : "C"; absl::Cleanup restore = [&] { std::setlocale(LC_NUMERIC, saved_locale.c_str()); }; bool set = false; for (const char* name : {"de_DE.UTF-8", "fr_FR.UTF-8", "de_DE", "fr_FR"}) { if (std::setlocale(LC_NUMERIC, name) != nullptr) { set = true; break; } } if (!set) { GTEST_SKIP() << "No comma-radix locale available on this system."; } EXPECT_EQ("1.1f", GenericPrintToString(1.1f)); EXPECT_EQ("1.1", GenericPrintToString(1.1)); } TEST(GenericPrinterTest, StreamableLvalue) { generic_logging_test::Streamable x{234}; EXPECT_EQ("Streamable{234}", GenericPrintToString(x)); } TEST(GenericPrinterTest, StreamableXvalue) { EXPECT_EQ("Streamable{345}", GenericPrintToString(generic_logging_test::Streamable{345})); } TEST(GenericPrinterTest, NotStreamableWithoutGenericPrint) { ::generic_logging_test::NotStreamable x; std::stringstream ss; ::operator<<(ss, x); EXPECT_EQ(ss.str(), "This overload should NOT be found by GenericPrint."); } TEST(GenericPrinterTest, NotStreamableLvalue) { generic_logging_test::NotStreamable x; EXPECT_THAT(GenericPrintToString(x), IsUnprintable()); } TEST(GenericPrinterTest, NotStreamableXvalue) { EXPECT_THAT(GenericPrintToString(generic_logging_test::NotStreamable{}), IsUnprintable()); } TEST(GenericPrinterTest, DebugString) { struct WithDebugString { std::string val; std::string DebugString() const { return absl::StrCat("WithDebugString{", val, "}"); } }; EXPECT_EQ("WithDebugString{foo}", GenericPrintToString(WithDebugString{"foo"})); } TEST(GenericPrinterTest, Vector) { std::vector v = {4, 5, 6}; EXPECT_THAT(GenericPrintToString(v), MatchesRegex(".*4,? 5,? 6.*")); } TEST(GenericPrinterTest, StreamableVector) { std::vector v = {{7}, {8}, {9}}; EXPECT_THAT(GenericPrintToString(v), MatchesRegex(".*Streamable.7.,? Streamable.8.,? Streamable.9.*")); } TEST(GenericPrinterTest, Map) { absl::flat_hash_map< std::string, absl::flat_hash_map>> v = {{"A", {{"B", {.5, .25}}}}}; EXPECT_THAT(GenericPrintToString(v), R"([<"A", [<"B", <0.5, 0.25>>]>])"); std::map>> v2 = { {"A", {{"B", {.5, .25}}}}}; EXPECT_THAT(GenericPrintToString(v2), R"([<"A", [<"B", <0.5, 0.25>>]>])"); } TEST(GenericPrinterTest, StreamAdapter) { std::stringstream ss; static_assert( std::is_same, internal_generic_printer::GenericPrintStreamAdapter::Impl< std::stringstream>>::value, "expected ostream << GenericPrint() to yield adapter impl"); ss << GenericPrint() << "again, " << "back-up, " << "cue, " << "double-u, " << "eye, " << "four: " << generic_logging_test::NotStreamable{}; EXPECT_THAT( ss.str(), MatchesRegex( "again, back-up, cue, double-u, eye, four: .unprintable value.*")); } TEST(GenericPrinterTest, NotStreamableVector) { std::vector v = {{}, {}, {}}; #ifdef GTEST_USES_SIMPLE_RE EXPECT_THAT(GenericPrintToString(v), HasSubstr("unprintable")); #else EXPECT_THAT(GenericPrintToString(v), MatchesRegex(".*(unprintable.*){3}.*")); #endif } struct CustomContainer : public std::array { template friend void AbslStringify(Sink& sink, const CustomContainer& c) { absl::Format(&sink, "%d %d", c[0], c[1]); } }; // Checks that AbslStringify (go/totw/215) is respected for container-like // types. TEST(GenericPrinterTest, ContainerLikeCustomLogging) { CustomContainer c = {1, 2, 3, 4}; EXPECT_EQ(GenericPrintToString(c), "1 2"); } // Test helper: this function demonstrates customizable printing logic: // 'GenericPrinter' can be nominated as a default template argument. template > std::string SpecializablePrint(const T& v) { std::stringstream ss; ss << Printer{v}; return ss.str(); } TEST(GenericPrinterTest, DefaultPrinter) { EXPECT_EQ("123", SpecializablePrint(123)); } // Example of custom printing logic. This doesn't actually test anything in // GenericPrinter, but it's a working example of customizing printing logic (as // opposed to the comments in generic_printer.h). struct CustomPrinter { explicit CustomPrinter(int) {} friend std::ostream& operator<<(std::ostream& os, CustomPrinter&&) { return os << "custom printer"; } }; TEST(GenericPrinterTest, CustomPrinter) { EXPECT_EQ("custom printer", (SpecializablePrint(123))); } TEST(GenricPrinterTest, Nullopt) { EXPECT_EQ("nullopt", GenericPrintToString(std::nullopt)); } TEST(GenericPrinterTest, Optional) { EXPECT_EQ("nullopt", GenericPrintToString(std::optional())); EXPECT_EQ("nullopt", GenericPrintToString(std::optional(std::nullopt))); EXPECT_EQ("<3>", GenericPrintToString(std::make_optional(3))); EXPECT_EQ("", GenericPrintToString(std::make_optional( generic_logging_test::Streamable{3}))); } TEST(GenericPrinterTest, Monostate) { EXPECT_EQ("monostate", GenericPrintToString(std::monostate{})); } TEST(GenericPrinterTest, Tuple) { EXPECT_EQ("<1, two, 3>", GenericPrintToString(std::make_tuple(1, "two", 3))); } TEST(GenericPrinterTest, EmptyTuple) { EXPECT_EQ("<>", GenericPrintToString(std::make_tuple())); } TEST(GenericPrinterTest, TupleWithStreamableMember) { EXPECT_EQ("<1, two, Streamable{3}>", GenericPrintToString(std::make_tuple( 1, "two", generic_logging_test::Streamable{3}))); } TEST(GenericPrinterTest, Variant) { EXPECT_EQ(R"(('(index = 0)' "cow"))", GenericPrintToString(std::variant("cow"))); EXPECT_EQ("('(index = 1)' 1.1f)", GenericPrintToString(std::variant(1.1F))); } TEST(GenericPrinterTest, VariantMonostate) { EXPECT_EQ("('(index = 0)' monostate)", GenericPrintToString(std::variant())); } TEST(GenericPrinterTest, VariantNonStreamable) { EXPECT_EQ(R"(('(index = 0)' "cow"))", GenericPrintToString( std::variant( "cow"))); EXPECT_THAT( GenericPrintToString( std::variant( generic_logging_test::NotStreamable{})), IsUnprintable()); } TEST(GenericPrinterTest, VariantNestedVariant) { EXPECT_EQ( "('(index = 1)' ('(index = 1)' 1.1f))", GenericPrintToString(std::variant>( std::variant(1.1F)))); } TEST(GenericPrinterTest, VariantInPlace) { EXPECT_EQ("('(index = 0)' 17)", GenericPrintToString(std::variant( std::in_place_index<0>, 17))); EXPECT_EQ("('(index = 1)' 17)", GenericPrintToString(std::variant( std::in_place_index<1>, 17))); } TEST(GenericPrinterTest, StatusOrLikeOkPrintsValue) { EXPECT_EQ(R"()", GenericPrintToString(absl::StatusOr("cow"))); EXPECT_EQ(R"()", GenericPrintToString(absl::StatusOr(1.1F))); } TEST(GenericPrinterTest, StatusOrLikeNonOkPrintsStatus) { EXPECT_THAT( GenericPrintToString(absl::StatusOr( absl::InvalidArgumentError("my error message"))), AllOf(HasSubstr("my error message"), HasSubstr("INVALID_ARGUMENT"))); EXPECT_THAT(GenericPrintToString( absl::StatusOr(absl::AbortedError("other message"))), AllOf(HasSubstr("other message"), HasSubstr("ABORTED"))); } TEST(GenericPrinterTest, StatusOrLikeNonStreamableValueUnprintable) { EXPECT_THAT( GenericPrintToString(absl::StatusOr( generic_logging_test::NotStreamable{})), IsUnprintable()); } TEST(GenericPrinterTest, StatusOrLikeNonStreamableErrorStillPrintable) { EXPECT_THAT( GenericPrintToString(absl::StatusOr( absl::AbortedError("other message"))), AllOf(HasSubstr("other message"), HasSubstr("ABORTED"))); } TEST(GenericPrinterTest, IsSupportedPointer) { using internal_generic_printer::is_supported_ptr; EXPECT_TRUE(is_supported_ptr>); EXPECT_TRUE(is_supported_ptr>); EXPECT_TRUE((is_supported_ptr>)); EXPECT_FALSE(is_supported_ptr); EXPECT_FALSE(is_supported_ptr>); EXPECT_FALSE(is_supported_ptr>); } TEST(GenericPrinterTest, SmartPointerPrintsNullptrForAllNullptrs) { std::unique_ptr up; EXPECT_EQ("", GenericPrintToString(up)); } TEST(GenericPrinterTest, SmartPointerPrintsValueIfNonNull) { EXPECT_THAT(GenericPrintToString(std::make_unique(5)), HasSubstr("pointing to 5")); } TEST(GenericPrinterTest, SmartPointerPrintsAddressOfPointee) { auto i = std::make_unique(5); auto c = std::make_unique('z'); char memory[] = "abcdefg"; auto cp = std::make_unique(memory); EXPECT_THAT(GenericPrintToString(i), AnyOf(Eq(absl::StrFormat("<%016X pointing to 5>", reinterpret_cast(&*i))), Eq(absl::StrFormat("<%#x pointing to 5>", reinterpret_cast(&*i))))); EXPECT_THAT( GenericPrintToString(c), AnyOf(HasSubstr(absl::StrFormat("<%016X pointing to 'z'", reinterpret_cast(&*c))), HasSubstr(absl::StrFormat("<%#x pointing to 'z'", reinterpret_cast(&*c))))); EXPECT_THAT(GenericPrintToString(cp), AnyOf(Eq(absl::StrFormat("<%016X pointing to abcdefg>", reinterpret_cast(&*cp))), Eq(absl::StrFormat("<%#x pointing to abcdefg>", reinterpret_cast(&*cp))))); } TEST(GenericPrinterTest, SmartPointerToArrayOnlyPrintsAddressAndHelpText) { auto empty = std::make_unique(0); auto nonempty = std::make_unique(5); nonempty[0] = 12345; nonempty[4] = 54321; // NOTE: ArenaSafeUniquePtr is not meant to support array-type template // parameters, so we skip testing that here. // http://g/c-users/J-AEFrFHssY/UMMFzCkdBAAJ, b/265984185. EXPECT_THAT( GenericPrintToString(nonempty), AllOf(AnyOf(HasSubstr(absl::StrFormat( "%016X", reinterpret_cast(nonempty.get()))), HasSubstr(absl::StrFormat( "%#x", reinterpret_cast(nonempty.get())))), HasSubstr("array"), Not(HasSubstr("to 54321")), Not(HasSubstr("to 12345")))); EXPECT_THAT( GenericPrintToString(empty), AllOf(AnyOf(HasSubstr(absl::StrFormat( "%016X", reinterpret_cast(empty.get()))), HasSubstr(absl::StrFormat( "%#x", reinterpret_cast(empty.get())))), HasSubstr("array"))); } TEST(GenericPrinterTest, SmartPointerToNonObjectType) { auto int_ptr_deleter = [](void* data) { int* p = static_cast(data); delete p; }; std::unique_ptr void_ptr(new int(959), int_ptr_deleter); EXPECT_THAT(GenericPrintToString(void_ptr), HasSubstr("pointing to a non-object type")); } TEST(GenericPrinterTest, PrintsCustomDeleterSmartPointer) { // Delete `p` (if not nullptr) only on the 4th time the deleter is used. auto four_deleter = [](std::string* p) { static int counter = 0; if (p == nullptr) return; // skip calls to moved-from destructors. if (++counter >= 4) delete p; }; // Have four `unique_ptr`s "manage" the same string-pointer, with only the // final (4th) call to the deleter deleting the string pointer. auto* unique_string = new std::string("unique string"); std::vector> test_ptrs; for (int i = 0; i < 4; ++i) { test_ptrs.emplace_back(unique_string, four_deleter); } EXPECT_THAT(GenericPrintToString(test_ptrs), HasExactlyNInstancesOf(4, "unique string")); } // Ensure that GenericPrint is robust to recursion when a type's operator<< // calls into GenericPrint internally. struct CustomRecursive { std::unique_ptr next; int val = 0; friend std::ostream& operator<<(std::ostream& os, const CustomRecursive& cr) { return os << "custom print: next = " << GenericPrintToString(cr.next); } }; TEST(GenericPrinterTest, DISABLED_CustomPrintOverloadRecursionDetected) { auto r1 = std::make_unique(); r1->val = 1; auto& r2 = r1->next = std::make_unique(); r2->val = 2; r2->next = std::move(r1); EXPECT_THAT(GenericPrintToString(*r2), AllOf(HasExactlyNInstancesOf(2, "custom print"), HasExactlyNInstancesOf(1, ""))); r2->next = nullptr; // break the cycle } // enum CStyleEnum { kValue0, kValue1 }; TEST(GenericPrinterTest, Enum) { EXPECT_EQ("1", GenericPrintToString(kValue1)); } enum class CppStyleEnum { kValue0, kValue1, kValue2 }; TEST(GenericPrinterTest, EnumClass) { EXPECT_EQ("2", GenericPrintToString(CppStyleEnum::kValue2)); } enum class CharBasedEnum : char { kValueA = 'A', kValue1 = '\x01' }; TEST(GenericPrinterTest, CharBasedEnum) { EXPECT_EQ("'A' (0x41 65)", GenericPrintToString(CharBasedEnum::kValueA)); EXPECT_EQ("'\\x01' (0x01 1)", GenericPrintToString(CharBasedEnum::kValue1)); } enum class WideBasedEnum : uint64_t { kValue = std::numeric_limits::max() }; TEST(GenericPrinterTest, WideBasedEnum) { EXPECT_EQ(absl::StrCat(std::numeric_limits::max()), GenericPrintToString(WideBasedEnum::kValue)); } enum CStyleEnumWithStringify { kValueA = 0, kValueB = 2 }; template void AbslStringify(Sink& sink, CStyleEnumWithStringify e) { switch (e) { case CStyleEnumWithStringify::kValueA: sink.Append("A"); return; case CStyleEnumWithStringify::kValueB: sink.Append("B"); return; } sink.Append("??"); } TEST(GenericPrinterTest, CStyleEnumWithStringify) { EXPECT_EQ("A", GenericPrintToString(CStyleEnumWithStringify::kValueA)); EXPECT_EQ("??", GenericPrintToString(static_cast(1))); } enum class CppStyleEnumWithStringify { kValueA, kValueB, kValueC }; template void AbslStringify(Sink& sink, CppStyleEnumWithStringify e) { switch (e) { case CppStyleEnumWithStringify::kValueA: sink.Append("A"); return; case CppStyleEnumWithStringify::kValueB: sink.Append("B"); return; case CppStyleEnumWithStringify::kValueC: sink.Append("C"); return; } sink.Append("??"); } TEST(GenericPrinterTest, CppStyleEnumWithStringify) { EXPECT_EQ("A", GenericPrintToString(CppStyleEnumWithStringify::kValueA)); EXPECT_EQ("??", GenericPrintToString(static_cast(17))); } enum class CharBasedEnumWithStringify : char { kValueA = 'A', kValueB = 'B' }; template void AbslStringify(Sink& sink, CharBasedEnumWithStringify e) { switch (e) { case CharBasedEnumWithStringify::kValueA: sink.Append("charA"); return; case CharBasedEnumWithStringify::kValueB: sink.Append("charB"); return; } sink.Append("??"); } TEST(GenericPrinterTest, CharBasedEnumWithStringify) { EXPECT_EQ("charA", GenericPrintToString(CharBasedEnumWithStringify::kValueA)); EXPECT_EQ("??", GenericPrintToString(static_cast('W'))); } } // namespace } // namespace strings_internal ABSL_NAMESPACE_END } // namespace absl