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
6 changes: 6 additions & 0 deletions doc/domains-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ protocol buffers. Specifically, for the following types:
- Protocol buffer types: `MyProtoMessage`, etc.
- [Abseil time library types](https://abseil.io/docs/cpp/guides/time):
`absl::Duration`, `absl::Time`.
- [Abseil status types](https://abseil.io/docs/cpp/guides/status):
`absl::StatusCode`, `absl::Status`.
- Enum types: `enum class Color { kRed, kGreen, kBlue };`

Composite or container types, like `std::optional<T>` or `std::vector<T>`, are
supported as long as the inner types are. For example,
Expand All @@ -59,6 +62,9 @@ cannot have C-style array members (e.g., `int[5]`).
TIP: If your struct doesn't satisfy the requirements for `Arbitrary`, you can
construct a domain for it using `Map`, `ReversibleMap`, or `FlatMap`.

Enum types only supported for `clang` and `gcc`
with enums that have fewer than 256 fields.

Recall that `Arbitrary` is the default input domain, which means that you can
fuzz a function like below without a `.WithDomains()` clause:

Expand Down
10 changes: 10 additions & 0 deletions domain_tests/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,16 @@ cc_library(
],
)

cc_test(
name = "enum_reflection_test",
srcs = ["enum_reflection_test.cc"],
deps = [
"@abseil-cpp//absl/strings:string_view",
"@com_google_fuzztest//fuzztest/internal/domains:core_domains_impl",
"@googletest//:gtest_main",
],
)

cc_test(
name = "in_grammar_domain_test",
srcs = ["in_grammar_domain_test.cc"],
Expand Down
83 changes: 83 additions & 0 deletions domain_tests/arbitrary_domains_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -626,5 +626,88 @@ TEST(ArbitraryTimeTest, ArbitraryVectorHasAllTypesOfValues) {
EXPECT_THAT(to_find, IsEmpty());
}

TEST(ArbitraryStatusCodeTest, GeneratesAllValues) {
absl::flat_hash_set<absl::StatusCode> to_find = {
absl::StatusCode::kOk,
absl::StatusCode::kCancelled,
absl::StatusCode::kUnknown,
absl::StatusCode::kInvalidArgument,
absl::StatusCode::kDeadlineExceeded,
absl::StatusCode::kNotFound,
absl::StatusCode::kAlreadyExists,
absl::StatusCode::kPermissionDenied,
absl::StatusCode::kResourceExhausted,
absl::StatusCode::kFailedPrecondition,
absl::StatusCode::kAborted,
absl::StatusCode::kOutOfRange,
absl::StatusCode::kUnimplemented,
absl::StatusCode::kInternal,
absl::StatusCode::kUnavailable,
absl::StatusCode::kDataLoss,
absl::StatusCode::kUnauthenticated,
};
auto domain = Arbitrary<absl::StatusCode>();
absl::BitGen prng;

const int max_iterations = IterationsToHitAll(17, 1.0 / 17);
for (int i = 0; i < max_iterations && !to_find.empty(); ++i) {
to_find.erase(domain.GetRandomValue(prng));
}

EXPECT_THAT(to_find, IsEmpty());
}

TEST(ArbitraryStatusCodeTest, InitGeneratesSeeds) {
Domain<absl::StatusCode> domain = Arbitrary<absl::StatusCode>().WithSeeds(
{absl::StatusCode::kInvalidArgument});

EXPECT_THAT(GenerateInitialValues(domain, 1000),
Contains(Value(domain, absl::StatusCode::kInvalidArgument)));
}

TEST(ArbitraryStatusTest, GeneratesOkAndError) {
auto domain = Arbitrary<absl::Status>();
absl::BitGen prng;
bool found_ok = false;
bool found_error = false;

for (int i = 0; i < 100 && (!found_ok || !found_error); ++i) {
absl::Status s = domain.GetRandomValue(prng);
if (s.ok()) {
found_ok = true;
} else {
found_error = true;
}
}

EXPECT_TRUE(found_ok);
EXPECT_TRUE(found_error);
}

TEST(ArbitraryStatusTest, InitGeneratesSeeds) {
absl::Status seed = absl::InvalidArgumentError("seed message");
Domain<absl::Status> domain = Arbitrary<absl::Status>().WithSeeds({seed});

EXPECT_THAT(GenerateInitialValues(domain, 1000),
Contains(Value(domain, seed)));
}

enum class MyTestEnum { kValue0, kValue1, kValue2 };

TEST(ArbitraryEnumTest, GeneratesAllValues) {
auto domain = Arbitrary<MyTestEnum>();
absl::BitGen prng;
absl::flat_hash_set<MyTestEnum> found;

const int max_iterations = IterationsToHitAll(3, 1.0 / 3);
for (int i = 0; i < max_iterations && found.size() < 3; ++i) {
found.insert(domain.GetRandomValue(prng));
}

EXPECT_THAT(found, testing::UnorderedElementsAre(MyTestEnum::kValue0,
MyTestEnum::kValue1,
MyTestEnum::kValue2));
}

} // namespace
} // namespace fuzztest
53 changes: 53 additions & 0 deletions domain_tests/enum_reflection_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "./fuzztest/internal/domains/enum_reflection.h"

