| # FuzzTest Use Cases |
| |
| ## Finding Undefined Behavior with Sanitizers |
| |
| By far the most typical type of fuzz test is the one that checks for undefined |
| behavior. These are easy to write as |
| sanitizers |
| take care of |
| the assertions, we just need to exercise the code under test: |
| |
| ```c++ |
| void UnescapeStringNeverCrashes(const std::string& s) { |
| UnescapeString(s); |
| } |
| FUZZ_TEST(TagUtilsFuzzTests, UnescapeStringNeverCrashes); |
| ``` |
| |
| These tests can find serious security vulnerabilities and other robustness bugs, |
| such as |
| |
| - buffer overflows, |
| - use-after-frees, |
| - uninitialized memory, |
| - memory leaks, |
| - division by zero, |
| - integer overflows, |
| - invalid casts, |
| - nullptr dereferences, |
| - data races, |
| - hangs, |
| - infinite recursion, |
| - memory exhaustion bugs, |
| - algorithmic complexity vulnerabilities, |
| - etc. |
| |
| By default, sanitizers are enabled in fuzzing mode. Whether or not the code |
| under test has any explicit assertions in it (e.g., `CHECK`/`DCHECK`), the |
| implicit undefined behavior checks of the sanitizer will always catch these |
| bugs. |
| |
| ## Find Correctness Bugs using Explicit Assertions |
| |
| The other main type of fuzz tests is when we check for some correctness |
| properties as well (on top of undefined behavior). We can do this by either |
| adding assertions to code under test or to the test itself. The more assertions |
| you have in your implementation, `CHECK`-ing invariants, pre- and |
| post-conditions, the more bugs you can catch. You can also assert some higher |
| level properties in the test itself, in your property function. There are a few |
| common patterns for such correctness properties that you can use. |
| |
| ### API Invariants |
| |
| Often the API you’re testing has some correctness invariant. This test, for |
| example asserts that workflow IDs are always unique, i.e., no two IDs are ever |
| the same: |
| |
| ```c++ |
| void BuildWorkflowIdTest(WorkflowType type) { |
| std::string workflow_id_1 = BuildWorkflowId(type); |
| std::string workflow_id_2 = BuildWorkflowId(type); |
| |
| EXPECT_THAT(workflow_id_1, ::testing::Ne(workflow_id_2)); |
| int32_t length = WorkflowType::Type_Name(type.type()).size(); |
| // length + len("_")=1 + len(str(timestamp_usec))=16 + len("_")=1 |
| // + len(unique_id)=16 = length + 34 |
| EXPECT_EQ(length + 34, workflow_id_1.size()); |
| EXPECT_EQ(length + 34, workflow_id_2.size()); |
| } |
| FUZZ_TEST(WorkflowUtilFuzzTest, BuildWorkflowIdTest) |
| .WithDomains(fuzztest::Arbitrary<WorkflowType>()); |
| ``` |
| |
| This one is for a linear algebra library, checking that rotating any 2D vector |
| won’t change its magnitude: |
| |
| ```c++ |
| void RotationDoesNotChangeMagnitude(Vector2f v, float angle) { |
| { |
| Vector2f rotated = |
| v * |
| RotationMatrix<SecondComponentPoints::Down, MatrixOnThe::Right>(angle); |
| EXPECT_NEAR(Magnitude<float>(v), Magnitude<float>(rotated), 1E-5); |
| } |
| |
| { |
| Vector2f rotated = |
| RotationMatrix<SecondComponentPoints::Down, MatrixOnThe::Left>(angle) * |
| v; |
| EXPECT_NEAR(Magnitude<float>(v), Magnitude<float>(rotated), 1E-5); |
| } |
| } |
| FUZZ_TEST(LinearAlgebraTest, RotationDoesNotChangeMagnitude) |
| .WithDomains(Vector2fDomain(), InRange(-2 * M_PI, 2 * M_PI)); |
| ``` |
| |
| ### Differential Fuzzing with an Oracle |
| |
| If you have two implementations of the same thing, you can check that they both |
| return the same value for any input. For example, this proto library tests its |
| `Equals` method against `util::MessageDifferencer::Equals`: |
| |
| ```c++ |
| void EqualsConsistentWithMessageDifferencerProto3( |
| const testdata::TestProto3Type& m1, const testdata::TestProto3Type& m2) { |
| EXPECT_EQ(testdata::Equals(m1, m2), util::MessageDifferencer::Equals(m1, m2)); |
| } |
| FUZZ_TEST(CppEqualsGeneratorTest, EqualsConsistentWithMessageDifferencerProto3); |
| ``` |
| |
| The "oracle" implementation is often just a simpler version of the real |
| implementation. |
| |
| ### Roundtrip Fuzzing |
| |
| Certain pairs of operations, like encoding/decoding, compression/decompression, |
| or serialize/parse, are symmetrical. For these we can test that for any input, |
| if we decode an encoded value, we get back the original. For instance, this |
| proto library prints then parses back a message to ensure that the result is the |
| same as the original message. |
| |
| ```c++ |
| void PrintThenParseEqualsOriginalProto3( |
| const proto3_unittest::TestAllTypes& m) { |
| TextFormat::Printer printer; |
| std::string serialized; |
| EXPECT_TRUE(printer.PrintToString(m, &serialized)); |
| TextFormat::Parser parser; |
| proto3_unittest::TestAllTypes out_message; |
| EXPECT_TRUE(parser.ParseFromString(serialized, &out_message)); |
| EXPECT_THAT(out_message, |
| testing::proto::TreatingNaNsAsEqual(testing::EqualsProto(m))); |
| } |
| FUZZ_TEST(TextFormatFuzzTests, PrintThenParseEqualsOriginalProto3); |
| ``` |
| |
| Similarly, this HTML library ensures that escaping and unescaping text produces |
| the same input back again. |
| |
| ```c++ |
| void EscapeStringForPREThenUnescapeStringEqualsOriginal(const std::string& s) { |
| EXPECT_THAT(UnescapeString(EscapeStringForPRE(s)), testing::StrEq(s)); |
| } |
| FUZZ_TEST(TagUtilsFuzzTests, |
| EscapeStringForPREThenUnescapeStringEqualsOriginal); |
| ``` |