Skip to content

Commit cbffde6

Browse files
fniksiccopybara-github
authored andcommitted
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: 868817106
1 parent fb29fbd commit cbffde6

3 files changed

Lines changed: 176 additions & 248 deletions

File tree

doc/domains-reference.md

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -894,10 +894,11 @@ support seeds.
894894
## Customizing Value Printers
895895
896896
FuzzTest provides a mechanism to display the values that cause a test to fail.
897-
By default, it knows how to print standard C++ types, but you can extend this
898-
system to support your own custom types. This is especially useful for making
899-
test failure reports clear and actionable. There are two ways FuzzTest prints
900-
values, and you can customize the output for each:
897+
By default, it knows how to print standard C++ types, including aggregate types
898+
like tuples and simple structs, but you can extend this system to support your
899+
own custom types. This is especially useful for making test failure reports
900+
clear and actionable. There are two ways FuzzTest prints values, and you can
901+
customize the output for each:
901902
902903
- Human-readable mode: This mode is designed to be easily read and understood
903904
by a developer. The goal is clarity, not necessarily compilable code.
@@ -907,9 +908,22 @@ values, and you can customize the output for each:
907908
908909
### Customizing the human-readable printer
909910
910-
The simplest and most recommended way to implement custom printing for your
911-
types is to implement `AbslStringify`. This hooks into Abseil's string
912-
formatting library, which FuzzTest uses internally. For example:
911+
For aggregate types (like simple structs), FuzzTest already provides a default
912+
printer that performs field-level printing, but without field names. For
913+
example, if you have:
914+
915+
```c++
916+
struct MyObject {
917+
int id;
918+
std::string name;
919+
};
920+
```
921+
922+
The default output for `MyObject{1, "Alice"}` will be `MyObject{1, "Alice"}`.
923+
924+
To customize the output, the simplest and most recommended way is to implement
925+
`AbslStringify`. This hooks into Abseil's string formatting library, which
926+
FuzzTest uses internally. For example:
913927

914928
```c++
915929
struct MyObject {
@@ -918,11 +932,18 @@ struct MyObject {
918932

919933
template <typename Sink>
920934
friend void AbslStringify(Sink& sink, const MyObject& obj) {
921-
absl::Format(&sink, "MyObject[%d](name=\"%s\")", obj.id, obj.name);
935+
absl::Format(&sink, "MyObject{.id = %d, .name = \"%s\"}", obj.id, obj.name);
922936
}
923937
};
924938
```
925939
940+
Now the output for `MyObject{1, "Alice"}` will be
941+
`MyObject{.id = 1, .name = "Alice"}`.
942+
943+
WARNING: If your aggregate type contains a nested C-style array, the default
944+
field-type printing may yield a compile-time error. In this case, you should
945+
define a custom printer as a workaround.
946+
926947
### Customizing the source code printer
927948
928949
To provide a custom source code representation (which is used in regression
@@ -953,9 +974,6 @@ If you define only one of `AbslStringify` and `FuzzTestPrintSourceCode`,
953974
FuzzTest will use the defined function for both the human-readable and the
954975
source-code mode. If you define both, FuzzTest will use `AbslStringify` for the
955976
human-readable mode, and `FuzzTestPrintSourceCode` for the source-code mode.
956-
Note that this fallback behavior primarily applies to non-aggregate types. For
957-
aggregate types (like simple structs), FuzzTest prefers field-level printing for
958-
the mode that doesn't have a custom extension point.
959977

960978
NOTE: FuzzTest does not validate the output. You are responsible for ensuring
961979
that in the source-code mode, the function prints a valid C++ expression that

fuzztest/internal/type_support.h

Lines changed: 63 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,60 @@ struct StringPrinter {
221221
}
222222
};
223223

