| # Test Fixtures | |
| If your fuzz test requires setup/teardown logic that is too expensive to be | |
| embedded in your [property function](fuzz-test-macro.md#the-property-function) | |
| and executed on each fuzz test iteration, you can use a | |
| test fixture. | |
| In FuzzTest, any default-constructible class can serve as a test fixture. | |
| To instantiate it, use the macro | |
| FUZZ_TEST_F, | |
| which corresponds to [`FUZZ_TEST`](fuzz-test-macro.md) in the same way that | |
| `TEST_F` corresponds to `TEST` in GoogleTest. Furthermore, you can easily adapt | |
| and reuse the existing | |
| [GoogleTest fixtures](https://google.github.io/googletest/primer.html#same-data-multiple-tests) | |
| in your fuzz tests. | |
| [TOC] | |
| ## When to use fixtures in fuzz tests | |
| Most best practices for writing unit tests | |
| (https://abseil.io/tips/122) | |
| apply equally well when writing fuzz tests. In view of that, you should | |
| generally avoid fixtures and prefer standalone helper functions for your fuzz | |
| test's initialization. However, in some situations test fixtures are | |
| appropriate. | |
| 1. If your fuzz test requires expensive setup, e.g., initializing a server, | |
| doing it in the property function may be prohibitively expensive. The | |
| fuzzing engine calls the property function once per fuzz test iteration, and | |
| running the setup each time may slow down fuzzing to a point where it | |
| becomes ineffective. | |
| 2. If you want to add a fuzz test to an existing code base that uses test | |
| fixtures for unit tests, refactoring the code to eliminate fixtures may be | |
| impractical or even impossible. | |
| ## Using a fixture for expensive setup | |
| Suppose we wanted to test that a certain HTTP server called EchoServer echoes | |
| back the string it receives. We could do this with a fuzz test that doesn't use | |
| fixtures. | |
| ```c++ | |
| // A standalone property function that tests the property of interest. | |
| void ReturnsTheSameString(const std::string& request) { | |
| // Initialize the server. | |
| EchoServer server; | |
| server.Start("localhost:9999"); | |
| // Test the property. | |
| std::string response; | |
| SendRequest("localhost:9999", request, &response); | |
| EXPECT_EQ(response, request); | |
| // Tear down the server. | |
| server.Stop(); | |
| } | |
| // Instantiate the fuzz test using the FUZZ_TEST macro with a chosen suite name | |
| // and the property function as the test name. | |
| FUZZ_TEST(EchoServerFuzzTest, ReturnsTheSameString) | |
| .WithDomains(fuzztest::Arbitrary<std::string>()) | |
| .WithSeeds({"Hello"}); | |
| ``` | |
| The issue with this fuzz test is that the server initialization and teardown | |
| happen in each fuzz test iteration, that is, in each call to the property | |
| function. If starting and stopping the server takes non-trivial time, the fuzz | |
| test will be slow and ineffective. | |
| To improve the test, we would like to do the initialization and teardown once | |
| and reuse the same server across all fuzz test iterations. We can do this using | |
| a test fixture: | |
| ```c++ | |
| // In FuzzTest, any default-constructible class can be a test fixture. | |
| class EchoServerFuzzTest { | |
| public: | |
| // The constructor initializes the fixture. | |
| EchoServerFuzzTest() { server_.Start("localhost:9999"); } | |
| // The destructor tears down the fixture. | |
| ~EchoServerFuzzTest() { server_.Stop(); } | |
| // The fuzz test's property function must be a public member of the fixture. | |
| void ReturnsTheSameString(const std::string& request) { | |
| std::string response; | |
| SendRequest("localhost:9999", request, &response); | |
| EXPECT_EQ(response, request); | |
| } | |
| private: | |
| EchoServer server_; | |
| }; | |
| // Instantiate the fuzz test using the FUZZ_TEST_F macro, with the fixture as | |
| // the suite name and the property function as the test name. | |
| FUZZ_TEST_F(EchoServerFuzzTest, ReturnsTheSameString) | |
| // The macro supports the FUZZ_TEST clauses for specifying domains and seeds. | |
| .WithDomains(fuzztest::Arbitrary<std::string>()) | |
| .WithSeeds({"Hello"}); | |
| ``` | |
| FuzzTest will instantiate the fixture once and call the property function | |
| on the same object in each fuzz test iteration. | |
| ### Note on mutable test fixtures | |
| While persisting state across fuzz test iterations can be effective, it can also | |
| have unwanted consequences. Consider the following example. | |
| ```c++ | |
| class MyFuzzTest { | |
| public: | |
| void MyProperty(int n) { | |
| MyApi(n, flag_); | |
| flag_ = !flag_; | |
| } | |
| private: | |
| bool flag_ = false; | |
| }; | |
| FUZZ_TEST_F(MyFuzzTest, MyProperty); | |
| ``` | |
| By mutating `flag_` and using it in the call to `MyApi()`, the test no longer | |
| depends only on the input generated by the fuzzer. This can potentially break | |
| the fuzzer's assumptions about how the inputs affect coverage and make fuzzing | |
| ineffective. Additionally, a crashing input found by the fuzzer may not be | |
| sufficient for reproducing the crash, since the crash also depends on a | |
| particular value of `flag_`. | |
| BEST PRACTICE: Make sure that the property function resets the mutated fixture | |
| so that each fuzz test iteration starts in the same state. | |
| ## Using existing GoogleTest fixtures in fuzz tests | |
| Note: Only use GoogleTest fixtures if they are shared with existing unit tests, | |
| or if you need static per-fixture setup and teardown (via `SetUpTestSuite` and | |
| `TearDownTestSuite`). Otherwise, prefer fixtureless code with | |
| [standalone initialization functions](https://abseil.io/tips/122). | |
| If you already use a GoogleTest fixture derived from `::testing::Test` in your | |
| unit tests and you want to reuse it in fuzz tests, you first need to choose when | |
| you want the fixture to be instantiated and destroyed. GoogleTest fixtures are | |
| typically built around a guarantee that the same fixture object will never be | |
| reused for multiple tests or multiple runs of the same test. In a fuzz test, | |
| this guarantee requires instantiating the fixture *once per fuzz test | |
| iteration*, i.e., calling the property function each time on a fresh fixture | |
| object. However, for increased performance, it is possible to instantiate a | |
| fixture *once per fuzz test* (if the fixture supports such persistence) and | |
| reuse the same fixture object in all calls to the property function. As noted | |
| earlier, this comes with the risk of state mutation, which can lead to | |
| non-reproducible crashes, so design your fixture carefully. | |
| ### Semantics 1: Fixture instantiated once per fuzz test iteration | |
| Adapt a fixture called `Fixture` by extending | |
| `::fuzztest::PerIterationFixtureAdapter<Fixture>`. This case should be the more | |
| common one, since it observes the GoogleTest invariant of not using a fixture | |
| object more than once. | |
| As an example, suppose we have a function `SumVec()` that returns the sum of a | |
| vector of integers and we have the following fixture. | |
| ```c++ | |
| class SumVecTest : public testing::Test { | |
| public: | |
| SumVecTest() : vec_{1, 2, 3} {} | |
| protected: | |
| std::vector<int> vec_; | |
| }; | |
| ``` | |
| A fuzz test that uses the fixture would look like the following: | |
| ```c++ | |
| // Adapts the fixture using the "per-iteration" semantics. | |
| class SumVecFuzzTest : public fuzztest::PerIterationFixtureAdapter<SumVecTest> { | |
| public: | |
| void SumsLastEntry(int last_entry) { | |
| int previous_sum = SumVec(vec_); | |
| vec_.push_back(last_entry); | |
| EXPECT_EQ(SumVec(vec_), previous_sum + last_entry); | |
| } | |
| }; | |
| FUZZ_TEST_F(SumVecFuzzTest, SumsLastEntry); | |
| ``` | |
| The "per-iteration" semantics is appropriate for this fixture because the test | |
| mutates `vec_`. | |
| ### Semantics 2: Fixture instantiated once per fuzz test | |
| Adapt a fixture called `Fixture` by extending | |
| `::fuzztest::PerFuzzTestFixtureAdapter<Fixture>`. This case should be the less | |
| common one: it relies on the fixture being easily resettable, which is not a | |
| typical concern when designing GoogleTest fixtures. | |
| As an example, let's return to EchoServer. Suppose we already have a GoogleTest | |
| fixture that sets up the server. | |
| ```c++ | |
| class EchoServerTest : public testing::Test { | |
| public: | |
| EchoServerTest() { server_.Start("localhost:9999"); } | |
| ~EchoServerTest() override { server_.Stop(); } | |
| private: | |
| EchoServer server_; | |
| }; | |
| ``` | |
| We would write a test using this fixture as in the following code: | |
| ```c++ | |
| // Adapts the fixture using the "per-fuzz-test" semantics. | |
| class EchoServerFuzzTest | |
| : public fuzztest::PerFuzzTestFixtureAdapter<EchoServerTest> { | |
| public: | |
| void ReturnsTheSameString(const std::string& request) { | |
| std::string response; | |
| SendRequest("localhost:9999", request, &response); | |
| EXPECT_EQ(response, request); | |
| } | |
| }; | |
| FUZZ_TEST_F(EchoServerFuzzTest, ReturnsTheSameString); | |
| ``` | |
| Here, the "per-fuzz-test" semantics is appropriate because the test doesn't | |
| mutate the server in any way that would be relevant for the test, and the server | |
| initialization is too expensive to be performed in each iteration. | |
| ## Advanced fixtures with runner functions | |
| When using non-GoogleTest fixtures, FuzzTest can make use of *runner functions* | |
| on the fixture to specify per-test or per-iteration setups in a more flexible | |
| way. FuzzTest provides the following runner interfaces for a fixture to inherit: | |
| - `::fuzztest::FuzzTestRunnerFixture`, which declares the runner function | |
| `void FuzzTestRunner(absl::AnyInvocable<void() &&> run_test)` | |
| If the interface is inherited, instead of directly running the fuzz test, | |
| FuzzTest will call `FuzzTestRunner`, expecting that it will in turn: | |
| 1. Set up everything needed for running the test. | |
| 2. Invoke `run_test`, through which FuzzTest will essentially pass the | |
| whole fuzzing loop with multiple invocations to the property function. | |
| The function `run_test` will return once FuzzTest is done with the test. | |
| 3. Tear down everything no longer needed after the test. | |
| - `::fuzztest::IterationRunnerFixture`, which declares the runner function | |
| `void FuzzTestIterationRunner(absl::AnyInvocable<void() &&> run_iteration)` | |
| If the interface is inherited, instead of directly running a fuzz test | |
| iteration, FuzzTest will call `FuzzTestIterationRunner`, expecting that it | |
| will in turn: | |
| 1. Set up everything needed for running a test iteration. | |
| 2. Invoke `run_iteration`, through which FuzzTest will invoke the property | |
| function once. The function `run_iteration` will return once the | |
| iteration is done. | |
| 3. Tear down everything no longer needed after the test iteration. | |
| Note that the type `absl::AnyInvocable<void() &&>` ensures that the functions | |
| `run_test` and `run_iteration` can be called only once. To call them, you must | |
| first move them, for example `std::move(run_test)()`. Attempting to call these | |
| functions more than once will result in a runtime error. | |
| The traditional SetUp/TearDown functions of a fixture can be trivially | |
| transformed into a runner function. For example, the `EchoServerFuzzTest` | |
| fixture could be defined as: | |
| ```c++ | |
| class EchoServerFuzzTest : public fuzztest::FuzzTestRunnerFixture { | |
| public: | |
| void FuzzTestRunner(absl::AnyInvocable<void() &&> run_test) override { | |
| server_.Start("localhost:9999"); | |
| std::move(run_test)(); | |
| server_.Stop(); | |
| } | |
| // The fuzz test's property function | |
| void ReturnsTheSameString(const std::string& request) { | |
| std::string response; | |
| SendRequest("localhost:9999", request, &response); | |
| EXPECT_EQ(response, request); | |
| } | |
| private: | |
| EchoServer server_; | |
| }; | |
| FUZZ_TEST_F(EchoServerFuzzTest, ReturnsTheSameString); | |
| ``` | |
| To create a new `EchoServer` instance for each fuzz test iteration, define | |
| `FuzzTestIterationRunner` instead: | |
| ```c++ | |
| class EchoServerFuzzTest : public fuzztest::IterationRunnerFixture { | |
| public: | |
| void FuzzTestIterationRunner(absl::AnyInvocable<void() &&> run_iteration) override { | |
| server_.Start("localhost:9999"); | |
| std::move(run_iteration)(); | |
| server_.Stop(); | |
| } | |
| ... | |
| ``` | |
| Although converting SetUp/TearDown to runner functions is trivial, the other way | |
| around may not even be possible. For example, suppose there is a fuzz test whose | |
| property function must run within an SUT created by an SUT method | |
| `RunWithinSut(sut_ready_cb)`, which starts the SUT and calls the callback when | |
| the SUT is ready, and shuts down the SUT when the callback returns. Suppose | |
| additionally that `RunWithinSut` must be run in the main thread of the test, | |
| which is also the thread where FuzzTest runs the test fixture. Given these | |
| constraints, it is impossible to run such test with a SetUp/TearDown-style | |
| fixture without calling `RunWithinSut` for every iteration, which may be too | |
| heavy and impact the fuzzing performance. With runner functions, the fixture can | |
| be defined as: | |
| ```c++ | |
| class SutBasedFuzzTest : public fuzztest::FuzzTestRunnerFixture { | |
| public: | |
| void FuzzTestRunner(absl::AnyInvocable<void() &&> run_test) { | |
| sut_.RunWithinSut([&] { std::move(run_test)(); });`. | |
| } | |
| private: | |
| SUT sut_; | |
| } | |
| ``` |