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
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@
cmake_minimum_required(VERSION 3.19)
project(fuzztest)

# Limit parallel link jobs to avoid OOM in CI (GitHub Actions)
if (CMAKE_GENERATOR STREQUAL "Ninja" AND DEFINED ENV{GITHUB_ACTIONS})
set_property(GLOBAL PROPERTY JOB_POOLS link_pool=1)
set(CMAKE_JOB_POOL_LINK link_pool)
endif ()


option(FUZZTEST_BUILD_TESTING "Building the tests." OFF)
option(FUZZTEST_BUILD_FLATBUFFERS "Building the flatbuffers support." OFF)
option(FUZZTEST_FUZZING_MODE "Building the fuzztest in fuzzing mode." OFF)
Expand Down
17 changes: 15 additions & 2 deletions domain_tests/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,10 @@ cc_test(
"@abseil-cpp//absl/random:bit_gen_ref",
"@abseil-cpp//absl/status",
"@abseil-cpp//absl/types:optional",
"@abseil-cpp//absl/types:span",
"@abseil-cpp//absl/types:variant",
"@com_google_fuzztest//fuzztest:domain_core",
"@com_google_fuzztest//fuzztest/internal:serialization",
"@com_google_fuzztest//fuzztest/internal:type_support",
"@com_google_fuzztest//fuzztest/internal/domains:core_domains_impl",
"@googletest//:gtest_main",
],
)
Expand Down Expand Up @@ -137,6 +136,7 @@ cc_test(
"@abseil-cpp//absl/random",
"@com_google_fuzztest//fuzztest:domain_core",
"@com_google_fuzztest//fuzztest/internal:table_of_recent_compares",
"@com_google_fuzztest//fuzztest/internal/domains:core_domains_impl",
"@googletest//:gtest_main",
],
)
Expand Down Expand Up @@ -199,7 +199,9 @@ cc_test(
":domain_testing",
"@abseil-cpp//absl/algorithm:container",
"@abseil-cpp//absl/random",
"@abseil-cpp//absl/status",
"@com_google_fuzztest//fuzztest:domain_core",
"@com_google_fuzztest//fuzztest/internal/domains:core_domains_impl",
"@googletest//:gtest_main",
],
)
Expand Down Expand Up @@ -256,6 +258,17 @@ cc_test(
":domain_testing",
"@abseil-cpp//absl/random",
"@com_google_fuzztest//fuzztest:domain_core",
"@com_google_fuzztest//fuzztest/internal/domains:core_domains_impl",
"@googletest//:gtest_main",
],
)

cc_test(
name = "traversal_context_test",
srcs = ["traversal_context_test.cc"],
deps = [
"@abseil-cpp//absl/status",
"@com_google_fuzztest//fuzztest/internal/domains:core_domains_impl",
"@googletest//:gtest_main",
],
)
Expand Down
12 changes: 12 additions & 0 deletions domain_tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -281,3 +281,15 @@ fuzztest_cc_test(
fuzztest::table_of_recent_compares
GTest::gmock_main
)

fuzztest_cc_test(
NAME
traversal_context_test
SRCS
"traversal_context_test.cc"
DEPS
fuzztest::domain_core
fuzztest::meta
absl::status
GTest::gmock_main
)
53 changes: 53 additions & 0 deletions domain_tests/aggregate_combinators_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "absl/types/optional.h"
#include "./fuzztest/domain_core.h"
#include "./domain_tests/domain_testing.h"
#include "./fuzztest/internal/domains/traversal_context.h"
#include "./fuzztest/internal/serialization.h"
#include "./fuzztest/internal/type_support.h"

Expand Down Expand Up @@ -491,5 +492,57 @@ TEST(TupleOf, DomainWithCustomPairCorpusType) {
EXPECT_TRUE(optional_corpus_tuple.has_value());
}

TEST(StructOf, InitWithTrackerUpdatesCount) {
struct LocalStruct {
int a;
double b;
};
auto domain = StructOf<LocalStruct>(Arbitrary<int>(), Arbitrary<double>());

absl::BitGen prng;
internal::TraversalState state;
state.count = 10;

Value val(domain, prng, state);

// 1 (root) + 2 (fields: int, double) = 3 decrements
EXPECT_EQ(*state.count, 7);
}

