Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 35 additions & 16 deletions doc/domains-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 <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.
1 change: 0 additions & 1 deletion e2e_tests/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
12 changes: 6 additions & 6 deletions e2e_tests/functional_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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) {
Expand Down
51 changes: 14 additions & 37 deletions e2e_tests/testdata/fuzz_tests_for_functional_testing.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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 <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();
Expand Down
1 change: 1 addition & 0 deletions fuzztest/internal/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions fuzztest/internal/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,7 @@ fuzztest_cc_library(
DEPS
fuzztest::meta
fuzztest::printer
absl::nullability
absl::symbolize
absl::function_ref
absl::int128
Expand Down
Loading
Loading