From da120eedfd1bfea5ca8d3c67dd596f6e09626a1f Mon Sep 17 00:00:00 2001 From: Filip Niksic Date: Tue, 3 Feb 2026 11:52:26 -0800 Subject: [PATCH] Refactor custom printer extension points. The source code printing extension point `FuzzTestPrintSourceCode` is changed to be consistent with `AbslStringify`. The custom printer auto-detection is simplified: a type is considered to have a custom printer if it has either `AbslStringify` or `FuzzTestPrintSourceCode` (or both). `AbslStringify` is used for human-readable mode, and `FuzzTestPrintSourceCode` is used for the source-code mode. Either is a fallback if the other extension point is missing. PiperOrigin-RevId: 864980702 --- doc/domains-reference.md | 51 +++-- e2e_tests/BUILD | 1 - e2e_tests/functional_test.cc | 12 +- .../fuzz_tests_for_functional_testing.cc | 51 ++--- fuzztest/internal/BUILD | 1 + fuzztest/internal/CMakeLists.txt | 1 + fuzztest/internal/type_support.h | 186 ++++++++++-------- fuzztest/internal/type_support_test.cc | 153 +++++++++++--- 8 files changed, 282 insertions(+), 174 deletions(-) diff --git a/doc/domains-reference.md b/doc/domains-reference.md index 1a8ab9147..f350ea733 100644 --- a/doc/domains-reference.md +++ b/doc/domains-reference.md @@ -896,7 +896,7 @@ values, and you can customize the output for each: expression. This is used in the auto-generated regression tests to recreate the exact value that caused the failure. This mode is purely best-effort. -### Customizing the human-readable printer: +### Customizing the human-readable printer The simplest and most recommended way to implement custom printing for your types is to implement `AbslStringify`. This hooks into Abseil's string @@ -914,26 +914,45 @@ struct MyObject { }; ``` -### Customizing the source code printer: +### Customizing the source code printer -To provide a custom source code printer, you can implement the -`FuzzTestPrintSourceCode` function for your type: +To provide a custom source code representation (which is used in regression +tests), you can implement the `FuzzTestPrintSourceCode` function for your type. +For example: ```c++ -void FuzzTestPrintSourceCode(const YourValueType& v, std::ostream* os) { - // Write a valid C++ expression that recreates `v` into the output stream. - *os << "my_namespace::CreateMyObject(" << v.GetId() << ").WithName(\"" << v.name << "\")"; -} +class MyObject { + public: + static MyObject Make(int id, std::string name) { + return MyObject{id, std::move(name)}; + } + + template + friend void FuzzTestPrintSourceCode(Sink& sink, const MyObject& v) { + absl::Format(&sink, "MyObject::Make(%d, \"%s\")", v.id_, v.name_); + } + + private: + MyObject(int id, std::string name) : id_{id}, name_{std::move(name)} {} + + int id_ = 0; + std::string name_; +}; ``` -FuzzTest will call this function to generate the reproduction code for a test -failure. +If you define only one of `AbslStringify` and `FuzzTestPrintSourceCode`, +FuzzTest will use the defined function for both the human-readable and the +source-code mode. If you define both, FuzzTest will use `AbslStringify` for the +human-readable mode, and `FuzzTestPrintSourceCode` for the source-code mode. +Note that this fallback behavior primarily applies to non-aggregate types. For +aggregate types (like simple structs), FuzzTest prefers field-level printing for +the mode that doesn't have a custom extension point. NOTE: FuzzTest does not validate the output. You are responsible for ensuring -the function prints a valid C++ expression that correctly recreates the original -value. +that in the source-code mode, the function prints a valid C++ expression that +correctly recreates the original value. -NOTE: FuzzTest uses C++ name resolution to find `FuzzTestPrintSourceCode` (and -`AbslStringify` for that matter). This function should generally be declared -either at the same place as the printed struct/class or in the same translation -unit as the fuzz test. +NOTE: FuzzTest uses ADL resolution to find `AbslStringify` and +`FuzzTestPrintSourceCode`. These functions should generally be declared either +at the same place as the printed struct/class or in the same translation unit as +the fuzz test. diff --git a/e2e_tests/BUILD b/e2e_tests/BUILD index 0bbdc186e..fc0e23f5b 100644 --- a/e2e_tests/BUILD +++ b/e2e_tests/BUILD @@ -72,7 +72,6 @@ cc_test( "@com_google_fuzztest//common:temp_dir", "@com_google_fuzztest//fuzztest/internal:escaping", "@com_google_fuzztest//fuzztest/internal:io", - "@com_google_fuzztest//fuzztest/internal:logging", "@com_google_fuzztest//fuzztest/internal:printer", "@com_google_fuzztest//fuzztest/internal:serialization", "@com_google_fuzztest//fuzztest/internal:subprocess", diff --git a/e2e_tests/functional_test.cc b/e2e_tests/functional_test.cc index 3ccbcc1c0..bd54ea750 100644 --- a/e2e_tests/functional_test.cc +++ b/e2e_tests/functional_test.cc @@ -38,7 +38,6 @@ #include "./e2e_tests/test_binary_util.h" #include "./fuzztest/internal/escaping.h" #include "./fuzztest/internal/io.h" -#include "./fuzztest/internal/logging.h" #include "./fuzztest/internal/printer.h" #include "./fuzztest/internal/serialization.h" #include "./fuzztest/internal/subprocess.h" @@ -449,14 +448,15 @@ TEST_F(UnitTestModeTest, CustomSourceCodePrinterCorrectlyPrintsValue) { auto [status, std_out, std_err] = Run("MySuite.CustomSourceCodePrinterCorrectlyPrintsValue"); ExpectTargetAbort(status, std_err); - // This is the argument to the output domain. + // Human-readable mode. EXPECT_THAT_LOG( std_err, - HasSubstr("MyCustomPrintableTestType::BuildWithValue(\"abcd\")")); - // This is the argument to the input domain. - EXPECT_THAT_LOG(std_err, Not(HasSubstr("argument 0: \"abcd\""))); + HasSubstr("argument 0: MyCustomPrintableTestType{val=\"abcd\"}")); + // Source code mode. EXPECT_THAT_LOG( - std_err, HasSubstr("argument 0: MyCustomPrintableTestType - input=abcd")); + std_err, HasReproducerTest( + "MySuite", "CustomSourceCodePrinterCorrectlyPrintsValue", + "MyCustomPrintableTestType::BuildWithValue\\(\"abcd\"\\)")); } TEST_F(UnitTestModeTest, PrintsVeryLongInputsTrimmed) { diff --git a/e2e_tests/testdata/fuzz_tests_for_functional_testing.cc b/e2e_tests/testdata/fuzz_tests_for_functional_testing.cc index be0739e61..0a066d024 100644 --- a/e2e_tests/testdata/fuzz_tests_for_functional_testing.cc +++ b/e2e_tests/testdata/fuzz_tests_for_functional_testing.cc @@ -692,53 +692,30 @@ FUZZ_TEST(MySuite, FlatMapCorrectlyPrintsValues) }, Just(3))); -namespace { struct MyCustomPrintableTestType { public: - static MyCustomPrintableTestType BuildWithValue(std::string inp) { - return MyCustomPrintableTestType(inp + "_foo", inp + "_bar"); - } - absl::string_view foo() const { return foo_; } - absl::string_view bar() const { return bar_; } - bool correctly_built() const { - auto ends_with = [](absl::string_view s, absl::string_view suffix) { - return s.size() >= suffix.size() && - s.substr(s.size() - suffix.size()) == suffix; - }; - return ends_with(foo(), "_foo") && ends_with(bar(), "_bar") && - foo().substr(0, foo().size() - 4) == - bar().substr(0, bar().size() - 4); + static MyCustomPrintableTestType BuildWithValue(std::string val) { + return MyCustomPrintableTestType{std::move(val)}; } template friend void AbslStringify(Sink& sink, const MyCustomPrintableTestType& v) { - if (v.correctly_built()) { - absl::Format(&sink, "MyCustomPrintableTestType - input=%s", - v.foo().substr(0, v.foo().size() - 4)); - return; - } - absl::Format(&sink, "MyCustomPrintableTestType - foo=%s bar=%s", v.foo(), - v.bar()); + absl::Format(&sink, "MyCustomPrintableTestType{val=\"%s\"}", v.val_); + } + + template + friend void FuzzTestPrintSourceCode(Sink& sink, + const MyCustomPrintableTestType& v) { + absl::Format(&sink, "MyCustomPrintableTestType::BuildWithValue(\"%s\")", + v.val_); } private: - MyCustomPrintableTestType(std::string foo, std::string bar) - : foo_(foo), bar_(bar) {} - std::string foo_; - std::string bar_; + MyCustomPrintableTestType(std::string val) : val_{std::move(val)} {} + + std::string val_; }; -void FuzzTestPrintSourceCode(const MyCustomPrintableTestType& v, // NOLINT - std::ostream* os) { - if (v.correctly_built()) { - *os << "MyCustomPrintableTestType::BuildWithValue(\"" - << v.foo().substr(0, v.foo().size() - 4) << "\")"; - return; - } - // NB Not actually valid constructor. - *os << "MyCustomPrintableTestType{.foo=\"" << v.foo() << "\", .bar=\"" - << v.bar() << "\"}"; -} -} // namespace + void CustomSourceCodePrinterCorrectlyPrintsValue( const MyCustomPrintableTestType& v) { std::abort(); diff --git a/fuzztest/internal/BUILD b/fuzztest/internal/BUILD index ce749d409..abb0a3fa0 100644 --- a/fuzztest/internal/BUILD +++ b/fuzztest/internal/BUILD @@ -547,6 +547,7 @@ cc_library( deps = [ ":meta", ":printer", + "@abseil-cpp//absl/base:nullability", "@abseil-cpp//absl/debugging:symbolize", "@abseil-cpp//absl/functional:function_ref", "@abseil-cpp//absl/numeric:int128", diff --git a/fuzztest/internal/CMakeLists.txt b/fuzztest/internal/CMakeLists.txt index 147ae5115..1a674bcd5 100644 --- a/fuzztest/internal/CMakeLists.txt +++ b/fuzztest/internal/CMakeLists.txt @@ -550,6 +550,7 @@ fuzztest_cc_library( DEPS fuzztest::meta fuzztest::printer + absl::nullability absl::symbolize absl::function_ref absl::int128 diff --git a/fuzztest/internal/type_support.h b/fuzztest/internal/type_support.h index 063083605..cc3063622 100644 --- a/fuzztest/internal/type_support.h +++ b/fuzztest/internal/type_support.h @@ -20,8 +20,6 @@ #include #include #include -#include -#include #include #include #include @@ -29,6 +27,7 @@ #include #include +#include "absl/base/nullability.h" #include "absl/debugging/symbolize.h" #include "absl/functional/function_ref.h" #include "absl/numeric/int128.h" @@ -58,13 +57,12 @@ namespace fuzztest::internal { // value. // It implements a good printer for common known types and fallbacks to an // "unknown" printer to prevent compile time errors. -template +template decltype(auto) AutodetectTypePrinter(); -// Returns true iff type `T` has a known printer that isn't UnknownPrinter for -// the given mode. +// Returns true iff type `T` has a known printer that isn't UnknownPrinter. template -constexpr bool HasKnownPrinter(domain_implementor::PrintMode mode); +constexpr bool HasKnownPrinter(); // If `prefix` is present in `name`, consume everything until the rightmost // occurrence of `prefix` and return true. Otherwise, return false. @@ -439,8 +437,7 @@ struct MappedPrinter { } case domain_implementor::PrintMode::kSourceCode: if constexpr (!HasFunctionName() && - HasKnownPrinter( - domain_implementor::PrintMode::kSourceCode)) { + HasKnownPrinter()) { if (map_fn_name.empty()) { // Fall back on printing the user value if the mapping function is // unknown (e.g. a lambda) and the value has a useful printer. @@ -489,31 +486,6 @@ struct FlatMappedPrinter { } }; -struct AutodetectAggregatePrinter { - template - void PrintUserValue(const T& v, domain_implementor::RawSink out, - domain_implementor::PrintMode mode) { - if (mode == domain_implementor::PrintMode::kHumanReadable) { - // In human-readable mode, prefer formatting with Abseil if possible. - if constexpr (has_absl_stringify_v) { - absl::Format(out, "%v", v); - return; - } - } - std::tuple bound = DetectBindAggregate(v); - const auto print_one = [&](auto I) { - if (I > 0) absl::Format(out, ", "); - AutodetectTypePrinter< - std::remove_reference_t>>() - .PrintUserValue(std::get(bound), out, mode); - }; - absl::Format(out, "%s{", GetTypeNameIfUserDefined()); - ApplyIndex>( - [&](auto... Is) { (print_one(Is), ...); }); - absl::Format(out, "}"); - } -}; - struct DurationPrinter { void PrintUserValue(const absl::Duration duration, domain_implementor::RawSink out, @@ -571,6 +543,92 @@ struct TimePrinter { } }; +// A wrapper around `RawSink` that can be passed to the user's +// `FuzzTestPrintSourceCode` function, a FTADLE extension point for custom +// printers. This is so that the function can look more like `AbslStringify`, +// which is parameterized by the sink type and passes a pointer to the sink to +// `absl::Format()`. +struct RawSinkWrapper { + domain_implementor::RawSink& raw_sink; + + friend void AbslFormatFlush(RawSinkWrapper* absl_nonnull sink, + absl::string_view part) { + absl::Format(sink->raw_sink, "%s", part); + } +}; + +template +struct HasCustomSourceCodePrinter : std::false_type {}; + +template +struct HasCustomSourceCodePrinter< + T, std::enable_if_t(), std::declval()))>::value>> + : std::true_type {}; + +template +inline constexpr bool has_custom_source_code_printer_v = + HasCustomSourceCodePrinter::value; + +template +inline constexpr bool has_custom_printer_v = + has_absl_stringify_v || has_custom_source_code_printer_v; + +struct AutodetectAggregatePrinter { + template + void PrintUserValue(const T& v, domain_implementor::RawSink out, + domain_implementor::PrintMode mode) { + if (mode == domain_implementor::PrintMode::kHumanReadable) { + // In human-readable mode, prefer formatting with Abseil if possible. + if constexpr (has_absl_stringify_v) { + absl::Format(out, "%v", v); + return; + } + } else { + // In source-code mode, prefer custom source-code printer if possible. + if constexpr (has_custom_source_code_printer_v) { + RawSinkWrapper sink{out}; + FuzzTestPrintSourceCode(sink, v); + return; + } + } + std::tuple bound = DetectBindAggregate(v); + const auto print_one = [&](auto I) { + if (I > 0) absl::Format(out, ", "); + AutodetectTypePrinter< + std::remove_reference_t>>() + .PrintUserValue(std::get(bound), out, mode); + }; + absl::Format(out, "%s{", GetTypeNameIfUserDefined()); + ApplyIndex>( + [&](auto... Is) { (print_one(Is), ...); }); + absl::Format(out, "}"); + } +}; + +template >> +struct CustomPrinter { + void PrintUserValue(const T& v, domain_implementor::RawSink out, + domain_implementor::PrintMode mode) { + RawSinkWrapper sink{out}; + if (mode == domain_implementor::PrintMode::kHumanReadable) { + // Prefer AbslStringify, fall back on source code printer. + if constexpr (has_absl_stringify_v) { + absl::Format(out, "%v", v); + } else { + FuzzTestPrintSourceCode(sink, v); + } + } else { + // Prefer source code printer, fall back on AbslStringify. + if constexpr (has_custom_source_code_printer_v) { + FuzzTestPrintSourceCode(sink, v); + } else { + absl::Format(out, "%v", v); + } + } + } +}; + struct UnknownPrinter { template void PrintUserValue(const T& v, domain_implementor::RawSink out, @@ -592,31 +650,13 @@ struct UnknownPrinter { } }; -template -struct HasCustomSourceCodePrinter : std::false_type {}; - -template -struct HasCustomSourceCodePrinter< - T, std::enable_if_t(), std::declval()))>::value>> - : std::true_type {}; - template -inline constexpr bool has_custom_source_code_printer_v = - HasCustomSourceCodePrinter::value; - -struct CustomPrinter { - template - void PrintUserValue(const T& v, domain_implementor::RawSink out, - domain_implementor::PrintMode mode); -}; - -template decltype(auto) AutodetectTypePrinter() { - if constexpr (kAllowCustomSourcePrinter && - has_custom_source_code_printer_v) { - return CustomPrinter{}; - } else if constexpr (is_protocol_buffer_enum_v) { + // The order of these checks somewhat matters. Most of the concrete types have + // AbslStringify, so they should come first not to be captured by the custom + // printer case. The aggregate case is also more specific than the custom + // printer case, so it needs to come before. + if constexpr (is_protocol_buffer_enum_v) { return ProtobufEnumPrinter{ google::protobuf::GetEnumDescriptor()}; } else if constexpr (std::numeric_limits::is_integer || @@ -632,43 +672,23 @@ decltype(auto) AutodetectTypePrinter() { return MonostatePrinter{}; } else if constexpr (is_protocol_buffer_v) { return ProtobufPrinter{}; - } else if constexpr (is_bindable_aggregate_v) { - return AutodetectAggregatePrinter{}; } else if constexpr (std::is_same_v) { return DurationPrinter{}; } else if constexpr (std::is_same_v) { return TimePrinter{}; + } else if constexpr (is_bindable_aggregate_v) { + return AutodetectAggregatePrinter{}; + } else if constexpr (has_custom_printer_v) { + return CustomPrinter{}; } else { return UnknownPrinter{}; } } template -void CustomPrinter::PrintUserValue(const T& v, domain_implementor::RawSink out, - domain_implementor::PrintMode mode) { - if (mode == domain_implementor::PrintMode::kSourceCode) { - std::ostringstream oss; - FuzzTestPrintSourceCode(v, &oss); - absl::Format(out, "%s", std::move(oss).str()); - } else { - // Fallback for non-source-code. - auto printer = - AutodetectTypePrinter(); - printer.PrintUserValue(v, out, mode); - } -} - -template -constexpr bool HasKnownPrinter(domain_implementor::PrintMode mode) { - if (mode == domain_implementor::PrintMode::kSourceCode) { - return !std::is_convertible_v< - decltype(AutodetectTypePrinter()), - UnknownPrinter>; - } - return !std::is_convertible_v< - decltype(AutodetectTypePrinter()), - UnknownPrinter>; +constexpr bool HasKnownPrinter() { + return !std::is_convertible_v()), + UnknownPrinter>; } } // namespace fuzztest::internal diff --git a/fuzztest/internal/type_support_test.cc b/fuzztest/internal/type_support_test.cc index bdd558764..62366fa68 100644 --- a/fuzztest/internal/type_support_test.cc +++ b/fuzztest/internal/type_support_test.cc @@ -51,6 +51,7 @@ namespace { using ::fuzztest::domain_implementor::PrintMode; using ::fuzztest::domain_implementor::PrintValue; +using ::fuzztest::domain_implementor::RawSink; using ::testing::AllOf; using ::testing::Contains; using ::testing::Each; @@ -488,35 +489,57 @@ TEST(MonostateTest, Printer) { EXPECT_THAT(TestPrintValue(UserDefinedEmpty{}), Each("UserDefinedEmpty{}")); } -struct AggregateStructWithNoAbslStringify { +struct AggregateWithoutCustomPrinter { int i = 1; std::pair nested = {"Foo", "Bar"}; }; -struct AggregateStructWithAbslStringify { +struct AggregateWithAbslStringify { int i = 1; std::pair nested = {"Foo", "Bar"}; template - friend void AbslStringify(Sink& sink, - const AggregateStructWithAbslStringify& s) { + friend void AbslStringify(Sink& sink, const AggregateWithAbslStringify& s) { absl::Format(&sink, "value={%d, {%s, %s}}", s.i, s.nested.first, s.nested.second); } }; +struct AggregateWithCustomSourceCodePrinter { + int i = 1; + std::pair nested = {"Foo", "Bar"}; + + static AggregateWithCustomSourceCodePrinter Make(int i, std::string fst, + std::string snd) { + return AggregateWithCustomSourceCodePrinter{ + i, {std::move(fst), std::move(snd)}}; + } + + template + friend void FuzzTestPrintSourceCode( + Sink& sink, const AggregateWithCustomSourceCodePrinter& s) { + absl::Format( + &sink, "AggregateWithCustomSourceCodePrinter::Make(%d, \"%s\", \"%s\")", + s.i, s.nested.first, s.nested.second); + } +}; + TEST(AutodetectAggregateTest, Printer) { // MonostateTest handles empty tuple and array. EXPECT_THAT(TestPrintValue(std::tuple{123}), Each("{123}")); EXPECT_THAT(TestPrintValue(std::pair{123, 456}), Each("{123, 456}")); EXPECT_THAT(TestPrintValue(std::array{123, 456}), Each("{123, 456}")); - EXPECT_THAT(TestPrintValue(AggregateStructWithNoAbslStringify{}), - Each(R"(AggregateStructWithNoAbslStringify{1, {"Foo", "Bar"}})")); + EXPECT_THAT(TestPrintValue(AggregateWithoutCustomPrinter{}), + Each(R"(AggregateWithoutCustomPrinter{1, {"Foo", "Bar"}})")); + EXPECT_THAT(TestPrintValue(AggregateWithAbslStringify{}), + ElementsAre("value={1, {Foo, Bar}}", + R"(AggregateWithAbslStringify{1, {"Foo", "Bar"}})")); EXPECT_THAT( - TestPrintValue(AggregateStructWithAbslStringify{}), - ElementsAre("value={1, {Foo, Bar}}", - R"(AggregateStructWithAbslStringify{1, {"Foo", "Bar"}})")); + TestPrintValue(AggregateWithCustomSourceCodePrinter{}), + ElementsAre( + R"(AggregateWithCustomSourceCodePrinter{1, {"Foo", "Bar"}})", + R"(AggregateWithCustomSourceCodePrinter::Make(1, "Foo", "Bar"))")); } TEST(DurationTest, Printer) { @@ -553,36 +576,104 @@ TEST(TimeTest, Printer) { "absl::UnixEpoch() + absl::Seconds(-1290000)")); } -struct NonAggregateStructWithNoAbslStringify { - NonAggregateStructWithNoAbslStringify() : i(1), nested("Foo", "Bar") {} - int i; - std::pair nested; +class ClassWithoutCustomPrinter { + public: + // Needs a default constructor so that it isn't considered an aggregate. + ClassWithoutCustomPrinter() = default; + + private: + [[maybe_unused]] int a_ = 0; }; -struct NonAggregateStructWithAbslStringify { - NonAggregateStructWithAbslStringify() : i(1), nested("Foo", "Bar") {} - int i; - std::pair nested; +class ClassWithAbslStringify { + public: + ClassWithAbslStringify(int a, std::string b) : a_{a}, b_{std::move(b)} {} template - friend void AbslStringify(Sink& sink, - const NonAggregateStructWithAbslStringify& s) { - absl::Format(&sink, "value={%d, {%s, %s}}", s.i, s.nested.first, - s.nested.second); + friend void AbslStringify(Sink& sink, const ClassWithAbslStringify& v) { + absl::Format(&sink, "value={%d, \"%s\"}", v.a_, v.b_); } + + private: + int a_ = 0; + std::string b_; }; -TEST(UnprintableTest, Printer) { - EXPECT_THAT(TestPrintValue(NonAggregateStructWithNoAbslStringify{}), - Each("")); - EXPECT_THAT(TestPrintValue(NonAggregateStructWithAbslStringify{}), - ElementsAre("value={1, {Foo, Bar}}", "")); - EXPECT_THAT( - TestPrintValue(std::vector{}), - Each("")); +class ClassWithCustomSourceCodePrinter { + public: + static ClassWithCustomSourceCodePrinter Make(int a, std::string b) { + return ClassWithCustomSourceCodePrinter{a, std::move(b)}; + } + + template + friend void FuzzTestPrintSourceCode( + Sink& sink, const ClassWithCustomSourceCodePrinter& v) { + absl::Format(&sink, "ClassWithCustomSourceCodePrinter::Make(%d, \"%s\")", + v.a_, v.b_); + } + + private: + ClassWithCustomSourceCodePrinter(int a, std::string b) + : a_{a}, b_{std::move(b)} {} + + int a_ = 0; + std::string b_; +}; + +class ClassWithAbslStringifyAndCustomSourceCodePrinter { + public: + static ClassWithAbslStringifyAndCustomSourceCodePrinter Make(int a, + std::string b) { + return ClassWithAbslStringifyAndCustomSourceCodePrinter{a, std::move(b)}; + } + + template + friend void AbslStringify( + Sink& sink, const ClassWithAbslStringifyAndCustomSourceCodePrinter& v) { + absl::Format(&sink, "value={a=%d, b=\"%s\"}", v.a_, v.b_); + } + + template + friend void FuzzTestPrintSourceCode( + Sink& sink, const ClassWithAbslStringifyAndCustomSourceCodePrinter& v) { + absl::Format( + &sink, + "ClassWithAbslStringifyAndCustomSourceCodePrinter::Make(%d, \"%s\")", + v.a_, v.b_); + } + + private: + ClassWithAbslStringifyAndCustomSourceCodePrinter(int a, std::string b) + : a_{a}, b_{std::move(b)} {} + + int a_ = 0; + std::string b_; +}; + +TEST(HasCustomPrinterTest, CorrectlyDetectsCustomPrinters) { + static_assert(has_custom_printer_v); + static_assert(has_custom_printer_v); + static_assert( + has_custom_printer_v); + static_assert(!has_custom_printer_v); +} + +TEST(CustomPrinterTest, PrintsValuesCorrectly) { + EXPECT_THAT(TestPrintValue(ClassWithAbslStringify{1, "foo"}), + Each("value={1, \"foo\"}")); + EXPECT_THAT(TestPrintValue(ClassWithCustomSourceCodePrinter::Make(1, "foo")), + Each("ClassWithCustomSourceCodePrinter::Make(1, \"foo\")")); EXPECT_THAT( - TestPrintValue(std::vector{}), - Each("")); + TestPrintValue( + ClassWithAbslStringifyAndCustomSourceCodePrinter::Make(1, "foo")), + ElementsAre("value={a=1, b=\"foo\"}", + "ClassWithAbslStringifyAndCustomSourceCodePrinter::Make(1, " + "\"foo\")")); +} + +TEST(UnprintableTest, PrintsUnprintableValues) { + EXPECT_THAT(TestPrintValue(ClassWithoutCustomPrinter{}), + Each("")); } } // namespace