#include <vector>

#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/strings/string_view.h"

namespace fuzztest::internal::enum_reflection {
namespace {

TEST(EnumReflectionTest, IsValidEnumValueSuffix_ValidSuffixes) {
EXPECT_TRUE(IsValidEnumValueSuffix("... V = kGreen]"));
EXPECT_TRUE(IsValidEnumValueSuffix("... V = Green]"));
EXPECT_TRUE(IsValidEnumValueSuffix("... V = _Value_123]"));
EXPECT_TRUE(IsValidEnumValueSuffix("... V = fuzztest::Color::kGreen]"));
}

TEST(EnumReflectionTest, IsValidEnumValueSuffix_InvalidSuffixes) {
EXPECT_FALSE(IsValidEnumValueSuffix("... V = kGreen")); // Missing ]
EXPECT_FALSE(IsValidEnumValueSuffix("")); // Empty
EXPECT_FALSE(IsValidEnumValueSuffix("]")); // Only ]
EXPECT_FALSE(IsValidEnumValueSuffix("... V = 42]")); // Ends with number
EXPECT_FALSE(
IsValidEnumValueSuffix("... V = (Color)2]")); // Ends with number
EXPECT_FALSE(IsValidEnumValueSuffix("... V = ]")); // Empty suffix
}

enum class MyTestEnum { kVal0 = 0, kVal_1 = 2, ABC = 3 };

TEST(EnumReflectionTest, GetEnumValues_ReturnsValidValues) {
std::vector<MyTestEnum> values = GetEnumValues<MyTestEnum>();
EXPECT_THAT(values,
testing::UnorderedElementsAre(
MyTestEnum::kVal0, MyTestEnum::kVal_1, MyTestEnum::ABC));
}

} // namespace
} // namespace fuzztest::internal::enum_reflection
1 change: 1 addition & 0 deletions fuzztest/internal/domains/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ cc_library(
"domain_base.h",
"domain_type_erasure.h",
"element_of_impl.h",
"enum_reflection.h",
"filter_impl.h",
"flat_map_impl.h",
"in_range_impl.h",
Expand Down
59 changes: 58 additions & 1 deletion fuzztest/internal/domains/arbitrary_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <limits>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
Expand All @@ -31,6 +32,7 @@

#include "absl/random/bit_gen_ref.h"
#include "absl/random/distributions.h"
#include "absl/status/status.h"
#include "absl/strings/string_view.h"
#include "absl/time/time.h"
#include "./fuzztest/internal/domains/absl_helpers.h"
Expand All @@ -40,6 +42,7 @@
#include "./fuzztest/internal/domains/domain.h"
#include "./fuzztest/internal/domains/domain_base.h"
#include "./fuzztest/internal/domains/element_of_impl.h"
#include "./fuzztest/internal/domains/enum_reflection.h"
#include "./fuzztest/internal/domains/in_range_impl.h"
#include "./fuzztest/internal/domains/map_impl.h"
#include "./fuzztest/internal/domains/one_of_impl.h"
Expand All @@ -50,7 +53,6 @@
#include "./fuzztest/internal/domains/variant_of_impl.h"
#include "./fuzztest/internal/meta.h"
#include "./fuzztest/internal/serialization.h"
#include "./fuzztest/internal/status.h"
#include "./fuzztest/internal/table_of_recent_compares.h"
#include "./fuzztest/internal/type_support.h"

Expand All @@ -64,6 +66,16 @@ class ArbitraryImpl {
);
};

// Arbitrary for enums.
// See limitations in fuzztest::internal::enum_reflection::GetEnumValues
template <typename T>
class ArbitraryImpl<
T, std::enable_if_t<std::is_enum_v<T> && !is_protocol_buffer_enum_v<T>>>
: public ElementOfImpl<T> {
public:
ArbitraryImpl() : ElementOfImpl<T>(enum_reflection::GetEnumValues<T>()) {}
};

