From 023b3408963330511d6fef96f55c34f881e646eb Mon Sep 17 00:00:00 2001 From: Laramie Leavitt Date: Sat, 21 Feb 2026 14:26:13 -0800 Subject: [PATCH] Update FuzzingBitGen to take two streams, an instruction stream and a data stream. The instruction stream controls the behavior of the mocked distribution functions, determining whether to return boundary values (min, max, mean) or a value derived from the data stream. The data stream provides the actual byte data for generating random values. Also add additional tests to FuzzingBitGen for the distribution functions. NOTE: This will change the variates generated by FuzzingBitGen from prior versions. PiperOrigin-RevId: 873431571 --- domain_tests/bitgen_ref_domain_test.cc | 28 +- .../fuzz_tests_for_microbenchmarking.cc | 17 +- fuzztest/BUILD | 11 + fuzztest/fuzzing_bit_gen.cc | 86 +++- fuzztest/fuzzing_bit_gen.h | 89 ++-- fuzztest/fuzzing_bit_gen_test.cc | 85 ++++ fuzztest/internal/BUILD | 12 +- fuzztest/internal/domains/arbitrary_impl.h | 18 +- fuzztest/internal/domains/bit_gen_ref.h | 88 ++-- fuzztest/internal/register_fuzzing_mocks.cc | 400 ++++++++++-------- fuzztest/internal/register_fuzzing_mocks.h | 12 +- .../internal/register_fuzzing_mocks_test.cc | 118 ++++++ 12 files changed, 664 insertions(+), 300 deletions(-) create mode 100644 fuzztest/fuzzing_bit_gen_test.cc create mode 100644 fuzztest/internal/register_fuzzing_mocks_test.cc diff --git a/domain_tests/bitgen_ref_domain_test.cc b/domain_tests/bitgen_ref_domain_test.cc index 869bc0f8b..1cafdf53c 100644 --- a/domain_tests/bitgen_ref_domain_test.cc +++ b/domain_tests/bitgen_ref_domain_test.cc @@ -14,6 +14,7 @@ #include +#include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/random/bit_gen_ref.h" #include "absl/random/random.h" @@ -30,31 +31,36 @@ TEST(BitGenRefDomainTest, DistinctVariatesGeneratedByCallOperator) { Value v0(domain, bitgen_for_seeding); Value v1(domain, bitgen_for_seeding); - // Discard the first value, which may be from the data stream. - // If the implementation of BitGenRefDomain changes this may break. - v0.user_value(); - v1.user_value(); + // The domain uses an instruction stream of at least 7 bytes; + // If the instruction stream has no high-order bits set (0x80), the + // results below will be identical, otherwise they will differ as + // at least one value should be an lcg call. std::vector a, b; for (int i = 0; i < 10; ++i) { a.push_back(v0.user_value()); b.push_back(v1.user_value()); } - EXPECT_NE(a, b); + EXPECT_THAT(a, testing::Not(testing::ElementsAreArray(b))); } -TEST(BitGenRefDomainTest, AbseilUniformReturnsLowerBoundWhenExhausted) { +TEST(BitGenRefDomainTest, AbseilUniformWrapsAroundWhenExhausted) { absl::BitGen bitgen_for_seeding; Domain domain = Arbitrary(); Value v0(domain, bitgen_for_seeding); - // Discard the first value, which may be from the data stream. - // If the implementation of BitGenRefDomain changes this may break. - v0.user_value(); + // When the same domain is used to generate multiple values, the generated + // data sequence should be identical. + std::vector values; + for (int i = 0; i < 20; ++i) { + values.push_back(absl::Uniform(v0.user_value, 0, 100)); + } - for (int i = 0; i < 10; ++i) { - EXPECT_EQ(absl::Uniform(v0.user_value, 0, 100), 0); + // Verify repeatability + Value v1(v0, domain); + for (int i = 0; i < 20; ++i) { + EXPECT_EQ(absl::Uniform(v1.user_value, 0, 100), values[i]); } } diff --git a/e2e_tests/testdata/fuzz_tests_for_microbenchmarking.cc b/e2e_tests/testdata/fuzz_tests_for_microbenchmarking.cc index f1c7d636a..dddfea3a7 100644 --- a/e2e_tests/testdata/fuzz_tests_for_microbenchmarking.cc +++ b/e2e_tests/testdata/fuzz_tests_for_microbenchmarking.cc @@ -24,6 +24,7 @@ // i.e., to check that the fuzzer behaves as expected and outputs the expected // results. E.g., the fuzzer finds the abort() or bug. +#include #include #include #include @@ -33,6 +34,7 @@ #include #include #include +#include #include #include @@ -240,15 +242,26 @@ FUZZ_TEST(MySuite, FixedSizeVectorValue) .WithDomains(fuzztest::VectorOf(fuzztest::Arbitrary()).WithSize(4)); __attribute__((optnone)) void BitGenRef(absl::BitGenRef bitgen) { + // This uses FuzzingBitGen's mocking support for absl::Uniform(). if (absl::Uniform(bitgen, 0, 256) == 'F' && absl::Uniform(bitgen, 0, 256) == 'U' && - absl::Uniform(bitgen, 0, 256) == 'Z' && - absl::Uniform(bitgen, 0, 256) == 'Z') { + absl::Uniform(bitgen, 32, 128) == 'Z' && + absl::Uniform(bitgen, 32, 128) == 'Z') { std::abort(); // Bug! } } FUZZ_TEST(MySuite, BitGenRef); +__attribute__((optnone)) void BitGenRefShuffle(absl::BitGenRef bitgen) { + // This uses FuzzingBitGen's operator(). + std::vector v = {4, 1, 3, 2, 5}; + std::shuffle(v.begin(), v.end(), bitgen); + if (std::is_sorted(v.begin(), v.end())) { + std::abort(); // Bug! + } +} +FUZZ_TEST(MySuite, BitGenRefShuffle); + __attribute__((optnone)) void WithDomainClass(uint8_t a, double d) { // This will only crash with a=10, to make it easier to check the results. // d can have any value. diff --git a/fuzztest/BUILD b/fuzztest/BUILD index c3bd2261f..8456c566d 100644 --- a/fuzztest/BUILD +++ b/fuzztest/BUILD @@ -218,6 +218,17 @@ cc_library( ], ) +cc_test( + name = "fuzzing_bit_gen_test", + srcs = ["fuzzing_bit_gen_test.cc"], + deps = [ + ":fuzzing_bit_gen", + "@abseil-cpp//absl/random", + "@abseil-cpp//absl/random:bit_gen_ref", + "@googletest//:gtest_main", + ], +) + cc_library( name = "fuzzing_bit_gen", srcs = ["fuzzing_bit_gen.cc"], diff --git a/fuzztest/fuzzing_bit_gen.cc b/fuzztest/fuzzing_bit_gen.cc index 689a8f7cd..97b8ee4d4 100644 --- a/fuzztest/fuzzing_bit_gen.cc +++ b/fuzztest/fuzzing_bit_gen.cc @@ -18,42 +18,89 @@ #include #include #include +#include +#include +#include #include "absl/base/fast_type_id.h" #include "absl/base/no_destructor.h" #include "absl/container/flat_hash_map.h" +#include "absl/numeric/bits.h" +#include "absl/numeric/int128.h" #include "absl/types/span.h" #include "./fuzztest/internal/register_fuzzing_mocks.h" namespace fuzztest { +namespace { -FuzzingBitGen::FuzzingBitGen(absl::Span data_stream) - : data_stream_(data_stream) { +// Minimal implementation of a PCG64 engine equivalent to xsl_rr_128_64. +static inline constexpr absl::uint128 multiplier() { + return absl::MakeUint128(0x2360ed051fc65da4, 0x4385df649fccf645); +} +static inline constexpr absl::uint128 increment() { + return absl::MakeUint128(0x5851f42d4c957f2d, 0x14057b7ef767814f); +} +inline absl::uint128 lcg(absl::uint128 s) { + return s * multiplier() + increment(); +} +inline uint64_t mix(absl::uint128 state) { + uint64_t h = absl::Uint128High64(state); + uint64_t rotate = h >> 58u; + uint64_t s = absl::Uint128Low64(state) ^ h; + return absl::rotr(s, rotate); +} + +} // namespace + +FuzzingBitGen::FuzzingBitGen(absl::Span data_stream, + absl::Span instruction_stream) + : instruction_stream_(instruction_stream), data_stream_(data_stream) { // Seed the internal URBG with the first 8 bytes of the data stream. uint64_t stream_seed = 0x6C7FD535EDC7A62D; if (!data_stream_.empty()) { size_t num_bytes = std::min(sizeof(stream_seed), data_stream_.size()); std::memcpy(&stream_seed, data_stream_.data(), num_bytes); - data_stream_.remove_prefix(num_bytes); } seed(stream_seed); } -FuzzingBitGen::result_type FuzzingBitGen::operator()() { - // The non-mockable calls will consume the next 8 bytes from the data - // stream until it is exhausted, then they will return a value from the - // internal URBG. - if (!data_stream_.empty()) { - result_type x = 0; - size_t num_bytes = std::min(sizeof(x), data_stream_.size()); - std::memcpy(&x, data_stream_.data(), num_bytes); - data_stream_.remove_prefix(num_bytes); - return x; +uint64_t FuzzingBitGen::operator()() { + if (instruction_stream_.empty() || data_stream_.empty()) { + state_ = lcg(state_); + return mix(state_); } + // Use the instruction stream to determine the return value. + if (i_ >= instruction_stream_.size()) { + i_ = 0; + } + const uint8_t instruction = instruction_stream_[i_++]; + if (instruction & 0x80) { + state_ = lcg(state_); + return mix(state_); + } + switch (instruction & 0x03) { + case 1: + return 0; // min + case 2: + return (std::numeric_limits::max)(); // max + case 3: + return (std::numeric_limits::max)() / 2; // mean + default: + break; + } + if (d_ >= data_stream_.size()) { + d_ = 0; + } + uint64_t x = 0; + size_t num_bytes = std::min(sizeof(x), data_stream_.size() - d_); + std::memcpy(&x, data_stream_.data() + d_, num_bytes); + d_ += num_bytes; + return x; +} - // Fallback to the internal URBG. - state_ = lcg(state_); - return mix(state_); +void FuzzingBitGen::seed(result_type seed_value) { + absl::uint128 tmp = seed_value; + state_ = lcg(tmp + increment()); } bool FuzzingBitGen::InvokeMock(absl::FastTypeIdType key_id, void* args_tuple, @@ -73,7 +120,12 @@ bool FuzzingBitGen::InvokeMock(absl::FastTypeIdType key_id, void* args_tuple, if (it == fuzzing_map->end()) { return false; } - it->second(data_stream_, args_tuple, result); + using InstructionStream = std::pair, size_t&>; + using DataStream = std::pair, size_t&>; + + InstructionStream instruction_stream{instruction_stream_, i_}; + DataStream data_stream{data_stream_, d_}; + it->second(instruction_stream, data_stream, args_tuple, result); return true; } diff --git a/fuzztest/fuzzing_bit_gen.h b/fuzztest/fuzzing_bit_gen.h index f613892e7..79dfb94a8 100644 --- a/fuzztest/fuzzing_bit_gen.h +++ b/fuzztest/fuzzing_bit_gen.h @@ -15,11 +15,12 @@ #ifndef FUZZTEST_FUZZTEST_FUZZING_BIT_GEN_H_ #define FUZZTEST_FUZZTEST_FUZZING_BIT_GEN_H_ +#include #include +#include #include #include "absl/base/fast_type_id.h" -#include "absl/numeric/bits.h" #include "absl/numeric/int128.h" #include "absl/random/bit_gen_ref.h" #include "absl/types/span.h" @@ -28,36 +29,50 @@ namespace fuzztest { /// FuzzingBitGen is a BitGen instance which uses the Abseil mock mechanisms /// to return distribution specific variates based on the fuzz data stream. +/// The specific sequence generated by a FuzzingBitGen may vary due to the +/// underlying code paths and whether implementation details change, such as +/// adding support for new distributions, etc. /// -/// It is perhaps useful to think of the data stream as a sequence of structured -/// variates with semantic meaning, rather than just values. Recombinations of, -/// and modifications to, the sequence are useful in exploring the behavior of -/// the code under test in ways where a mere random-number generator sequence -/// would not, as changing the seed mutates the entire sequence. +/// It is useful to think of the `instruction stream` a sequence of control +/// bytes which modify the behavior of the mocked distribution functions, and +/// distinct from the data stream which is used to provide the random values. +/// The `data_stream` is used to provide the random values for the mocked +/// distribution functions, as well as an internal URBG used for non-mocked +/// functions, such as std::shuffle(...). +/// +/// Recombinations of, and modifications to, the sequence are useful in +/// exploring the behavior of the code under test in ways where a mere +/// random-number generator sequence would not, as changing the seed mutates the +/// entire sequence. +/// +/// Both the data stream and the instruction stream are reused (wrapped around) +/// when they are exhausted. /// /// NOTE: The first 8 bytes of the fuzzed data stream may be used to seed an -/// internal pnrg which is used to generate random variates for calls which -/// are not captured through mockable Abseil random distribution methods -/// (for example, calls to std::shuffle(...)). Otherwise the data stream is -/// treated as a stream where the next value in the sequence maps to the output -/// of the next distribution method. Note that the specific sequence generated -/// by a FuzzingBitGen may vary due to the underlying code paths and whether -/// implementation details change, such as adding support for new distributions, -/// etc. +/// internal pnrg which is sometimes used to generate random variates for calls +/// which are not captured through mockable Abseil random distribution methods +/// (for example, calls to std::shuffle(...)). /// -/// When the data stream is exhausted, absl::MockingBitGen mockable calls will -/// continue to return an arbitrary legal value, typically the minimum or mean -/// value of the distribution. +/// It is possible to initialize the FuzzingBitGen in such a way that usage may +/// get stuck in an infinite loop, such as when an underlying distribution +/// function uses rejection sampling, and the generated sequence is +/// deterministic and happens to be be in the rejected set. /// /// This type is thread-compatible, but not thread-safe. class FuzzingBitGen { public: - // Create a FuzzingBitGen from an unowned fuzzed `data` source, which must - // outlive the FuzzingBitGen instance. + // Create a FuzzingBitGen from an unowned fuzzed `data_stream` source and an + // optional `instruction_stream` source. Both streams must outlive the + // FuzzingBitGen and are reused (wrapped around) when exhausted. // - // The first 8 bytes of the data stream are used to seed an internal URBG used - // for calls which are not mockable. - explicit FuzzingBitGen(absl::Span data_stream); + // The `instruction_stream` is an opaque control sequence for the mocked + // calls. The `data_stream` is used as the source of random variates for + // both mocked and non-mocked calls; the first 8 bytes are also used to seed + // and internal LCG PRNG. + explicit FuzzingBitGen(absl::Span data_stream) + : FuzzingBitGen(data_stream, {}) {} + explicit FuzzingBitGen(absl::Span data_stream, + absl::Span instruction_stream); // Disallow copy, assign, and move. FuzzingBitGen(const FuzzingBitGen&) = delete; @@ -75,38 +90,22 @@ class FuzzingBitGen { return (std::numeric_limits::max)(); } - void seed(result_type seed_value = 0) { - absl::uint128 tmp = seed_value; - state_ = lcg(tmp + increment()); - } + void seed(result_type seed_value = 0); result_type operator()(); private: - // Minimal implementation of a PCG64 engine equivalent to xsl_rr_128_64. - static inline constexpr absl::uint128 multiplier() { - return absl::MakeUint128(0x2360ed051fc65da4, 0x4385df649fccf645); - } - static inline constexpr absl::uint128 increment() { - return absl::MakeUint128(0x5851f42d4c957f2d, 0x14057b7ef767814f); - } - inline absl::uint128 lcg(absl::uint128 s) { - return s * multiplier() + increment(); - } - inline result_type mix(absl::uint128 state) { - uint64_t h = absl::Uint128High64(state); - uint64_t rotate = h >> 58u; - uint64_t s = absl::Uint128Low64(state) ^ h; - return absl::rotr(s, rotate); - } - // InvokeMock meets the requirements of absl::BitGenRef::InvokeMock. // This method detects whether the key has been registered as supported, // and, if so, returns a value derived from `data_stream_`. bool InvokeMock(absl::FastTypeIdType key_id, void* args_tuple, void* result); - absl::Span data_stream_; // Mock data stream. - absl::uint128 state_ = 0; // Internal URBG state. + absl::uint128 state_ = 0; // Internal URBG state. + + absl::Span instruction_stream_; + size_t i_ = 0; // Offset into the instruction stream. + absl::Span data_stream_; + size_t d_ = 0; // Offset into the data stream. template friend struct ::absl::random_internal::DistributionCaller; // for InvokeMock diff --git a/fuzztest/fuzzing_bit_gen_test.cc b/fuzztest/fuzzing_bit_gen_test.cc new file mode 100644 index 000000000..53145cb2b --- /dev/null +++ b/fuzztest/fuzzing_bit_gen_test.cc @@ -0,0 +1,85 @@ +// 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 +// +// http://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/fuzzing_bit_gen.h" + +#include +#include +#include + +#include "gtest/gtest.h" +#include "absl/random/bit_gen_ref.h" +#include "absl/random/random.h" + +namespace fuzztest { +namespace { + +TEST(FuzzingBitGenTest, OperatorReturnsBytesFromStream) { + std::vector data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + // {0} -> uses data stream, reads up to 8 bytes. + FuzzingBitGen bitgen(data, {0}); + EXPECT_EQ(bitgen(), 0x0807060504030201); + EXPECT_EQ(bitgen(), 0x0A09); + EXPECT_EQ(bitgen(), 0x0807060504030201); +} + +TEST(FuzzingBitGenTest, OperatorUsesPcgForEmptyStreams) { + FuzzingBitGen bitgen({}, {}); + uint64_t v1 = bitgen(); + uint64_t v2 = bitgen(); + FuzzingBitGen bitgen2({}, {}); + EXPECT_EQ(bitgen2(), v1); + EXPECT_EQ(bitgen2(), v2); +} + +TEST(FuzzingBitGenTest, OperatorUsesInstructionStream) { + std::vector data = {1, 2, 3, 4, 5, 6, 7, 8}; + // 0: default (8 bytes), 1: min, 2: max, 3: mean + std::vector instructions = {0, 1, 2, 3}; + FuzzingBitGen bitgen(data, instructions); + EXPECT_EQ(bitgen(), 0x0807060504030201); + EXPECT_EQ(bitgen(), 0); + EXPECT_EQ(bitgen(), std::numeric_limits::max()); + EXPECT_EQ(bitgen(), std::numeric_limits::max() / 2); +} + +TEST(FuzzingBitGenTest, StreamsWrapAround) { + std::vector data = {1, 2}; + // Reads up to 8 bytes, but only 2 available. + std::vector instructions = {0}; + FuzzingBitGen bitgen(data, instructions); + EXPECT_EQ(bitgen(), 0x0201); + EXPECT_EQ(bitgen(), 0x0201); +} + +TEST(FuzzingBitGenTest, MockingIsRepeatable) { + std::vector data = {1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, + 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3}; + FuzzingBitGen bg1(data); + absl::BitGenRef ref1(bg1); + int v_a = absl::Uniform(ref1, 0, 100); + int v_b = absl::Uniform(ref1, 0, 100); + int v_c = absl::Uniform(ref1, 0, 100); + uint64_t v_d = ref1(); + + FuzzingBitGen bg2(data); + absl::BitGenRef ref2(bg2); + EXPECT_EQ(absl::Uniform(ref2, 0, 100), v_a); + EXPECT_EQ(absl::Uniform(ref2, 0, 100), v_b); + EXPECT_EQ(absl::Uniform(ref2, 0, 100), v_c); + EXPECT_EQ(ref2(), v_d); +} + +} // namespace +} // namespace fuzztest diff --git a/fuzztest/internal/BUILD b/fuzztest/internal/BUILD index 0ea46b829..a2da5c71c 100644 --- a/fuzztest/internal/BUILD +++ b/fuzztest/internal/BUILD @@ -315,7 +315,17 @@ cc_library( "@abseil-cpp//absl/functional:function_ref", "@abseil-cpp//absl/random:distributions", "@abseil-cpp//absl/types:span", - "@com_google_fuzztest//common:logging", + ], +) + +cc_test( + name = "register_fuzzing_mocks_test", + srcs = ["register_fuzzing_mocks_test.cc"], + deps = [ + "@abseil-cpp//absl/random", + "@abseil-cpp//absl/random:bit_gen_ref", + "@com_google_fuzztest//fuzztest:fuzzing_bit_gen", + "@googletest//:gtest_main", ], ) diff --git a/fuzztest/internal/domains/arbitrary_impl.h b/fuzztest/internal/domains/arbitrary_impl.h index f6b170258..559b29a8c 100644 --- a/fuzztest/internal/domains/arbitrary_impl.h +++ b/fuzztest/internal/domains/arbitrary_impl.h @@ -449,8 +449,8 @@ AggregateOfImpl...> // improve if possible. template ()> decltype(DetectAggregateOfImpl2( - BindAggregate(std::declval(), std::integral_constant{}))) -DetectAggregateOfImpl(); + BindAggregate(std::declval(), + std::integral_constant{}))) DetectAggregateOfImpl(); template class ArbitraryImpl< @@ -584,13 +584,19 @@ class ArbitraryImpl // Arbitrary for absl::BitGenRef. template <> class ArbitraryImpl - : public BitGenRefDomain, - ArbitraryImpl>> { - using InnerContainer = + : public BitGenRefDomain< + SequenceContainerOfImpl, ArbitraryImpl>, + SequenceContainerOfImpl, + ArbitraryImpl>> { + using DataSequence = + SequenceContainerOfImpl, ArbitraryImpl>; + using InstructionSequence = SequenceContainerOfImpl, ArbitraryImpl>; public: - ArbitraryImpl() : BitGenRefDomain(InnerContainer{}.WithMinSize(8)) {} + ArbitraryImpl() + : BitGenRefDomain(DataSequence{}.WithMinSize(15), + InstructionSequence{}.WithMinSize(7)) {} }; } // namespace fuzztest::internal diff --git a/fuzztest/internal/domains/bit_gen_ref.h b/fuzztest/internal/domains/bit_gen_ref.h index 824d35e88..9d71c46bf 100644 --- a/fuzztest/internal/domains/bit_gen_ref.h +++ b/fuzztest/internal/domains/bit_gen_ref.h @@ -39,35 +39,41 @@ namespace fuzztest::internal { // destroyed when CleanupBitGen is called. class BitGenCorpusValue { public: - using InitializerData = std::vector; using URBG = FuzzingBitGen; - explicit BitGenCorpusValue(InitializerData data) - : initializer_data_(std::move(data)) {} + explicit BitGenCorpusValue(std::vector data, + std::vector instruction) + : data_(std::move(data)), instruction_(std::move(instruction)) {} ~BitGenCorpusValue() { CleanupBitGen(); } // Copy and move do not initialize the internal URBG instance. BitGenCorpusValue(const BitGenCorpusValue& o) - : initializer_data_(o.initializer_data_), bitgen_(std::nullopt) {} + : data_(o.data_), instruction_(o.instruction_), bitgen_(std::nullopt) {} BitGenCorpusValue& operator=(const BitGenCorpusValue& o) { // The internal URBG should be unused. FUZZTEST_CHECK(!bitgen_.has_value()); - initializer_data_ = o.initializer_data_; + data_ = o.data_; + instruction_ = o.instruction_; return *this; } BitGenCorpusValue(BitGenCorpusValue&& o) - : initializer_data_(std::move(o.initializer_data_)), + : data_(std::move(o.data_)), + instruction_(std::move(o.instruction_)), bitgen_(std::nullopt) {} BitGenCorpusValue& operator=(BitGenCorpusValue&& o) { // The internal URBG should be unused. FUZZTEST_CHECK(!o.bitgen_.has_value()); FUZZTEST_CHECK(!bitgen_.has_value()); - initializer_data_ = std::move(o.initializer_data_); + data_ = std::move(o.data_); + instruction_ = std::move(o.instruction_); return *this; } - InitializerData& initializer_data() { return initializer_data_; } - const InitializerData& initializer_data() const { return initializer_data_; } + std::vector& data() { return data_; } + const std::vector& data() const { return data_; } + + std::vector& instruction() { return instruction_; } + const std::vector& instruction() const { return instruction_; } // Cleanup the internal URBG instance. void CleanupBitGen() { bitgen_.reset(); } @@ -77,16 +83,14 @@ class BitGenCorpusValue { // NOTE: The returned reference is valid until the next call to CleanupBitGen. URBG& GetBitGen() const { if (!bitgen_.has_value()) { - bitgen_.emplace(initializer_data_); + bitgen_.emplace(data_, instruction_); } return *bitgen_; } private: - // Underlying fuzzed data stream; the input to the URBG constructor. - // When using util_random::FuzzingBitGen, this is a vector of uint8_t which - // defines the sequence of random variates. - std::vector initializer_data_; + std::vector data_; // fuzztest generated data stream. + std::vector instruction_; // fuzztest generated instruction stream. mutable std::optional bitgen_; }; @@ -99,21 +103,23 @@ class BitGenCorpusValue { // // The domain accepts an input "data stream" corpus which is used to initialize // a FuzzingBitGen instance. This internal FuzzingBitGen instance is bound to an -// absl::BitGenRef when GetValue is called. +// absl::BitGenRef when GetValue is called. Both the data and instruction +// streams are reused (wrapped around) when they are exhausted. // // BitGenRefDomain does not support seeded domains. // BitGenRefDomain does not support GetRandomValue. -template -class BitGenRefDomain - : public domain_implementor::DomainBase, - /*value_type=*/absl::BitGenRef, - /*corpus_type=*/BitGenCorpusValue> { +template +class BitGenRefDomain : public domain_implementor::DomainBase< + BitGenRefDomain, + /*value_type=*/absl::BitGenRef, + /*corpus_type=*/BitGenCorpusValue> { public: using typename BitGenRefDomain::DomainBase::corpus_type; using typename BitGenRefDomain::DomainBase::value_type; - explicit BitGenRefDomain(const Inner& inner) : inner_(inner) {} - explicit BitGenRefDomain(Inner&& inner) : inner_(std::move(inner)) {} + explicit BitGenRefDomain(const DataSequence& data, + const InstructionSequence& instruction) + : data_(data), instruction_(instruction) {} BitGenRefDomain(const BitGenRefDomain&) = default; BitGenRefDomain(BitGenRefDomain&&) = default; @@ -121,13 +127,15 @@ class BitGenRefDomain BitGenRefDomain& operator=(BitGenRefDomain&&) = default; corpus_type Init(absl::BitGenRef prng) { - return corpus_type{inner_.Init(prng)}; + return corpus_type{data_.Init(prng), instruction_.Init(prng)}; } void Mutate(corpus_type& corpus_value, absl::BitGenRef prng, const domain_implementor::MutationMetadata& metadata, bool only_shrink) { corpus_value.CleanupBitGen(); - inner_.Mutate(corpus_value.initializer_data(), prng, metadata, only_shrink); + data_.Mutate(corpus_value.data(), prng, metadata, only_shrink); + instruction_.Mutate(corpus_value.instruction(), prng, metadata, + only_shrink); } absl::BitGenRef GetValue(const corpus_type& corpus_value) const { @@ -144,23 +152,38 @@ class BitGenRefDomain return std::nullopt; } absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const { - return inner_.ValidateCorpusValue(corpus_value.initializer_data()); + absl::Status status; + status.Update(data_.ValidateCorpusValue(corpus_value.data())); + status.Update(instruction_.ValidateCorpusValue(corpus_value.instruction())); + return status; } + void UpdateMemoryDictionary( const corpus_type& corpus_value, domain_implementor::ConstCmpTablesPtr cmp_tables) { - return inner_.UpdateMemoryDictionary(corpus_value.initializer_data(), - cmp_tables); + data_.UpdateMemoryDictionary(corpus_value.data(), cmp_tables); + instruction_.UpdateMemoryDictionary(corpus_value.instruction(), cmp_tables); } + std::optional ParseCorpus(const internal::IRObject& obj) const { - auto x = inner_.ParseCorpus(obj); - if (x.has_value()) { - return corpus_type(*std::move(x)); + auto container = obj.Subs(); + if (container && container->size() == 2) { + auto x = data_.ParseCorpus((*container)[0]); + auto y = instruction_.ParseCorpus((*container)[1]); + if (x.has_value() && y.has_value()) { + return corpus_type(*std::move(x), *std::move(y)); + } } return std::nullopt; } + internal::IRObject SerializeCorpus(const corpus_type& corpus_value) const { - return inner_.SerializeCorpus(corpus_value.initializer_data()); + internal::IRObject obj; + auto& v = obj.MutableSubs(); + v.reserve(2); + v.emplace_back(data_.SerializeCorpus(corpus_value.data())); + v.emplace_back(instruction_.SerializeCorpus(corpus_value.instruction())); + return obj; } auto GetPrinter() const { return Printer{}; } @@ -174,7 +197,8 @@ class BitGenRefDomain } }; - Inner inner_; + DataSequence data_; + InstructionSequence instruction_; }; } // namespace fuzztest::internal diff --git a/fuzztest/internal/register_fuzzing_mocks.cc b/fuzztest/internal/register_fuzzing_mocks.cc index 6bb05df37..7bc966445 100644 --- a/fuzztest/internal/register_fuzzing_mocks.cc +++ b/fuzztest/internal/register_fuzzing_mocks.cc @@ -15,7 +15,6 @@ #include "./fuzztest/internal/register_fuzzing_mocks.h" #include -#include #include #include #include @@ -23,6 +22,7 @@ #include #include #include +#include #include "absl/base/fast_type_id.h" #include "absl/functional/function_ref.h" @@ -35,147 +35,74 @@ #include "absl/random/poisson_distribution.h" #include "absl/random/zipf_distribution.h" #include "absl/types/span.h" -#include "./common/logging.h" namespace fuzztest::internal { namespace { -// Reference type to consume bytes from a data stream; these are used by -// the fuzzing bitgen distribution implementations. -struct DataStreamConsumer { - // This is a reference to the fuzzing data stream since the mutations - // (src.remove_prefix(...), etc.) are applied to the source stream. - absl::Span& src; - - // Consumes up to num_bytes from the head of the data stream. - size_t ConsumeHead(void* destination, size_t num_bytes) { - num_bytes = std::min(num_bytes, src.size()); - std::memcpy(destination, src.data(), num_bytes); - src.remove_prefix(num_bytes); - return num_bytes; - } +enum class Instruction : uint8_t { + kDefault = 0, + kMin = 1, + kMax = 2, + kMean = 3, +}; - // Consumes up to num_bytes from the tail of the data stream. - size_t ConsumeTail(void* destination, size_t num_bytes) { - num_bytes = std::min(num_bytes, src.size()); - std::memcpy(destination, src.data() + src.size() - num_bytes, num_bytes); - src.remove_suffix(num_bytes); - return num_bytes; - } +class ImplURBG { + public: + InstructionStream instruction_stream_; + DataStream data_stream_; - // Consumes a T from the head of the data stream. - template - T ConsumeHead() { - std::conditional_t, uint8_t, T> x{}; - ConsumeHead(&x, sizeof(x)); - if constexpr (std::is_same_v) { - return static_cast(x & 1); - } else { - return x; + // Extensions used by the fuzzing functions. + Instruction instruction() { + return static_cast(unmasked_instruction() & 0x03); + } + uint8_t unmasked_instruction() { + auto& [data, i] = instruction_stream_; + uint8_t instruction = data.empty() ? 0 : data[i++]; + if (i >= data.size()) { + i = 0; } + return instruction; } - // Consumes a T from the tail of the data stream. + // Returns an integral value of type T, or 0 if there's no data left. template - T ConsumeTail() { - std::conditional_t, uint8_t, T> x{}; - ConsumeTail(&x, sizeof(x)); - if constexpr (std::is_same_v) { - return static_cast(x & 1); - } else { - return x; + std::enable_if_t, T> get_int_value() { + auto& [data, i] = data_stream_; + if (data.empty()) { + return 0; } - } + if (i >= data.size()) { + i = 0; + } + T x = 0; + size_t num_bytes = std::min(sizeof(x), data.size() - i); + std::memcpy(&x, data.data() + i, num_bytes); - // Returns a real value in the range [0.0, 1.0]. - template - T ConsumeProbability() { - static_assert(std::is_floating_point_v && sizeof(T) <= sizeof(uint64_t), - "A floating point type is required."); - using IntegralType = - typename std::conditional_t<(sizeof(T) <= sizeof(uint32_t)), uint32_t, - uint64_t>; - auto int_value = ConsumeTail(); - return static_cast(int_value) / - static_cast(std::numeric_limits::max()); + i += num_bytes; + return x; } - // Returns a value in the closed-closed range [min, max]. - template - T ConsumeValueInRange(T min, T max) { - FUZZTEST_CHECK_LE(min, max); - - if (min == max) return min; + // URBG interface. + using result_type = uint64_t; - // Return the min or max value more frequently. - uint8_t byte = ConsumeHead(); - if (byte == 0) { - return min; - } else if (byte == 1) { - return max; - } - byte >>= 1; - - return ConsumeValueInRangeImpl(min, max, byte); + static constexpr result_type(min)() { + return (std::numeric_limits::min)(); } - - private: - // Returns a real value in the range [min, max] - template - std::enable_if_t, T> // - ConsumeValueInRangeImpl(T min, T max, uint8_t byte) { - static_assert(sizeof(T) <= sizeof(uint64_t), "Unsupported float type."); - // Returns a floating point value in the given range by consuming bytes - // from the input data. If there's no input data left, returns |min|. Note - // that |min| must be less than or equal to |max|. - T range = .0; - T result = min; - constexpr T zero(.0); - if (max > zero && min < zero && max > min + std::numeric_limits::max()) { - // The diff |max - min| would overflow the given floating point type. - // Use the half of the diff as the range and consume a bool to decide - // whether the result is in the first of the second part of the diff. - range = (max / 2.0) - (min / 2.0); - if (byte & 1) { - result += range; - } - } else { - range = max - min; - } - return result + range * ConsumeProbability(); + static constexpr result_type(max)() { + return (std::numeric_limits::max)(); } - // Returns an integral value in the range [min, max] - template - std::enable_if_t, T> // - ConsumeValueInRangeImpl(T min, T max, uint8_t) { - static_assert(sizeof(T) <= sizeof(uint64_t), "Unsupported integral type."); - - // Use the biggest type possible to hold the range and the result. - uint64_t range = static_cast(max) - static_cast(min); - uint64_t result = 0; - size_t offset = 0; - while (offset < sizeof(T) * CHAR_BIT && (range >> offset) > 0 && - !src.empty()) { - uint8_t byte = src.back(); - src.remove_suffix(1); - result = (result << CHAR_BIT) | byte; - offset += CHAR_BIT; - } - - // Avoid division by 0, in case |range + 1| results in overflow. - if (range != std::numeric_limits::max()) { - result = result % (range + 1); - } + void reset() { data_stream_.second = 0; } - return static_cast(static_cast(min) + result); - } + result_type operator()() { return get_int_value(); } }; // ----------------------------------------------------------------------------- // Bernoulli -struct ImplBernoulli : public DataStreamConsumer { +struct ImplBernoulli { + ImplURBG urbg; + using DistrT = absl::bernoulli_distribution; using ArgTupleT = std::tuple; using ResultT = bool; @@ -183,150 +110,256 @@ struct ImplBernoulli : public DataStreamConsumer { ResultT operator()(double p) { // Just generate a boolean; mostly ignoring p. // The 0/1 cases are special cased to avoid returning false on constants. - if (p == 0.0) { + if (p <= 0.0) { return false; - } else if (p == 1.0) { + } else if (p >= 1.0) { return true; - } else { - return ConsumeHead(); } + switch (urbg.instruction()) { + case Instruction::kMin: + return false; + case Instruction::kMax: + return true; + case Instruction::kMean: + return p >= 0.5; + default: + break; + } + return urbg.get_int_value() & 1; } }; // Beta template -struct ImplBeta : public DataStreamConsumer { +struct ImplBeta { + ImplURBG urbg; + using DistrT = absl::beta_distribution; using ArgTupleT = std::tuple; using ResultT = RealType; ResultT operator()(RealType a, RealType b) { - if (!src.empty()) { - auto x = ConsumeTail(); - if (std::isfinite(x)) { - return x; - } + switch (urbg.instruction()) { + case Instruction::kMin: + return 0.0; + case Instruction::kMax: + return 1.0; + case Instruction::kMean: + return a / (a + b); // mean + default: + break; } - return a / (a + b); // mean + return DistrT(a, b)(urbg); } }; // Exponential template -struct ImplExponential : public DataStreamConsumer { +struct ImplExponential { + ImplURBG urbg; + using DistrT = absl::exponential_distribution; using ArgTupleT = std::tuple; using ResultT = RealType; ResultT operator()(RealType lambda) { - if (!src.empty()) { - auto x = ConsumeTail(); - if (std::isfinite(x)) { - return x; - } + switch (urbg.instruction()) { + case Instruction::kMin: + return 0; + case Instruction::kMean: + return RealType{1} / lambda; // mean + default: + break; } - return RealType{1} / lambda; // mean + return DistrT(lambda)(urbg); } }; // Gaussian template -struct ImplGaussian : public DataStreamConsumer { +struct ImplGaussian { + ImplURBG urbg; + using DistrT = absl::gaussian_distribution; using ArgTupleT = std::tuple; using ResultT = RealType; ResultT operator()(RealType mean, RealType sigma) { - if (src.empty()) return mean; const auto ten_sigma = sigma * 10; - RealType min = mean - ten_sigma; - RealType max = mean + ten_sigma; - return ConsumeValueInRange(min, max); + switch (urbg.instruction()) { + case Instruction::kMean: + return mean; + case Instruction::kDefault: + // this makes unlikely values much more likely. + return absl::uniform_real_distribution( + mean - ten_sigma, mean + ten_sigma)(urbg); + default: + break; + } + return DistrT(mean, sigma)(urbg); } }; // LogUniform template -struct ImplLogUniform : public DataStreamConsumer { +struct ImplLogUniform { + ImplURBG urbg; + using DistrT = absl::log_uniform_int_distribution; using ArgTupleT = std::tuple; using ResultT = IntType; - ResultT operator()(IntType a, IntType b, IntType) { - if (src.empty()) return a; - return ConsumeValueInRange(a, b); + ResultT operator()(IntType a, IntType b, IntType c) { + switch (urbg.instruction()) { + case Instruction::kMin: + return a; + case Instruction::kMax: + return b; + case Instruction::kMean: + if (a > 0 && b > 0) { + return static_cast((b - a) / std::log(b / a)); + } + break; + default: + break; + } + return DistrT(a, b, c)(urbg); } }; // Poisson template -struct ImplPoisson : public DataStreamConsumer { +struct ImplPoisson { + ImplURBG urbg; + using DistrT = absl::poisson_distribution; using ArgTupleT = std::tuple; using ResultT = IntType; - ResultT operator()(double) { - if (src.empty()) return 0; - return ConsumeValueInRange(0, std::numeric_limits::max()); + ResultT operator()(double lambda) { + switch (urbg.instruction()) { + case Instruction::kMin: + return 0; + case Instruction::kMean: + return static_cast(lambda); + default: + break; + } + return DistrT(lambda)(urbg); } }; // Zipf template -struct ImplZipf : public DataStreamConsumer { +struct ImplZipf { + ImplURBG urbg; + using DistrT = absl::zipf_distribution; using ArgTupleT = std::tuple; using ResultT = IntType; - ResultT operator()(IntType a, double, double) { - if (src.empty()) return 0; - return ConsumeValueInRange(0, a); + ResultT operator()(IntType a, double b, double c) { + switch (urbg.instruction()) { + case Instruction::kMin: + return 0; + case Instruction::kMax: + return a; + default: + break; + } + return DistrT(a, b, c)(urbg); } }; // Uniform template -struct ImplUniform : public DataStreamConsumer { +struct ImplUniform { + ImplURBG urbg; using DistrT = absl::random_internal::UniformDistributionWrapper; using ResultT = R; ResultT operator()(absl::IntervalClosedClosedTag, R min, R max) { - if (src.empty()) return min; - return ConsumeValueInRange(min, max); + if constexpr (std::is_floating_point_v) { + return operator()(absl::IntervalClosedOpen, min, + std::nexttoward(max, std::numeric_limits::max())); + } + // Only int-typed calls should reach here. + uint8_t instruction = urbg.unmasked_instruction(); + switch (static_cast(instruction & 0x03)) { + case Instruction::kMin: + return min; + case Instruction::kMax: + return max; + case Instruction::kMean: + return min + ((max - min) / 2); + default: + break; + } + if constexpr (std::is_integral_v) { + if constexpr (sizeof(R) <= sizeof(uint8_t)) { + uint64_t range = + static_cast(max) - static_cast(min); + if (range == 0) { + return min; + } else if ((range & (range + 1)) == 0) { + // Consume fewer bytes of the data_stream when dealing with a power + // of 2 range. + if (range <= std::numeric_limits::max()) { + return min + static_cast(urbg.get_int_value() & range); + } + if (range <= std::numeric_limits::max()) { + return min + static_cast(urbg.get_int_value() & range); + } + if (range <= std::numeric_limits::max()) { + return min + static_cast(urbg.get_int_value() & range); + } + return min + static_cast(urbg.get_int_value() & range); + } + } + // Fallback to absl::uniform_int_distribution. + return absl::uniform_int_distribution(min, max)(urbg); + } else { + return 0; + } } ResultT operator()(absl::IntervalClosedOpenTag, R min, R max) { - if (src.empty()) return min; + if constexpr (std::is_integral_v) { + return operator()(absl::IntervalClosedClosed, min, max - 1); + } + // Only real-typed calls should reach here. + switch (urbg.instruction()) { + case Instruction::kMin: + return min; + case Instruction::kMax: + return std::nexttoward(max, std::numeric_limits::min()); + case Instruction::kMean: + return min + ((max - min) / 2); + default: + break; + } if constexpr (std::is_floating_point_v) { - max = std::nexttoward(max, min); - return ConsumeValueInRange(min, max); + return absl::uniform_real_distribution(min, max)(urbg); } else { - max--; - return ConsumeValueInRange(min, max); + return 0; } } ResultT operator()(absl::IntervalOpenOpenTag, R min, R max) { - if (src.empty()) return min; if constexpr (std::is_floating_point_v) { - min = std::nexttoward(min, max); - max = std::nexttoward(max, min); - return ConsumeValueInRange(min, max); + return operator()(absl::IntervalClosedOpen, std::nexttoward(min, max), + max); } else { - min++; - max--; - return ConsumeValueInRange(min, max); + return operator()(absl::IntervalClosedOpen, min + 1, max); } } ResultT operator()(absl::IntervalOpenClosedTag, R min, R max) { - if (src.empty()) return min; if constexpr (std::is_floating_point_v) { - min = std::nexttoward(min, max); - return ConsumeValueInRange(min, max); + return operator()(absl::IntervalClosedClosed, std::nexttoward(min, max), + max); } else { - min++; - return ConsumeValueInRange(min, max); + return operator()(absl::IntervalClosedClosed, min + 1, max); } } @@ -336,23 +369,24 @@ struct ImplUniform : public DataStreamConsumer { ResultT operator()() { static_assert(std::is_unsigned_v); - if (src.empty()) return 0; - return ConsumeTail(); + return operator()(absl::IntervalClosedClosed, 0, + (std::numeric_limits::max)()); } }; // ----------------------------------------------------------------------------- -// InvokeFuzzFunction is a type-erased function pointer which is responsible for -// casting the args_tuple and result parameters to the correct types and then -// invoking the implementation functor. It is important that the ArgsTupleT and -// ResultT types match the types of the distribution and the implementation -// functions, so the HandleFuzzedFunction overloads are used to determine the -// correct types. +// InvokeFuzzFunction is a type-erased function pointer which is responsible +// for casting the args_tuple and result parameters to the correct types and +// then invoking the implementation functor. It is important that the +// ArgsTupleT and ResultT types match the types of the distribution and the +// implementation functions, so the HandleFuzzedFunction overloads are used to +// determine the correct types. template -void InvokeFuzzFunction(absl::Span& src, void* args_tuple, +void InvokeFuzzFunction(InstructionStream& instruction_stream, + DataStream& data_stream, void* args_tuple, void* result) { - FuzzFunctionT fn{src}; + FuzzFunctionT fn{ImplURBG{instruction_stream, data_stream}}; *static_cast(result) = absl::apply(fn, *static_cast(args_tuple)); } diff --git a/fuzztest/internal/register_fuzzing_mocks.h b/fuzztest/internal/register_fuzzing_mocks.h index 7726c2256..44c5f2f82 100644 --- a/fuzztest/internal/register_fuzzing_mocks.h +++ b/fuzztest/internal/register_fuzzing_mocks.h @@ -15,7 +15,9 @@ #ifndef FUZZTEST_FUZZTEST_INTERNAL_REGISTER_FUZZING_MOCKS_H_ #define FUZZTEST_FUZZTEST_INTERNAL_REGISTER_FUZZING_MOCKS_H_ +#include #include +#include #include "absl/base/fast_type_id.h" #include "absl/functional/function_ref.h" @@ -23,9 +25,13 @@ namespace fuzztest::internal { -// TypeErasedFuzzFunctionT(datastream, args_tuple, result) is a type erased -// function pointer for use with absl::MockingBitGen and fuzztest mocking. -using TypeErasedFuzzFunctionT = void (*)(absl::Span&, void*, +using InstructionStream = std::pair, size_t&>; +using DataStream = std::pair, size_t&>; + +// TypeErasedFuzzFunctionT(instruction_stream, data_stream, args_tuple, result) +// is a type erased function pointer for use with absl::MockingBitGen and +// fuzztest mocking. +using TypeErasedFuzzFunctionT = void (*)(InstructionStream&, DataStream&, void*, void*); // Registers the fuzzing functions for Abseil distributions. diff --git a/fuzztest/internal/register_fuzzing_mocks_test.cc b/fuzztest/internal/register_fuzzing_mocks_test.cc new file mode 100644 index 000000000..5748e1a9d --- /dev/null +++ b/fuzztest/internal/register_fuzzing_mocks_test.cc @@ -0,0 +1,118 @@ +// 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 +// +// http://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 +#include + +#include "gtest/gtest.h" +#include "absl/random/bit_gen_ref.h" +#include "absl/random/random.h" +#include "./fuzztest/fuzzing_bit_gen.h" + +namespace fuzztest { +namespace { + +// Tests for the absl/random distribution functions which use the +// fuzztest::internal::RegisterAbslRandomFuzzingMocks() function. + +TEST(FuzzingBitGenTest, BernoulliDistributionUsesMock) { + FuzzingBitGen bitgen({}, {1, 2, 3}); + absl::BitGenRef ref(bitgen); + + EXPECT_FALSE(absl::Bernoulli(ref, 0.5)); // min + EXPECT_TRUE(absl::Bernoulli(ref, 0.5)); // max + EXPECT_TRUE(absl::Bernoulli(ref, 0.6)); // mean +} + +TEST(FuzzingBitGenTest, BetaDistributionUsesMock) { + FuzzingBitGen bitgen({}, {1, 2, 3}); + absl::BitGenRef ref(bitgen); + + EXPECT_DOUBLE_EQ(absl::Beta(ref, 1.0, 1.0), 0.0); // min + EXPECT_DOUBLE_EQ(absl::Beta(ref, 1.0, 1.0), 1.0); // max + EXPECT_DOUBLE_EQ(absl::Beta(ref, 2.0, 2.0), 0.5); // mean +} + +TEST(FuzzingBitGenTest, ExponentialDistributionUsesMock) { + FuzzingBitGen bitgen({}, {1, 3}); + absl::BitGenRef ref(bitgen); + + EXPECT_DOUBLE_EQ(absl::Exponential(ref, 1.0), 0.0); // min + EXPECT_DOUBLE_EQ(absl::Exponential(ref, 2.0), 0.5); // mean +} + +TEST(FuzzingBitGenTest, GaussianDistributionUsesMock) { + FuzzingBitGen bitgen({}, {3}); + absl::BitGenRef ref(bitgen); + + EXPECT_DOUBLE_EQ(absl::Gaussian(ref, 10.0, 1.0), 10.0); // mean +} + +TEST(FuzzingBitGenTest, LogUniformDistributionUsesMock) { + FuzzingBitGen bitgen({}, {1, 2, 3}); + absl::BitGenRef ref(bitgen); + + EXPECT_EQ(absl::LogUniform(ref, 10, 1000), 10); // min + EXPECT_EQ(absl::LogUniform(ref, 10, 1000), 1000); // max + EXPECT_EQ(absl::LogUniform(ref, 10, 1000), 214); // mean (approx) +} + +TEST(FuzzingBitGenTest, PoissonDistributionUsesMock) { + FuzzingBitGen bitgen({}, {1, 3}); + absl::BitGenRef ref(bitgen); + + EXPECT_EQ(absl::Poisson(ref, 10.0), 0); // min + EXPECT_EQ(absl::Poisson(ref, 10.0), 10); // mean +} + +TEST(FuzzingBitGenTest, ZipfDistributionUsesMock) { + FuzzingBitGen bitgen({}, {1, 2}); + absl::BitGenRef ref(bitgen); + + EXPECT_EQ(absl::Zipf(ref, 100, 2.0, 1.0), 0); // min + EXPECT_EQ(absl::Zipf(ref, 100, 2.0, 1.0), 100); // max +} + +TEST(FuzzingBitGenTest, UniformDistributionUInt) { + FuzzingBitGen bitgen({1, 2, 3, 4}, {0, 1, 2, 3}); + absl::BitGenRef ref(bitgen); + + EXPECT_EQ(absl::Uniform(ref), 0x0201); // default + EXPECT_EQ(absl::Uniform(ref), 0); // min + EXPECT_EQ(absl::Uniform(ref), + std::numeric_limits::max()); // max + EXPECT_EQ(absl::Uniform(ref), + std::numeric_limits::max() / 2); // mean + EXPECT_EQ(absl::Uniform(ref), 0x0403); // default +} + +TEST(FuzzingBitGenTest, UniformDistributionInt) { + FuzzingBitGen bitgen({}, {1, 2, 3}); + absl::BitGenRef ref(bitgen); + + EXPECT_EQ(absl::Uniform(ref, 0, 100), 0); // min + EXPECT_EQ(absl::Uniform(ref, 0, 100), 99); // max + EXPECT_EQ(absl::Uniform(ref, 0, 100), 49); // mean +} + +TEST(FuzzingBitGenTest, UniformDistributionReal) { + FuzzingBitGen bitgen_real({}, {1, 2, 3}); + absl::BitGenRef ref_real(bitgen_real); + EXPECT_DOUBLE_EQ(absl::Uniform(ref_real, 0.0, 100.0), 0.0); + EXPECT_LT(absl::Uniform(ref_real, 0.0, 100.0), 100.0); + EXPECT_DOUBLE_EQ(absl::Uniform(ref_real, 0.0, 100.0), 50.0); +} + +} // namespace +} // namespace fuzztest