TEST(StructOf, InitWithTrackerPropagatesFailureFromInnerDomain) {
struct LocalStruct {
int a;
std::vector<int> v;
};
// Inner container cannot be empty.
auto domain = StructOf<LocalStruct>(
Arbitrary<int>(), VectorOf(Arbitrary<int>()).WithMinSize(1));

absl::BitGen prng;
internal::TraversalState state;
// The parent init decrements to 0 and the inner vector init decrements to -1
// and fails due to the min size constraint.
state.depth = 1;

Value val(domain, prng, state);

EXPECT_FALSE(state.status.ok());
EXPECT_THAT(state.status.ToString(),
testing::HasSubstr("Traversal budget exceeded"));
}

TEST(StructOf, InitWithTrackerHandlesPreExistingFailure) {
auto domain = StructOf<MyStruct>(Arbitrary<int>(), Arbitrary<std::string>());

absl::BitGen prng;
internal::TraversalState state;
state.status = absl::CancelledError("Pre-existing failure");

Value val(domain, prng, state);

EXPECT_FALSE(state.status.ok());
EXPECT_EQ(state.status.message(), "Pre-existing failure");
}

} // namespace
} // namespace fuzztest
2 changes: 1 addition & 1 deletion domain_tests/arbitrary_domains_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,7 @@ TEST(ArbitraryStatusTest, GeneratesOkAndError) {
bool found_ok = false;
bool found_error = false;

for (int i = 0; i < 100 && (!found_ok || !found_error); ++i) {
for (int i = 0; i < 1000 && (!found_ok || !found_error); ++i) {
absl::Status s = domain.GetRandomValue(prng);
if (s.ok()) {
found_ok = true;
Expand Down
91 changes: 91 additions & 0 deletions domain_tests/container_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "absl/random/random.h"
#include "./fuzztest/domain_core.h"
#include "./domain_tests/domain_testing.h"
#include "./fuzztest/internal/domains/traversal_context.h"
#include "./fuzztest/internal/table_of_recent_compares.h"

namespace fuzztest {
Expand Down Expand Up @@ -369,5 +370,95 @@ TEST(Container, ValidatesMemoryDictionaryMutationForInnerDomain) {
EXPECT_THAT(mutants, Not(Contains(std::vector<uint8_t>{129, 129, 129, 129})));
}

TEST(ContainerTest,
SequenceInitWithTrackerDepthExhaustedWithMinSizeReturnsEmptyAndFails) {
auto domain = VectorOf(Arbitrary<int>()).WithMinSize(3);

absl::BitGen prng;
internal::TraversalState state;
state.depth = 0;

Value val(domain, prng, state);

EXPECT_TRUE(val.user_value.empty());
EXPECT_FALSE(state.status.ok());
EXPECT_THAT(state.status.ToString(),
testing::HasSubstr("Traversal budget exceeded"));
}

TEST(ContainerTest,
SequenceInitWithTrackerDepthExhaustedNoMinSizeReturnsEmptyAndSucceeds) {
auto domain = VectorOf(Arbitrary<int>());

absl::BitGen prng;
internal::TraversalState state;
state.depth = 0;

Value val(domain, prng, state);

EXPECT_TRUE(val.user_value.empty());
EXPECT_TRUE(state.status.ok());
}

TEST(ContainerTest, SequenceInitWithTrackerUpdatesCount) {
auto domain = VectorOf(Arbitrary<int>()).WithSize(3);

absl::BitGen prng;
internal::TraversalState state;
state.count = 10;

Value val(domain, prng, state);

EXPECT_EQ(val.user_value.size(), 3);
// 1 (root) + 3 (elements) = 4 decrements
EXPECT_EQ(*state.count, 6);
}

TEST(ContainerTest, SequenceInitWithTrackerCountExhaustedWithMinSizeFails) {
auto domain = VectorOf(Arbitrary<int>()).WithMinSize(3);

absl::BitGen prng;
internal::TraversalState state;
state.count = 0; // Enter() will decrement to -1

Value val(domain, prng, state);

EXPECT_TRUE(val.user_value.empty());
EXPECT_FALSE(state.status.ok());
}

TEST(ContainerTest,
SequenceInitWithTrackerCountExhaustedNoMinSizeReturnsEmptyAndSucceeds) {
auto domain = VectorOf(Arbitrary<int>());

absl::BitGen prng;
internal::TraversalState state;
state.count = 0;

Value val(domain, prng, state);

EXPECT_TRUE(val.user_value.empty());
EXPECT_TRUE(state.status.ok());
}

TEST(ContainerTest,
SequenceInitWithTrackerPropagatesFailureFromInnerExhaustion) {
// Both the parent and inner vectors must not be empty.
auto domain =
VectorOf(VectorOf(Arbitrary<int>()).WithMinSize(1)).WithMinSize(1);

absl::BitGen prng;
internal::TraversalState state;
// The parent init decrements to 0 and the inner init decrements to -1
// and fails due to the min size constraint.
state.depth = 1;

Value val(domain, prng, state);

EXPECT_FALSE(state.status.ok());
EXPECT_THAT(state.status.ToString(),
testing::HasSubstr("Traversal budget exceeded"));
}

} // namespace
} // namespace fuzztest
6 changes: 6 additions & 0 deletions domain_tests/domain_testing.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#include "absl/strings/string_view.h"
#include "./common/logging.h"
#include "./fuzztest/internal/domains/mutation_metadata.h"
#include "./fuzztest/internal/domains/traversal_context.h"
#include "./fuzztest/internal/logging.h"
#include "./fuzztest/internal/meta.h"
#include "./fuzztest/internal/serialization.h"
Expand Down Expand Up @@ -137,6 +138,11 @@ struct Value {
: corpus_value(domain.Init(prng)),
user_value(domain.GetValue(corpus_value)) {}

Value(Domain& domain, absl::BitGenRef prng, internal::TraversalState& state)
: corpus_value(domain.InitWithTracker(
prng, internal::TraversalContextWithTotalCount<Domain>(state))),
user_value(domain.GetValue(corpus_value)) {}

// If the value_type is not copy constructible we have to copy the corpus and
// regenerate the value.
Value(const Value& other, Domain& domain)
Expand Down
62 changes: 62 additions & 0 deletions domain_tests/map_filter_combinator_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@
#include "gtest/gtest.h"
#include "absl/algorithm/container.h"
#include "absl/random/random.h"
#include "absl/status/status.h"
#include "./fuzztest/domain_core.h"
#include "./domain_tests/domain_testing.h"
#include "./fuzztest/internal/domains/traversal_context.h"

namespace fuzztest {
namespace {
Expand Down Expand Up @@ -408,5 +410,65 @@ TEST(Filter, ValidationRejectsInvalidValue) {
HasSubstr("Invalid corpus value for the inner domain in Filter()")));
}

TEST(Filter, InitWithTrackerRestoresBudgetOnPredicateFailure) {
int attempts = 0;
// Fails 50 times, then succeeds.
auto domain =
Filter([&attempts](int i) { return ++attempts > 50; }, Arbitrary<int>());

absl::BitGen prng;
internal::TraversalState state;
state.depth = 100;
state.count = 10;

Value val(domain, prng, state);

EXPECT_TRUE(state.status.ok());
// 10 - 1 (root) - 3 (successful inner with type erasure) = 6.
// The 50 failed attempts are restored.
EXPECT_EQ(*state.count, 6);
EXPECT_EQ(state.depth, 100);
}

TEST(Filter, InitWithTrackerReturnsEarlyOnPreExistingFailure) {
auto domain = Filter(
[](int i) {
ADD_FAILURE() << "Predicate should not be called";
return false;
},
Arbitrary<int>());

absl::BitGen prng;
internal::TraversalState state;
state.status = absl::CancelledError("Pre-existing failure");

Value val(domain, prng, state);

EXPECT_FALSE(state.status.ok());
EXPECT_EQ(state.status.message(), "Pre-existing failure");
}

TEST(Filter, InitWithTrackerReturnsInvalidValueOnFailure) {
auto domain = Filter(
[](const std::vector<int>& v) {
ADD_FAILURE() << "Predicate should not be called";
return false;
},
VectorOf(Arbitrary<int>()).WithMinSize(3));

absl::BitGen prng;
internal::TraversalState state;
// This causes the inner VectorOf initialization to fail due to budget
// exhaustion.
state.count = 2;

Value val(domain, prng, state);

EXPECT_FALSE(state.status.ok());
EXPECT_THAT(state.status.ToString(),
testing::HasSubstr("Traversal budget exceeded"));
EXPECT_TRUE(val.user_value.empty());
}

} // namespace
} // namespace fuzztest
Loading
Loading