// Arbitrary for monostate.
//
// For monostate types with a default constructor, just give the single value.
Expand Down Expand Up @@ -583,6 +595,51 @@ class ArbitraryImpl<absl::Time>
ArbitraryImpl<absl::Duration>()) {}
};

// Arbitrary for absl::StatusCode.
template <>
class ArbitraryImpl<absl::StatusCode>
: public ReversibleMapImpl<absl::StatusCode (*)(int),
std::optional<std::tuple<int>> (*)(
absl::StatusCode),
InRangeImpl<int>> {
public:
ArbitraryImpl()
: ReversibleMapImpl<absl::StatusCode (*)(int),
std::optional<std::tuple<int>> (*)(absl::StatusCode),
InRangeImpl<int>>(
[](int code) { return static_cast<absl::StatusCode>(code); },
[](absl::StatusCode code) {
return std::optional{std::tuple{static_cast<int>(code)}};
},
InRangeImpl<int>(0, 16)) {}
};

// Arbitrary for absl::Status.
// Note: This does not generate payloads.
template <>
class ArbitraryImpl<absl::Status>
: public ReversibleMapImpl<
absl::Status (*)(absl::StatusCode, std::string),
std::optional<std::tuple<absl::StatusCode, std::string>> (*)(
absl::Status),
ArbitraryImpl<absl::StatusCode>, ArbitraryImpl<std::string>> {
public:
ArbitraryImpl()
: ReversibleMapImpl<
absl::Status (*)(absl::StatusCode, std::string),
std::optional<std::tuple<absl::StatusCode, std::string>> (*)(
absl::Status),
ArbitraryImpl<absl::StatusCode>, ArbitraryImpl<std::string>>(
[](absl::StatusCode code, std::string msg) {
return absl::Status(code, msg);
},
[](absl::Status status) {
return std::optional{
std::tuple{status.code(), std::string(status.message())}};
},
ArbitraryImpl<absl::StatusCode>(), ArbitraryImpl<std::string>()) {}
};

// Arbitrary for absl::BitGenRef.
template <>
class ArbitraryImpl<absl::BitGenRef>
Expand Down
88 changes: 88 additions & 0 deletions fuzztest/internal/domains/enum_reflection.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_ENUM_REFLECTION_H_
#define FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_ENUM_REFLECTION_H_

#include <array>
#include <cstddef>
#include <utility>
#include <vector>

#include "absl/strings/string_view.h"

namespace fuzztest::internal {

namespace enum_reflection {

constexpr bool IsValidCharacter(char c) {
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') || c == '_';
}

constexpr bool IsNumber(char c) { return c >= '0' && c <= '9'; }

// When the template parameter V is equal to a valid enum value,
// clang replaces __PRETTY_FUNCTION__ with a string ending in something like:
// "[E = my_namespace::MyEnum, V = my_namespace::MyEnum::kRed]"
// If V is not a valid value, the suffix looks like:
// "[E = my_namespace::MyEnum, V = (my_namespace::MyEnum)5]".
constexpr bool IsValidEnumValueSuffix(absl::string_view name) {
if (name.empty() || name.back() != ']') return false;
name.remove_suffix(1);

size_t i = name.size();
while (i > 0 && IsValidCharacter(name[i - 1])) {
--i;
}
return i < name.size() && !IsNumber(name[i]);
}

template <typename E, E V>
constexpr bool IsValidEnumValue() {
#if defined(__clang__) || defined(__GNUC__)
return IsValidEnumValueSuffix(
{__PRETTY_FUNCTION__, sizeof(__PRETTY_FUNCTION__) - 1});
#else
#error "Enum reflection is only supported on Clang and GCC"
#endif
}

template <typename E, int... Is>
std::vector<E> GetValidEnumValues(std::integer_sequence<int, Is...>) {
constexpr auto is_valid =
std::array{IsValidEnumValue<E, static_cast<E>(Is - 128)>()...};
constexpr auto values_arr = std::array{static_cast<E>(Is - 128)...};

std::vector<E> values;
for (size_t i = 0; i < sizeof...(Is); ++i) {
if (is_valid[i]) {
values.push_back(values_arr[i]);
}
}
return values;
}

// Currently only available with Clang and GCC.
// Assumes that the enums values are within the range [-128, 127].
template <typename E>
std::vector<E> GetEnumValues() {
return GetValidEnumValues<E>(std::make_integer_sequence<int, 256>{});
}

} // namespace enum_reflection

} // namespace fuzztest::internal

#endif // FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_ENUM_REFLECTION_H_
Loading