Refactor FuzzTest 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. This change required updates in the XLS code, most of which are straightforward. One thing worth pointing out is that I moved the printing function for `std::vector<std::vector<Value>>` from `ir_fuzz_test_library` to the `value` library, where it fits more naturally. PiperOrigin-RevId: 868685539
diff --git a/doc/domains-reference.md b/doc/domains-reference.md index 929f7ad..a656179 100644 --- a/doc/domains-reference.md +++ b/doc/domains-reference.md
@@ -905,7 +905,7 @@ 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 @@ -923,26 +923,45 @@ }; ``` -### 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 <typename Sink> + 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 0bbdc18..fc0e23f 100644 --- a/e2e_tests/BUILD +++ b/e2e_tests/BUILD
@@ -72,7 +72,6 @@ "@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 3ccbcc1..bd54ea7 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 @@ 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 be0739e..0a066d0 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 @@ }, 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 <typename Sink> 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 <typename Sink> + 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 ce749d4..abb0a3f 100644 --- a/fuzztest/internal/BUILD +++ b/fuzztest/internal/BUILD
@@ -547,6 +547,7 @@ 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 147ae51..1a674bc 100644 --- a/fuzztest/internal/CMakeLists.txt +++ b/fuzztest/internal/CMakeLists.txt
@@ -550,6 +550,7 @@ 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 0630836..cc30636 100644 --- a/fuzztest/internal/type_support.h +++ b/fuzztest/internal/type_support.h
@@ -20,8 +20,6 @@ #include <cstddef> #include <cstdint> #include <limits> -#include <ostream> -#include <sstream> #include <string> #include <string_view> #include <tuple> @@ -29,6 +27,7 @@ #include <utility> #include <vector> +#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 @@ // value. // It implements a good printer for common known types and fallbacks to an // "unknown" printer to prevent compile time errors. -template <typename T, bool kAllowCustomSourcePrinter = true> +template <typename T> 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 <typename T> -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 @@ } case domain_implementor::PrintMode::kSourceCode: if constexpr (!HasFunctionName<Mapper>() && - HasKnownPrinter<decltype(value)>( - domain_implementor::PrintMode::kSourceCode)) { + HasKnownPrinter<decltype(value)>()) { 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 AutodetectAggregatePrinter { - template <typename T> - 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<T>) { - 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<std::tuple_element_t<I, decltype(bound)>>>() - .PrintUserValue(std::get<I>(bound), out, mode); - }; - absl::Format(out, "%s{", GetTypeNameIfUserDefined<T>()); - ApplyIndex<std::tuple_size_v<decltype(bound)>>( - [&](auto... Is) { (print_one(Is), ...); }); - absl::Format(out, "}"); - } -}; - struct DurationPrinter { void PrintUserValue(const absl::Duration duration, domain_implementor::RawSink out, @@ -571,6 +543,92 @@ } }; +// 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 <typename T, typename = void> +struct HasCustomSourceCodePrinter : std::false_type {}; + +template <typename T> +struct HasCustomSourceCodePrinter< + T, std::enable_if_t<std::is_void<decltype(FuzzTestPrintSourceCode( + std::declval<RawSinkWrapper&>(), std::declval<const T&>()))>::value>> + : std::true_type {}; + +template <typename T> +inline constexpr bool has_custom_source_code_printer_v = + HasCustomSourceCodePrinter<T>::value; + +template <typename T> +inline constexpr bool has_custom_printer_v = + has_absl_stringify_v<T> || has_custom_source_code_printer_v<T>; + +struct AutodetectAggregatePrinter { + template <typename T> + 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<T>) { + 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<T>) { + 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<std::tuple_element_t<I, decltype(bound)>>>() + .PrintUserValue(std::get<I>(bound), out, mode); + }; + absl::Format(out, "%s{", GetTypeNameIfUserDefined<T>()); + ApplyIndex<std::tuple_size_v<decltype(bound)>>( + [&](auto... Is) { (print_one(Is), ...); }); + absl::Format(out, "}"); + } +}; + +template <typename T, typename = std::enable_if_t<has_custom_printer_v<T>>> +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<T>) { + 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<T>) { + FuzzTestPrintSourceCode(sink, v); + } else { + absl::Format(out, "%v", v); + } + } + } +}; + struct UnknownPrinter { template <typename T> void PrintUserValue(const T& v, domain_implementor::RawSink out, @@ -592,31 +650,13 @@ } }; -template <typename T, typename = void> -struct HasCustomSourceCodePrinter : std::false_type {}; - template <typename T> -struct HasCustomSourceCodePrinter< - T, std::enable_if_t<std::is_void<decltype(FuzzTestPrintSourceCode( - std::declval<const T&>(), std::declval<std::ostream*>()))>::value>> - : std::true_type {}; - -template <typename T> -inline constexpr bool has_custom_source_code_printer_v = - HasCustomSourceCodePrinter<T>::value; - -struct CustomPrinter { - template <typename T> - void PrintUserValue(const T& v, domain_implementor::RawSink out, - domain_implementor::PrintMode mode); -}; - -template <typename T, bool kAllowCustomSourcePrinter> decltype(auto) AutodetectTypePrinter() { - if constexpr (kAllowCustomSourcePrinter && - has_custom_source_code_printer_v<T>) { - return CustomPrinter{}; - } else if constexpr (is_protocol_buffer_enum_v<T>) { + // 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<T>) { return ProtobufEnumPrinter<const google::protobuf::EnumDescriptor*>{ google::protobuf::GetEnumDescriptor<T>()}; } else if constexpr (std::numeric_limits<T>::is_integer || @@ -632,43 +672,23 @@ return MonostatePrinter{}; } else if constexpr (is_protocol_buffer_v<T>) { return ProtobufPrinter{}; - } else if constexpr (is_bindable_aggregate_v<T>) { - return AutodetectAggregatePrinter{}; } else if constexpr (std::is_same_v<T, absl::Duration>) { return DurationPrinter{}; } else if constexpr (std::is_same_v<T, absl::Time>) { return TimePrinter{}; + } else if constexpr (is_bindable_aggregate_v<T>) { + return AutodetectAggregatePrinter{}; + } else if constexpr (has_custom_printer_v<T>) { + return CustomPrinter<T>{}; } else { return UnknownPrinter{}; } } template <typename T> -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<T, /*kAllowCustomSourcePrinter=*/false>(); - printer.PrintUserValue(v, out, mode); - } -} - -template <typename T> -constexpr bool HasKnownPrinter(domain_implementor::PrintMode mode) { - if (mode == domain_implementor::PrintMode::kSourceCode) { - return !std::is_convertible_v< - decltype(AutodetectTypePrinter<T, - /*kAllowCustomSourcePrinter=*/true>()), - UnknownPrinter>; - } - return !std::is_convertible_v< - decltype(AutodetectTypePrinter<T, /*kAllowCustomSourcePrinter=*/false>()), - UnknownPrinter>; +constexpr bool HasKnownPrinter() { + return !std::is_convertible_v<decltype(AutodetectTypePrinter<T>()), + UnknownPrinter>; } } // namespace fuzztest::internal
diff --git a/fuzztest/internal/type_support_test.cc b/fuzztest/internal/type_support_test.cc index bdd5587..56b2f07 100644 --- a/fuzztest/internal/type_support_test.cc +++ b/fuzztest/internal/type_support_test.cc
@@ -488,35 +488,57 @@ EXPECT_THAT(TestPrintValue(UserDefinedEmpty{}), Each("UserDefinedEmpty{}")); } -struct AggregateStructWithNoAbslStringify { +struct AggregateWithoutCustomPrinter { int i = 1; std::pair<std::string, std::string> nested = {"Foo", "Bar"}; }; -struct AggregateStructWithAbslStringify { +struct AggregateWithAbslStringify { int i = 1; std::pair<std::string, std::string> nested = {"Foo", "Bar"}; template <typename Sink> - 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<std::string, std::string> nested = {"Foo", "Bar"}; + + static AggregateWithCustomSourceCodePrinter Make(int i, std::string fst, + std::string snd) { + return AggregateWithCustomSourceCodePrinter{ + i, {std::move(fst), std::move(snd)}}; + } + + template <typename Sink> + 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 +575,104 @@ "absl::UnixEpoch() + absl::Seconds(-1290000)")); } -struct NonAggregateStructWithNoAbslStringify { - NonAggregateStructWithNoAbslStringify() : i(1), nested("Foo", "Bar") {} - int i; - std::pair<std::string, std::string> nested; +class ClassWithoutCustomPrinter { + private: + // Needs a private member so that it isn't monostate or a bindable aggregate. + [[maybe_unused]] int a_ = 0; }; -struct NonAggregateStructWithAbslStringify { - NonAggregateStructWithAbslStringify() : i(1), nested("Foo", "Bar") {} - int i; - std::pair<std::string, std::string> nested; +static_assert(!is_monostate_v<ClassWithoutCustomPrinter>); +static_assert(!is_bindable_aggregate_v<ClassWithoutCustomPrinter>); + +class ClassWithAbslStringify { + public: + ClassWithAbslStringify(int a, std::string b) : a_{a}, b_{std::move(b)} {} template <typename Sink> - 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{}), +class ClassWithCustomSourceCodePrinter { + public: + static ClassWithCustomSourceCodePrinter Make(int a, std::string b) { + return ClassWithCustomSourceCodePrinter{a, std::move(b)}; + } + + template <typename Sink> + 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 <typename Sink> + friend void AbslStringify( + Sink& sink, const ClassWithAbslStringifyAndCustomSourceCodePrinter& v) { + absl::Format(&sink, "value={a=%d, b=\"%s\"}", v.a_, v.b_); + } + + template <typename Sink> + 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<ClassWithAbslStringify>); + static_assert(has_custom_printer_v<ClassWithCustomSourceCodePrinter>); + static_assert( + has_custom_printer_v<ClassWithAbslStringifyAndCustomSourceCodePrinter>); + static_assert(!has_custom_printer_v<ClassWithoutCustomPrinter>); +} + +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( + ClassWithAbslStringifyAndCustomSourceCodePrinter::Make(1, "foo")), + ElementsAre("value={a=1, b=\"foo\"}", + "ClassWithAbslStringifyAndCustomSourceCodePrinter::Make(1, " + "\"foo\")")); +} + +TEST(UnprintableTest, PrintsUnprintableValues) { + EXPECT_THAT(TestPrintValue(ClassWithoutCustomPrinter{}), Each("<unprintable value>")); - EXPECT_THAT(TestPrintValue(NonAggregateStructWithAbslStringify{}), - ElementsAre("value={1, {Foo, Bar}}", "<unprintable value>")); - EXPECT_THAT( - TestPrintValue(std::vector<NonAggregateStructWithNoAbslStringify>{}), - Each("<unprintable value>")); - EXPECT_THAT( - TestPrintValue(std::vector<NonAggregateStructWithAbslStringify>{}), - Each("<unprintable value>")); } } // namespace