224+
// A wrapper around `RawSink` that can be passed to the user's
225+
// `FuzzTestPrintSourceCode` function, a FTADLE extension point for custom
226+
// printers. This is so that the function can look more like `AbslStringify`,
227+
// which is parameterized by the sink type and passes a pointer to the sink to
228+
// `absl::Format()`.
229+
struct RawSinkWrapper {
230+
domain_implementor::RawSink& raw_sink;
231+
232+
friend void AbslFormatFlush(RawSinkWrapper* absl_nonnull sink,
233+
absl::string_view part) {
234+
absl::Format(sink->raw_sink, "%s", part);
235+
}
236+
};
237+
238+
template <typename T, typename = void>
239+
struct HasCustomSourceCodePrinter : std::false_type {};
240+
241+
template <typename T>
242+
struct HasCustomSourceCodePrinter<
243+
T, std::enable_if_t<std::is_void<decltype(FuzzTestPrintSourceCode(
244+
std::declval<RawSinkWrapper&>(), std::declval<const T&>()))>::value>>
245+
: std::true_type {};
246+
247+
template <typename T>
248+
inline constexpr bool has_custom_source_code_printer_v =
249+
HasCustomSourceCodePrinter<T>::value;
250+
251+
template <typename T>
252+
inline constexpr bool has_custom_printer_v =
253+
has_absl_stringify_v<T> || has_custom_source_code_printer_v<T>;
254+
255+
template <typename T, typename = std::enable_if_t<has_custom_printer_v<T>>>
256+
struct CustomPrinter {
257+
void PrintUserValue(const T& v, domain_implementor::RawSink out,
258+
domain_implementor::PrintMode mode) {
259+
RawSinkWrapper sink{out};
260+
if (mode == domain_implementor::PrintMode::kHumanReadable) {
261+
// Prefer AbslStringify, fall back on source code printer.
262+
if constexpr (has_absl_stringify_v<T>) {
263+
absl::Format(out, "%v", v);
264+
} else {
265+
FuzzTestPrintSourceCode(sink, v);
266+
}
267+
} else {
268+
// Prefer source code printer, fall back on AbslStringify.
269+
if constexpr (has_custom_source_code_printer_v<T>) {
270+
FuzzTestPrintSourceCode(sink, v);
271+
} else {
272+
absl::Format(out, "%v", v);
273+
}
274+
}
275+
}
276+
};
277+
224278
template <typename DomainT, typename... Inner>
225279
struct AggregatePrinter {
226280
const DomainT& domain;
@@ -230,12 +284,11 @@ struct AggregatePrinter {
230284
void PrintCorpusValue(const corpus_type_t<DomainT>& v,
231285
domain_implementor::RawSink out,
232286
domain_implementor::PrintMode mode) const {
233-
if (mode == domain_implementor::PrintMode::kHumanReadable) {
234-
// In human-readable mode, prefer formatting with Abseil if possible.
235-
if constexpr (has_absl_stringify_v<value_type_t<DomainT>>) {
236-
absl::Format(out, "%v", domain.GetValue(v));
237-
return;
238-
}
287+
if constexpr (has_custom_printer_v<value_type_t<DomainT>>) {
288+
// Prefer the custom printer if there is one.
289+
CustomPrinter<value_type_t<DomainT>>{}.PrintUserValue(domain.GetValue(v),
290+
out, mode);
291+
return;
239292
}
240293

241294
absl::Format(out, "%s", type_name);
@@ -543,55 +596,10 @@ struct TimePrinter {
543596
}
544597
};
545598

546-
// A wrapper around `RawSink` that can be passed to the user's
547-
// `FuzzTestPrintSourceCode` function, a FTADLE extension point for custom
548-
// printers. This is so that the function can look more like `AbslStringify`,
549-
// which is parameterized by the sink type and passes a pointer to the sink to
550-
// `absl::Format()`.
551-
struct RawSinkWrapper {
552-
domain_implementor::RawSink& raw_sink;
553-
554-
friend void AbslFormatFlush(RawSinkWrapper* absl_nonnull sink,
555-
absl::string_view part) {
556-
absl::Format(sink->raw_sink, "%s", part);
557-
}
558-
};
559-
560-
template <typename T, typename = void>
561-
struct HasCustomSourceCodePrinter : std::false_type {};
562-
563-
template <typename T>
564-
struct HasCustomSourceCodePrinter<
565-
T, std::enable_if_t<std::is_void<decltype(FuzzTestPrintSourceCode(
566-
std::declval<RawSinkWrapper&>(), std::declval<const T&>()))>::value>>
567-
: std::true_type {};
568-
569-
template <typename T>
570-
inline constexpr bool has_custom_source_code_printer_v =
571-
HasCustomSourceCodePrinter<T>::value;
572-
573-
template <typename T>
574-
inline constexpr bool has_custom_printer_v =
575-
has_absl_stringify_v<T> || has_custom_source_code_printer_v<T>;
576-
577599
struct AutodetectAggregatePrinter {
578600
template <typename T>
579601
void PrintUserValue(const T& v, domain_implementor::RawSink out,
580602
domain_implementor::PrintMode mode) {
581-
if (mode == domain_implementor::PrintMode::kHumanReadable) {
582-
// In human-readable mode, prefer formatting with Abseil if possible.
583-
if constexpr (has_absl_stringify_v<T>) {
584-
absl::Format(out, "%v", v);
585-
return;
586-
}
587-
} else {
588-
// In source-code mode, prefer custom source-code printer if possible.
589-
if constexpr (has_custom_source_code_printer_v<T>) {
590-
RawSinkWrapper sink{out};
591-
FuzzTestPrintSourceCode(sink, v);
592-
return;
593-
}
594-
}
595603
std::tuple bound = DetectBindAggregate(v);
596604
const auto print_one = [&](auto I) {
597605
if (I > 0) absl::Format(out, ", ");
@@ -606,40 +614,11 @@ struct AutodetectAggregatePrinter {
606614
}
607615
};
608616

609-
template <typename T, typename = std::enable_if_t<has_custom_printer_v<T>>>
610-
struct CustomPrinter {
611-
void PrintUserValue(const T& v, domain_implementor::RawSink out,
612-
domain_implementor::PrintMode mode) {
613-
RawSinkWrapper sink{out};
614-
if (mode == domain_implementor::PrintMode::kHumanReadable) {
615-
// Prefer AbslStringify, fall back on source code printer.
616-
if constexpr (has_absl_stringify_v<T>) {
617-
absl::Format(out, "%v", v);
618-
} else {
619-
FuzzTestPrintSourceCode(sink, v);
620-
}
621-
} else {
622-
// Prefer source code printer, fall back on AbslStringify.
623-
if constexpr (has_custom_source_code_printer_v<T>) {
624-
FuzzTestPrintSourceCode(sink, v);
625-
} else {
626-
absl::Format(out, "%v", v);
627-
}
628-
}
629-
}
630-
};
631-
632617
struct UnknownPrinter {
633618
template <typename T>
634619
void PrintUserValue(const T& v, domain_implementor::RawSink out,
635620
domain_implementor::PrintMode mode) {
636621
if (mode == domain_implementor::PrintMode::kHumanReadable) {
637-
// Try formatting with Abseil. We can't guarantee a good source code
638-
// result, but it should be ok for human readable.
639-
if constexpr (has_absl_stringify_v<T>) {
640-
absl::Format(out, "%v", v);
641-
return;
642-
}
643622
// Some standard types have operator<<.
644623
if constexpr (std::is_scalar_v<T> || is_std_complex_v<T>) {
645624
absl::Format(out, "%s", absl::FormatStreamed(v));
@@ -654,8 +633,8 @@ template <typename T>
654633
decltype(auto) AutodetectTypePrinter() {
655634
// The order of these checks somewhat matters. Most of the concrete types have
656635
// AbslStringify, so they should come first not to be captured by the custom
657-
// printer case. The aggregate case is also more specific than the custom
658-
// printer case, so it needs to come before.
636+
// printer case. The aggregate case comes after the custom case so that the
637+
// user can override the default aggregate printer.
659638
if constexpr (is_protocol_buffer_enum_v<T>) {
660639
return ProtobufEnumPrinter<const google::protobuf::EnumDescriptor*>{
661640
google::protobuf::GetEnumDescriptor<T>()};
@@ -676,10 +655,10 @@ decltype(auto) AutodetectTypePrinter() {
676655
return DurationPrinter{};
677656
} else if constexpr (std::is_same_v<T, absl::Time>) {
678657
return TimePrinter{};
679-
} else if constexpr (is_bindable_aggregate_v<T>) {
680-
return AutodetectAggregatePrinter{};
681658
} else if constexpr (has_custom_printer_v<T>) {
682659
return CustomPrinter<T>{};
660+
} else if constexpr (is_bindable_aggregate_v<T>) {
661+
return AutodetectAggregatePrinter{};
683662
} else {
684663
return UnknownPrinter{};
685664
}

0 commit comments

Comments
 (0)