Prioritize custom printers over default aggregate printing. This change makes `AbslStringify` and `FuzzTestPrintSourceCode` take precedence over the default field-level printing for aggregate types. If a custom printer is defined for a type, it will be used for both human-readable and source-code modes, with fallback between `AbslStringify` and `FuzzTestPrintSourceCode`. The documentation is updated to reflect this behavior. This reduces the amount of boilerplate that the user has to write to override the default field-level printing: with the change, it suffices to define `AbslStringify`. This is important for aggregate types that have a nested C-style array, for which the aggregate detection yields a compile-time error. PiperOrigin-RevId: 869769739
diff --git a/doc/domains-reference.md b/doc/domains-reference.md index a656179..4db6565 100644 --- a/doc/domains-reference.md +++ b/doc/domains-reference.md
@@ -53,7 +53,11 @@ User defined structs must support [aggregate initialization](https://en.cppreference.com/w/cpp/language/aggregate_initialization), -must have only public members and no more than 64 fields. +must have only public members, and no more than 80 fields. In addition, they +cannot have C-style array members (e.g., `int[5]`). + +TIP: If your struct doesn't satisfy the requirements for `Arbitrary`, you can +construct a domain for it using `Map`, `ReversibleMap`, or `FlatMap`. Recall that `Arbitrary` is the default input domain, which means that you can fuzz a function like below without a `.WithDomains()` clause: @@ -550,7 +554,7 @@ This may be useful in cases where 0 is silently interpreted as a sentinel value (e.g., "not set"). -### Map +### Map {#map} Often the best way to define a domain is using a mapping function. The `Map()` domain combinator takes a mapping function and an input domain for each of its @@ -573,6 +577,13 @@ seeding the input domains passed to `Map()`. Otherwise, if you need full support for seeds, consider using [`ReversibleMap()`](#reversible-map). +Note: If your return type is an +[aggregate type](https://en.cppreference.com/w/cpp/language/aggregate_initialization) +with a nested C-style array, you may get a compile-time error about a mismatch +between the number of elements the type binds to and the number of names +provided (e.g., `binds to 2 elements, but 3 names were provided`). A workaround +is to define a [custom value printer](#custom-value-printers) for the type. + ### ReversibleMap {#reversible-map} The `ReversibleMap()` domain combinator is similar to `Map()`: it takes a @@ -651,6 +662,9 @@ it `g`) must return `std::nullopt` because there is no possible value it could return so that `f(g(std::pair{a, b})) == std::pair{a, b}`. +Note: The [note for `Map`](#map) about aggregate types with nested C-style +arrays also applies to `ReversibleMap`. + ### FlatMap Sometimes we need to fuzz parameters that are dependent on each other. Think of @@ -701,6 +715,9 @@ [seeded domain](#seeded-domains)—a domain skewed toward certain values—consider seeding the input domains passed to `FlatMap()`. +Note: The [note for `Map`](#map) about aggregate types with nested C-style +arrays also applies to `FlatMap`. + ### Filter The `Filter` domain combinator takes a domain and a predicate and returns a new @@ -891,13 +908,14 @@ constructed using combinators `ConstructorOf`, `Map`, and `FlatMap` don't support seeds. -## Customizing Value Printers +## Customizing Value Printers {#custom-value-printers} FuzzTest provides a mechanism to display the values that cause a test to fail. -By default, it knows how to print standard C++ types, but you can extend this -system to support your own custom types. This is especially useful for making -test failure reports clear and actionable. There are two ways FuzzTest prints -values, and you can customize the output for each: +By default, it knows how to print standard C++ types, including +[aggregate types](https://en.cppreference.com/w/cpp/language/aggregate_initialization), +but you can extend this system to support your own custom types. This is +especially useful for making test failure reports clear and actionable. There +are two ways FuzzTest prints values, and you can customize the output for each: - Human-readable mode: This mode is designed to be easily read and understood by a developer. The goal is clarity, not necessarily compilable code. @@ -907,9 +925,21 @@ ### 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 -formatting library, which FuzzTest uses internally. For example: +For aggregate types, FuzzTest already provides a default printer that performs +field-level printing, but without field names. For example, if you have: + +```c++ +struct MyObject { + int id; + std::string name; +}; +``` + +The default output for `MyObject{1, "Alice"}` will be `MyObject{1, "Alice"}`. + +To customize the output, the simplest and most recommended way is to implement +`AbslStringify`. This hooks into Abseil's string formatting library, which +FuzzTest uses internally. For example: ```c++ struct MyObject { @@ -918,11 +948,19 @@ template <typename Sink> friend void AbslStringify(Sink& sink, const MyObject& obj) { - absl::Format(&sink, "MyObject[%d](name=\"%s\")", obj.id, obj.name); + absl::Format(&sink, "MyObject{.id = %d, .name = \"%s\"}", obj.id, obj.name); } }; ``` +Now the output for `MyObject{1, "Alice"}` will be +`MyObject{.id = 1, .name = "Alice"}`. + +WARNING: If your aggregate type contains a nested C-style array, the default +field-type printing may yield a compile-time error (see the +[note for `Map`](#map)). In this case, you should define a custom printer as +a workaround. + ### Customizing the source code printer To provide a custom source code representation (which is used in regression @@ -953,9 +991,6 @@ 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 that in the source-code mode, the function prints a valid C++ expression that
diff --git a/fuzztest/internal/type_support.h b/fuzztest/internal/type_support.h index cc30636..eb55471 100644 --- a/fuzztest/internal/type_support.h +++ b/fuzztest/internal/type_support.h
@@ -221,6 +221,60 @@ } }; +// 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 CustomPrinter { + template <typename T, typename = std::enable_if_t<has_custom_printer_v<T>>> + 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); + } + } + } +}; + template <typename DomainT, typename... Inner> struct AggregatePrinter { const DomainT& domain; @@ -230,12 +284,10 @@ void PrintCorpusValue(const corpus_type_t<DomainT>& v, domain_implementor::RawSink out, domain_implementor::PrintMode mode) const { - if (mode == domain_implementor::PrintMode::kHumanReadable) { - // In human-readable mode, prefer formatting with Abseil if possible. - if constexpr (has_absl_stringify_v<value_type_t<DomainT>>) { - absl::Format(out, "%v", domain.GetValue(v)); - return; - } + if constexpr (has_custom_printer_v<value_type_t<DomainT>>) { + // Prefer the custom printer if there is one. + CustomPrinter{}.PrintUserValue(domain.GetValue(v), out, mode); + return; } absl::Format(out, "%s", type_name); @@ -543,55 +595,10 @@ } }; -// 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, ", "); @@ -606,40 +613,11 @@ } }; -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, domain_implementor::PrintMode mode) { if (mode == domain_implementor::PrintMode::kHumanReadable) { - // Try formatting with Abseil. We can't guarantee a good source code - // result, but it should be ok for human readable. - if constexpr (has_absl_stringify_v<T>) { - absl::Format(out, "%v", v); - return; - } // Some standard types have operator<<. if constexpr (std::is_scalar_v<T> || is_std_complex_v<T>) { absl::Format(out, "%s", absl::FormatStreamed(v)); @@ -654,8 +632,8 @@ decltype(auto) AutodetectTypePrinter() { // 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. + // printer case. The aggregate case comes after the custom case so that the + // user can override the default aggregate printer. if constexpr (is_protocol_buffer_enum_v<T>) { return ProtobufEnumPrinter<const google::protobuf::EnumDescriptor*>{ google::protobuf::GetEnumDescriptor<T>()}; @@ -676,10 +654,10 @@ return DurationPrinter{}; } else if constexpr (std::is_same_v<T, absl::Time>) { return TimePrinter{}; + } else if constexpr (has_custom_printer_v<T>) { + return CustomPrinter{}; } else if constexpr (is_bindable_aggregate_v<T>) { return AutodetectAggregatePrinter{}; - } else if constexpr (has_custom_printer_v<T>) { - return CustomPrinter<T>{}; } else { return UnknownPrinter{}; }
diff --git a/fuzztest/internal/type_support_test.cc b/fuzztest/internal/type_support_test.cc index 56b2f07..afaa80b 100644 --- a/fuzztest/internal/type_support_test.cc +++ b/fuzztest/internal/type_support_test.cc
@@ -206,15 +206,59 @@ EXPECT_EQ(std::string("\000a\223b\"", 5).size(), 5); } -struct UserDefinedWithAbslStringify { - std::string foo; +struct AggregateWithoutCustomPrinter { + int i; + double d; + std::string s; +}; + +struct AggregateWithAbslStringify { + int i; + double d; + std::string s; template <typename Sink> - friend void AbslStringify(Sink& sink, const UserDefinedWithAbslStringify& v) { - absl::Format(&sink, "{foo=\"%s\"}", v.foo); + friend void AbslStringify(Sink& sink, const AggregateWithAbslStringify& v) { + absl::Format(&sink, "AbslStringify={%d, %.1f, \"%s\"}", v.i, v.d, v.s); } }; +struct AggregateWithCustomSourceCodePrinter { + int i; + double d; + std::string s; + + template <typename Sink> + friend void FuzzTestPrintSourceCode( + Sink& sink, const AggregateWithCustomSourceCodePrinter& v) { + absl::Format(&sink, "CustomSourceCodePrinter={%d, %.1f, \"%s\"}", v.i, v.d, + v.s); + } +}; + +struct AggregateWithBoth { + int i; + double d; + std::string s; + + template <typename Sink> + friend void AbslStringify(Sink& sink, const AggregateWithBoth& v) { + absl::Format(&sink, "AbslStringify={%d, %.1f, \"%s\"}", v.i, v.d, v.s); + } + + template <typename Sink> + friend void FuzzTestPrintSourceCode(Sink& sink, const AggregateWithBoth& v) { + absl::Format(&sink, "CustomSourceCodePrinter={%d, %.1f, \"%s\"}", v.i, v.d, + v.s); + } +}; + +class NonAggregateWithoutCustomPrinter { + private: + // Needs a private member so that it isn't considered monostate. + [[maybe_unused]] int a_ = 0; +}; + TEST(CompoundTest, Printer) { EXPECT_THAT( TestPrintValue(std::pair(1, 1.5), Arbitrary<std::pair<int, double>>()), @@ -222,21 +266,27 @@ EXPECT_THAT(TestPrintValue(std::tuple(2, -3, -0.0), Arbitrary<std::tuple<int, int, double>>()), Each("{2, -3, -0.}")); - - struct UserDefined { - int i; - double d; - std::string s; - }; - EXPECT_THAT(TestPrintValue( - std::tuple{2, -3.5, "Foo"}, - StructOf<UserDefined>(Arbitrary<int>(), Arbitrary<double>(), - Arbitrary<std::string>())), - Each("UserDefined{2, -3.5, \"Foo\"}")); - EXPECT_THAT( - TestPrintValue(std::tuple{"Foo"}, StructOf<UserDefinedWithAbslStringify>( - Arbitrary<std::string>())), - ElementsAre("{foo=\"Foo\"}", "UserDefinedWithAbslStringify{\"Foo\"}")); + EXPECT_THAT(TestPrintValue(std::tuple{2, -3.5, "Foo"}, + StructOf<AggregateWithoutCustomPrinter>( + Arbitrary<int>(), Arbitrary<double>(), + Arbitrary<std::string>())), + Each("AggregateWithoutCustomPrinter{2, -3.5, \"Foo\"}")); + EXPECT_THAT(TestPrintValue(std::tuple{2, -3.5, "Foo"}, + StructOf<AggregateWithAbslStringify>( + Arbitrary<int>(), Arbitrary<double>(), + Arbitrary<std::string>())), + Each("AbslStringify={2, -3.5, \"Foo\"}")); + EXPECT_THAT(TestPrintValue(std::tuple{2, -3.5, "Foo"}, + StructOf<AggregateWithCustomSourceCodePrinter>( + Arbitrary<int>(), Arbitrary<double>(), + Arbitrary<std::string>())), + Each("CustomSourceCodePrinter={2, -3.5, \"Foo\"}")); + EXPECT_THAT(TestPrintValue(std::tuple{2, -3.5, "Foo"}, + StructOf<AggregateWithBoth>( + Arbitrary<int>(), Arbitrary<double>(), + Arbitrary<std::string>())), + ElementsAre("AbslStringify={2, -3.5, \"Foo\"}", + "CustomSourceCodePrinter={2, -3.5, \"Foo\"}")); } TEST(ProtobufTest, Printer) { @@ -488,57 +538,14 @@ EXPECT_THAT(TestPrintValue(UserDefinedEmpty{}), Each("UserDefinedEmpty{}")); } -struct AggregateWithoutCustomPrinter { - int i = 1; - std::pair<std::string, std::string> nested = {"Foo", "Bar"}; -}; - -struct AggregateWithAbslStringify { - int i = 1; - std::pair<std::string, std::string> nested = {"Foo", "Bar"}; - - template <typename Sink> - 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(AggregateWithoutCustomPrinter{}), - Each(R"(AggregateWithoutCustomPrinter{1, {"Foo", "Bar"}})")); - EXPECT_THAT(TestPrintValue(AggregateWithAbslStringify{}), - ElementsAre("value={1, {Foo, Bar}}", - R"(AggregateWithAbslStringify{1, {"Foo", "Bar"}})")); - EXPECT_THAT( - TestPrintValue(AggregateWithCustomSourceCodePrinter{}), - ElementsAre( - R"(AggregateWithCustomSourceCodePrinter{1, {"Foo", "Bar"}})", - R"(AggregateWithCustomSourceCodePrinter::Make(1, "Foo", "Bar"))")); + EXPECT_THAT(TestPrintValue(AggregateWithoutCustomPrinter{2, -3.5, "Foo"}), + Each("AggregateWithoutCustomPrinter{2, -3.5, \"Foo\"}")); } TEST(DurationTest, Printer) { @@ -575,103 +582,27 @@ "absl::UnixEpoch() + absl::Seconds(-1290000)")); } -class ClassWithoutCustomPrinter { - private: - // Needs a private member so that it isn't monostate or a bindable aggregate. - [[maybe_unused]] int a_ = 0; -}; - -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 ClassWithAbslStringify& v) { - absl::Format(&sink, "value={%d, \"%s\"}", v.a_, v.b_); - } - - private: - int a_ = 0; - std::string b_; -}; - -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>); + static_assert(has_custom_printer_v<AggregateWithAbslStringify>); + static_assert(has_custom_printer_v<AggregateWithCustomSourceCodePrinter>); + static_assert(has_custom_printer_v<AggregateWithBoth>); + static_assert(!has_custom_printer_v<NonAggregateWithoutCustomPrinter>); + static_assert(!has_custom_printer_v<AggregateWithoutCustomPrinter>); } 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(AggregateWithAbslStringify{2, -3.5, "Foo"}), + Each("AbslStringify={2, -3.5, \"Foo\"}")); EXPECT_THAT( - TestPrintValue( - ClassWithAbslStringifyAndCustomSourceCodePrinter::Make(1, "foo")), - ElementsAre("value={a=1, b=\"foo\"}", - "ClassWithAbslStringifyAndCustomSourceCodePrinter::Make(1, " - "\"foo\")")); + TestPrintValue(AggregateWithCustomSourceCodePrinter{2, -3.5, "Foo"}), + Each("CustomSourceCodePrinter={2, -3.5, \"Foo\"}")); + EXPECT_THAT(TestPrintValue(AggregateWithBoth{2, -3.5, "Foo"}), + ElementsAre("AbslStringify={2, -3.5, \"Foo\"}", + "CustomSourceCodePrinter={2, -3.5, \"Foo\"}")); } TEST(UnprintableTest, PrintsUnprintableValues) { - EXPECT_THAT(TestPrintValue(ClassWithoutCustomPrinter{}), + EXPECT_THAT(TestPrintValue(NonAggregateWithoutCustomPrinter{}), Each("<unprintable value>")); }