From ac1e595fc7a0f6cdeae3230920c8987b02f336e3 Mon Sep 17 00:00:00 2001 From: Daniel Lowengrub Date: Tue, 12 May 2026 10:27:06 -0700 Subject: [PATCH] Arbitrary Status Domain PiperOrigin-RevId: 914354029 --- doc/domains-reference.md | 2 + domain_tests/BUILD | 1 + domain_tests/arbitrary_domains_test.cc | 74 ++++++++++++++++++++++ fuzztest/internal/domains/BUILD | 3 + fuzztest/internal/domains/absl_helpers.h | 10 +++ fuzztest/internal/domains/arbitrary_impl.h | 59 +++++++++++++++++ 6 files changed, 149 insertions(+) diff --git a/doc/domains-reference.md b/doc/domains-reference.md index 4db65653c..550168cd9 100644 --- a/doc/domains-reference.md +++ b/doc/domains-reference.md @@ -42,6 +42,8 @@ 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`. Composite or container types, like `std::optional` or `std::vector`, are supported as long as the inner types are. For example, diff --git a/domain_tests/BUILD b/domain_tests/BUILD index 8d8d25194..77ae79eb8 100644 --- a/domain_tests/BUILD +++ b/domain_tests/BUILD @@ -88,6 +88,7 @@ cc_test( "@abseil-cpp//absl/random", "@abseil-cpp//absl/random:bit_gen_ref", "@abseil-cpp//absl/status", + "@abseil-cpp//absl/strings:cord", "@abseil-cpp//absl/time", "@com_google_fuzztest//fuzztest:domain_core", "@com_google_fuzztest//fuzztest/internal:serialization", diff --git a/domain_tests/arbitrary_domains_test.cc b/domain_tests/arbitrary_domains_test.cc index ce611704e..6e7480e36 100644 --- a/domain_tests/arbitrary_domains_test.cc +++ b/domain_tests/arbitrary_domains_test.cc @@ -34,6 +34,7 @@ #include "absl/random/bit_gen_ref.h" #include "absl/random/random.h" #include "absl/status/status.h" +#include "absl/strings/cord.h" #include "absl/time/time.h" #include "./fuzztest/domain_core.h" // IWYU pragma: keep #include "./domain_tests/domain_testing.h" @@ -626,5 +627,78 @@ TEST(ArbitraryTimeTest, ArbitraryVectorHasAllTypesOfValues) { EXPECT_THAT(to_find, IsEmpty()); } +TEST(ArbitraryStatusCodeTest, GeneratesAllValues) { + absl::flat_hash_set 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::BitGen prng; + + const int max_iterations = IterationsToHitAll(17, 1.0 / (4 * 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 domain = Arbitrary().WithSeeds( + {absl::StatusCode::kInvalidArgument}); + + EXPECT_THAT(GenerateInitialValues(domain, 1000), + Contains(Value(domain, absl::StatusCode::kInvalidArgument))); +} + +TEST(ArbitraryStatusTest, GeneratesOkAndError) { + auto domain = Arbitrary(); + 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 domain = Arbitrary().WithSeeds({seed}); + + EXPECT_THAT(GenerateInitialValues(domain, 1000), + Contains(Value(domain, seed))); +} + +TEST(ArbitraryStatusTest, FromValueReturnsNulloptWhenPayloadsArePresent) { + auto domain = Arbitrary(); + absl::Status status = absl::InvalidArgumentError("msg"); + status.SetPayload("some_url", absl::Cord("payload")); + EXPECT_FALSE(domain.FromValue(status).has_value()); +} + } // namespace } // namespace fuzztest diff --git a/fuzztest/internal/domains/BUILD b/fuzztest/internal/domains/BUILD index 732d67573..deed526e5 100644 --- a/fuzztest/internal/domains/BUILD +++ b/fuzztest/internal/domains/BUILD @@ -22,6 +22,9 @@ cc_library( name = "absl_helpers", hdrs = ["absl_helpers.h"], deps = [ + "@abseil-cpp//absl/status", + "@abseil-cpp//absl/strings:cord", + "@abseil-cpp//absl/strings:string_view", "@abseil-cpp//absl/time", "@com_google_fuzztest//common:logging", "@com_google_fuzztest//fuzztest/internal:logging", diff --git a/fuzztest/internal/domains/absl_helpers.h b/fuzztest/internal/domains/absl_helpers.h index efcfe7c60..5f811466a 100644 --- a/fuzztest/internal/domains/absl_helpers.h +++ b/fuzztest/internal/domains/absl_helpers.h @@ -19,6 +19,9 @@ #include #include +#include "absl/status/status.h" +#include "absl/strings/cord.h" +#include "absl/strings/string_view.h" #include "absl/time/time.h" #include "./common/logging.h" #include "./fuzztest/internal/logging.h" @@ -64,6 +67,13 @@ inline uint32_t GetTicks(absl::Duration d) { return GetSecondsAndTicks(d).second; } +inline bool HasPayload(const absl::Status& status) { + bool has_payload = false; + status.ForEachPayload( + [&](absl::string_view, const absl::Cord&) { has_payload = true; }); + return has_payload; +} + } // namespace fuzztest::internal #endif // FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_ABSL_HELPERS_H_ diff --git a/fuzztest/internal/domains/arbitrary_impl.h b/fuzztest/internal/domains/arbitrary_impl.h index b4d53484e..6e27605b8 100644 --- a/fuzztest/internal/domains/arbitrary_impl.h +++ b/fuzztest/internal/domains/arbitrary_impl.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -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" @@ -583,6 +585,63 @@ class ArbitraryImpl ArbitraryImpl()) {} }; +// Arbitrary for absl::StatusCode. +using StatusCodeUnderlyingT = std::underlying_type_t; + +template <> +class ArbitraryImpl + : public ReversibleMapImpl< + absl::StatusCode (*)(StatusCodeUnderlyingT), + std::optional> (*)( + absl::StatusCode), + InRangeImpl> { + public: + ArbitraryImpl() + : ReversibleMapImpl> (*)( + absl::StatusCode), + InRangeImpl>( + [](StatusCodeUnderlyingT code) { + return static_cast(code); + }, + [](absl::StatusCode code) { + return std::optional{ + std::tuple{static_cast(code)}}; + }, + InRangeImpl(0, 16)) {} +}; + +// Arbitrary for absl::Status. +// Note: This does not generate payloads. Seeds with payloads will be skipped +// to avoid violating the ReversibleMap invariant (payloads would be stripped). +template <> +class ArbitraryImpl + : public ReversibleMapImpl< + absl::Status (*)(absl::StatusCode, std::string), + std::optional> (*)( + absl::Status), + ArbitraryImpl, ArbitraryImpl> { + public: + ArbitraryImpl() + : ReversibleMapImpl< + absl::Status (*)(absl::StatusCode, std::string), + std::optional> (*)( + absl::Status), + ArbitraryImpl, ArbitraryImpl>( + [](absl::StatusCode code, std::string msg) { + return absl::Status(code, msg); + }, + +[](absl::Status status) + -> std::optional> { + if (HasPayload(status)) { + return std::nullopt; + } + return std::optional{ + std::tuple{status.code(), std::string(status.message())}}; + }, + ArbitraryImpl(), ArbitraryImpl()) {} +}; + // Arbitrary for absl::BitGenRef. template <> class ArbitraryImpl