Implement seeded domains by adding `.WithSeeds()` to domains. (#180)

Implement seeded domains by adding `.WithSeeds()` to domains.

Seeded domains occasionally return one of the seeds as a result of
`.Init()`.

NOTE: The seeds are accepted as user values and converted to corpus
values using
`.FromValue()`. As a consequence, setting the seeds won't work for
domains for
which `.FromValue()` fails (e.g., `MapOf`, `FlatMap`, and anything
derived from
them like `Arbitrary<absl::Duration>`). Moreover, currently
`.FromValue()`
doesn't validate the inputs, so it is possible to store a seed that
doesn't
meet the domain's constraints (e.g., `InRange(0, 10).WithSeeds({42})`
works).
This will be fixed separately.

The goal of the added unit tets is to almost always succeed when the
functionality is implemented and almost always fail when the
functionality is
missing. For most domains this usually means that it is sufficient to
check that
one or two seeds appear at least once among 1000 generated values, since
it is
unlikely that those seeds would have been generated otherwise. But this
is not
sufficient for domains with few elements. For example, for
`Arbitrary<bool>` we
are surely going to see `true` generated at least once regardless of
whether it
is a seed or not. For such domains I derived lower bounds for the number
of seed
appearances using the [Hoeffding's inequality][1] so that the tests are
robuts.

[1]: https://en.wikipedia.org/wiki/Binomial_distribution#Tail_bounds

Co-authored-by: Filip Niksic <fniksic@google.com>
diff --git a/domain_tests/BUILD b/domain_tests/BUILD
index b7ea683..bcce512 100644
--- a/domain_tests/BUILD
+++ b/domain_tests/BUILD
@@ -29,6 +29,7 @@
         "@com_google_absl//absl/random:bit_gen_ref",
         "@com_google_absl//absl/status",
         "@com_google_absl//absl/strings",
+        "@com_google_fuzztest//fuzztest:logging",
         "@com_google_fuzztest//fuzztest:meta",
         "@com_google_fuzztest//fuzztest:serialization",
         "@com_google_fuzztest//fuzztest:test_protobuf_cc_proto",
@@ -43,8 +44,10 @@
     srcs = ["aggregate_combinators_test.cc"],
     deps = [
         ":domain_testing",
+        "@com_google_absl//absl/container:flat_hash_set",
         "@com_google_absl//absl/random",
         "@com_google_absl//absl/types:optional",
+        "@com_google_absl//absl/types:span",
         "@com_google_absl//absl/types:variant",
         "@com_google_fuzztest//fuzztest:domain",
         "@com_google_googletest//:gtest_main",
@@ -63,6 +66,7 @@
         "@com_google_absl//absl/random:bit_gen_ref",
         "@com_google_absl//absl/status",
         "@com_google_absl//absl/time",
+        "@com_google_absl//absl/types:span",
         "@com_google_fuzztest//fuzztest:absl_helpers",
         "@com_google_fuzztest//fuzztest:domain",
         "@com_google_fuzztest//fuzztest:serialization",
@@ -80,7 +84,11 @@
         ":domain_testing",
         "@com_google_absl//absl/container:flat_hash_map",
         "@com_google_absl//absl/container:flat_hash_set",
+        "@com_google_absl//absl/random",
+        "@com_google_absl//absl/strings",
+        "@com_google_absl//absl/types:span",
         "@com_google_fuzztest//fuzztest:domain",
+        "@com_google_fuzztest//fuzztest:type_support",
         "@com_google_googletest//:gtest_main",
     ],
 )
@@ -92,6 +100,7 @@
         ":domain_testing",
         "@com_google_absl//absl/container:flat_hash_set",
         "@com_google_absl//absl/random",
+        "@com_google_absl//absl/types:span",
         "@com_google_fuzztest//fuzztest:domain",
         "@com_google_fuzztest//fuzztest:logging",
         "@com_google_fuzztest//fuzztest:type_support",
@@ -106,7 +115,9 @@
     deps = [
         ":domain_testing",
         "@com_google_absl//absl/algorithm:container",
+        "@com_google_absl//absl/container:flat_hash_set",
         "@com_google_absl//absl/random",
+        "@com_google_absl//absl/types:span",
         "@com_google_fuzztest//fuzztest:domain",
         "@com_google_fuzztest//fuzztest:type_support",
         "@com_google_googletest//:gtest_main",
@@ -121,8 +132,10 @@
         "@com_google_absl//absl/container:flat_hash_set",
         "@com_google_absl//absl/numeric:int128",
         "@com_google_absl//absl/random",
+        "@com_google_absl//absl/types:span",
         "@com_google_fuzztest//fuzztest:domain",
         "@com_google_fuzztest//fuzztest:meta",
+        "@com_google_fuzztest//fuzztest:type_support",
         "@com_google_googletest//:gtest_main",
     ],
 )
@@ -132,8 +145,14 @@
     srcs = ["numeric_domains_test.cc"],
     deps = [
         ":domain_testing",
+        "@com_google_absl//absl/container:flat_hash_set",
+        "@com_google_absl//absl/numeric:int128",
+        "@com_google_absl//absl/random",
+        "@com_google_absl//absl/status",
         "@com_google_absl//absl/strings",
+        "@com_google_absl//absl/types:span",
         "@com_google_fuzztest//fuzztest:domain",
+        "@com_google_fuzztest//fuzztest:serialization",
         "@com_google_googletest//:gtest_main",
     ],
 )
@@ -179,6 +198,7 @@
         ":domain_testing",
         "@com_google_absl//absl/container:flat_hash_set",
         "@com_google_absl//absl/random",
+        "@com_google_absl//absl/types:span",
         "@com_google_fuzztest//fuzztest:domain",
         "@com_google_googletest//:gtest_main",
     ],
diff --git a/domain_tests/CMakeLists.txt b/domain_tests/CMakeLists.txt
index ac4b76d..17d3003 100644
--- a/domain_tests/CMakeLists.txt
+++ b/domain_tests/CMakeLists.txt
@@ -41,8 +41,10 @@
   fuzztest_type_support
   fuzztest_serialization
   fuzztest_logging
-  absl::random_random
+  absl::flat_hash_set
   absl::optional
+  absl::random_random
+  absl::span
   absl::variant
   GTest::gmock_main
 )
@@ -72,6 +74,7 @@
   absl::random_random
   absl::random_bit_gen_ref
   absl::time
+  absl::span
   absl::status
   GTest::gmock_main
 )
@@ -96,6 +99,9 @@
   fuzztest_logging
   absl::flat_hash_map
   absl::flat_hash_set
+  absl::random_random
+  absl::span
+  absl::strings
   GTest::gmock_main
 )
 add_test(
@@ -119,6 +125,7 @@
   fuzztest_logging
   absl::flat_hash_set
   absl::random_random
+  absl::span
   re2
   fuzztest_regexp_dfa
   GTest::gmock_main
@@ -142,7 +149,9 @@
   fuzztest_type_support
   fuzztest_serialization
   fuzztest_logging
+  absl::flat_hash_set
   absl::random_random
+  absl::span
   GTest::gmock_main
 )
 add_test(
@@ -168,6 +177,7 @@
   absl::flat_hash_set
   absl::int128
   absl::random_random
+  absl::span
   GTest::gmock_main
 )
 add_test(
@@ -189,6 +199,11 @@
   fuzztest_type_support
   fuzztest_serialization
   fuzztest_logging
+  absl::flat_hash_set
+  absl::int128
+  absl::random_random
+  absl::span
+  absl::status
   absl::strings
   GTest::gmock_main
 )
@@ -280,6 +295,7 @@
   fuzztest_logging
   absl::flat_hash_set
   absl::random_random
+  absl::span
   GTest::gmock_main
 )
 add_test(
diff --git a/domain_tests/aggregate_combinators_test.cc b/domain_tests/aggregate_combinators_test.cc
index ab83bdb..609148e 100644
--- a/domain_tests/aggregate_combinators_test.cc
+++ b/domain_tests/aggregate_combinators_test.cc
@@ -14,23 +14,19 @@
 
 // Tests of StructOf, ConstructorOf, VariantOf and OptionalOf.
 
-#include <bitset>
-#include <cctype>
-#include <deque>
-#include <iterator>
-#include <list>
+#include <cstdint>
 #include <optional>
-#include <set>
 #include <string>
-#include <unordered_set>
 #include <utility>
 #include <variant>
 #include <vector>
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
+#include "absl/container/flat_hash_set.h"
 #include "absl/random/random.h"
 #include "absl/types/optional.h"
+#include "absl/types/span.h"
 #include "absl/types/variant.h"
 #include "./fuzztest/domain.h"
 #include "./domain_tests/domain_testing.h"
@@ -39,8 +35,9 @@
 namespace {
 
 using ::testing::_;
+using ::testing::AllOf;
 using ::testing::AnyOf;
-using ::testing::Eq;
+using ::testing::Contains;
 using ::testing::Optional;
 using ::testing::UnorderedElementsAre;
 using ::testing::VariantWith;
@@ -48,6 +45,10 @@
 struct MyStruct {
   int a;
   std::string s;
+
+  friend bool operator==(const MyStruct& lhs, const MyStruct& rhs) {
+    return lhs.a == rhs.a && lhs.s == rhs.s;
+  }
 };
 
 TEST(StructOf, InitGeneratesValidValues) {
@@ -64,6 +65,15 @@
   }
 }
 
+TEST(StructOf, InitGeneratesSeeds) {
+  auto domain = StructOf<MyStruct>(ElementOf({5, 10}), Arbitrary<std::string>())
+                    .WithSeeds({MyStruct{5, "Five"}, MyStruct{10, "Ten"}});
+
+  EXPECT_THAT(GenerateInitialValues(domain, 1000),
+              AllOf(Contains(Value(domain, MyStruct{5, "Five"})),
+                    Contains(Value(domain, MyStruct{10, "Ten"}))));
+}
+
 TEST(StructOf, MutateGeneratesValidValues) {
   auto domain =
       StructOf<MyStruct>(ElementOf({5, 10}), Arbitrary<std::string>());
@@ -206,6 +216,16 @@
   }
 }
 
+TEST(VariantOf, InitGeneratesSeeds) {
+  using X = std::variant<int, std::string>;
+  Domain<X> domain = VariantOf(Arbitrary<int>(), PrintableAsciiString())
+                         .WithSeeds({X{42}, X{"Hello"}});
+
+  EXPECT_THAT(GenerateInitialValues(domain, 1000),
+              AllOf(Contains(Value(domain, X{42})),
+                    Contains(Value(domain, X{"Hello"}))));
+}
+
 TEST(VariantOf, MutateGenerateValidValues) {
   using X = std::variant<int, int, std::string, std::vector<int>>;
   Domain<X> domain =
@@ -252,6 +272,15 @@
                                            Optional(2), Optional(3)));
 }
 
+TEST(OptionalOf, InitGeneratesSeeds) {
+  auto domain = OptionalOf(Arbitrary<int>())
+                    .WithSeeds({std::optional{7}, std::optional{42}});
+
+  EXPECT_THAT(GenerateInitialValues(domain, 1000),
+              AllOf(Contains(Value(domain, std::optional{7})),
+                    Contains(Value(domain, std::optional{42}))));
+}
+
 TEST(OptionalOf, MutateCanMakeValuesOrNull) {
   auto domain = OptionalOf(InRange(1, 3));
   Set<std::optional<int>> values;
diff --git a/domain_tests/arbitrary_domains_test.cc b/domain_tests/arbitrary_domains_test.cc
index ab2a97b..db0d649 100644
--- a/domain_tests/arbitrary_domains_test.cc
+++ b/domain_tests/arbitrary_domains_test.cc
@@ -30,6 +30,7 @@
 #include <vector>
 
 #include "google/protobuf/descriptor.h"
+#include "google/protobuf/util/message_differencer.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "absl/container/flat_hash_map.h"
@@ -38,9 +39,11 @@
 #include "absl/random/random.h"
 #include "absl/status/status.h"
 #include "absl/time/time.h"
+#include "absl/types/span.h"
 #include "./fuzztest/domain.h"
 #include "./domain_tests/domain_testing.h"
 #include "./fuzztest/internal/domains/absl_helpers.h"
+#include "./fuzztest/internal/domains/container_mutation_helpers.h"
 #include "./fuzztest/internal/serialization.h"
 #include "./fuzztest/internal/test_protobuf.pb.h"
 #include "./fuzztest/internal/type_support.h"
@@ -49,10 +52,13 @@
 namespace {
 
 using ::google::protobuf::FieldDescriptor;
+using ::testing::Contains;
 using ::testing::Each;
 using ::testing::ElementsAre;
 using ::testing::Ge;
 using ::testing::IsEmpty;
+using ::testing::IsTrue;
+using ::testing::ResultOf;
 using ::testing::SizeIs;
 using ::testing::UnorderedElementsAre;
 
@@ -73,6 +79,17 @@
   EXPECT_EQ(b.user_value, copy);
 }
 
+TEST(ArbitraryBoolTest, InitGeneratesSeeds) {
+  Domain<bool> domain = Arbitrary<bool>().WithSeeds({true});
+
+  EXPECT_THAT(GenerateInitialValues(domain, 1000),
+              Contains(Value(domain, true))
+                  // Since there are only two possible values, the seed will
+                  // surely appear at least once. To make the test meaningful,
+                  // we expect to see it much more often than the other value.
+                  .Times(Ge(650)));
+}
+
 struct MyStruct {
   int a;
   std::string s;
@@ -113,6 +130,19 @@
   EXPECT_EQ(values.size(), 100);
 }
 
+TYPED_TEST(CompoundTypeTest, InitGeneratesSeeds) {
+  // Seed cannot be a move-only type like std::unique_ptr<std::string>.
+  if constexpr (std::is_copy_constructible_v<TypeParam>) {
+    auto domain = Arbitrary<TypeParam>();
+    absl::BitGen bitgen;
+    auto seed = Value(domain, bitgen);
+    seed.RandomizeByRepeatedMutation(domain, bitgen);
+    domain.WithSeeds({seed.user_value});
+
+    EXPECT_THAT(GenerateInitialValues(domain, 1000), Contains(seed));
+  }
+}
+
 template <typename T>
 class MonostateTypeTest : public testing::Test {};
 
@@ -224,6 +254,21 @@
   EXPECT_THAT(i.user_value, j.user_value + 11);
 }
 
+TEST(ArbitraryProtocolBufferTest, InitGeneratesSeeds) {
+  internal::TestProtobuf seed;
+  seed.set_i32(42);
+  seed.set_str("Hello");
+
+  EXPECT_THAT(GenerateInitialValues(
+                  Arbitrary<internal::TestProtobuf>().WithSeeds({seed}), 1000),
+              Contains(ResultOf(
+                  [&seed](const auto& val) {
+                    return google::protobuf::util::MessageDifferencer::Equals(
+                        val.user_value, seed);
+                  },
+                  IsTrue())));
+}
+
 // TODO(b/246448769): Rewrite the test to decrease the chance of failure.
 TEST(ProtocolBuffer,
      RepeatedMutationEventuallyMutatesAllFieldsOfArbitraryProtobuf) {
@@ -614,6 +659,20 @@
   val.Mutate(domain, bitgen, true);
 }
 
+TEST(ArbitraryProtocolBufferEnum, InitGeneratesSeeds) {
+  auto domain = Arbitrary<internal::TestProtobuf_Enum>().WithSeeds(
+      {internal::TestProtobuf_Enum::TestProtobuf_Enum_Label5});
+
+  EXPECT_THAT(
+      GenerateInitialValues(domain, 1000),
+      Contains(
+          Value(domain, internal::TestProtobuf_Enum::TestProtobuf_Enum_Label5))
+          // Since there are only 5 enum elements, the seed will surely appear
+          // at least once. To make the test meaningful, we expect to see it at
+          // least half the time, unlike the other 4 elements.
+          .Times(Ge(500)));
+}
+
 TEST(ProtocolBuffer, CountNumberOfFieldsCorrect) {
   using T = internal::TestProtobuf;
   using SubT = internal::TestSubProtobuf;
diff --git a/domain_tests/container_combinators_test.cc b/domain_tests/container_combinators_test.cc
index 664124a..e599ad7 100644
--- a/domain_tests/container_combinators_test.cc
+++ b/domain_tests/container_combinators_test.cc
@@ -14,24 +14,31 @@
 
 // Tests of domain ContainerOf, and various shorthands such as VectorOf.
 
-#include <algorithm>
+#include <array>
+#include <cstdio>
 #include <deque>
 #include <initializer_list>
 #include <list>
+#include <map>
 #include <set>
 #include <string>
+#include <tuple>
 #include <type_traits>
+#include <unordered_map>
 #include <unordered_set>
 #include <utility>
-#include <variant>
 #include <vector>
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "absl/container/flat_hash_map.h"
 #include "absl/container/flat_hash_set.h"
+#include "absl/random/random.h"
+#include "absl/strings/str_cat.h"
+#include "absl/types/span.h"
 #include "./fuzztest/domain.h"
 #include "./domain_tests/domain_testing.h"
+#include "./fuzztest/internal/type_support.h"
 
 namespace fuzztest {
 namespace {
@@ -170,6 +177,16 @@
   GenerateValues(Arbitrary<TypeParam>().WithSize(7));
 }
 
+TYPED_TEST(ContainerTest, InitGeneratesSeeds) {
+  auto domain = Arbitrary<TypeParam>();
+  absl::BitGen bitgen;
+  auto seed = Value(domain, bitgen);
+  seed.RandomizeByRepeatedMutation(domain, bitgen);
+  domain.WithSeeds({seed.user_value});
+
+  EXPECT_THAT(GenerateInitialValues(domain, 1000), Contains(seed));
+}
+
 TEST(ContainerCombinatorTest, ValueTypeOfListContainerIsInferred) {
   for (const auto& value :
        GenerateValues(ContainerOf<std::list>(Positive<int>()).WithSize(3))) {
@@ -204,6 +221,23 @@
   }
 }
 
+TEST(SequencedContainerTest, InitGeneratesSeeds) {
+  auto domain =
+      ContainerOf<std::vector>(Arbitrary<int>()).WithSeeds({{1, 3, 3, 7}});
+
+  EXPECT_THAT(GenerateInitialValues(domain, 1000),
+              Contains(Value(domain, {1, 3, 3, 7})));
+}
+
+TEST(AssociativeContainerTest, InitGeneratesSeeds) {
+  auto domain = ContainerOf<absl::flat_hash_map<std::string, int>>(
+                    PairOf(Arbitrary<std::string>(), Arbitrary<int>()))
+                    .WithSeeds({{{"hello", 7}, {"world", 42}}});
+
+  EXPECT_THAT(GenerateInitialValues(domain, 1000),
+              Contains(Value(domain, {{"hello", 7}, {"world", 42}})));
+}
+
 TEST(ContainerCombinatorTest, VectorOf) {
   for (const auto& value : GenerateValues(VectorOf(InRange(-5, 5)))) {
     ASSERT_THAT(value.user_value, Each(IsInClosedRange(-5, 5)));
@@ -310,6 +344,15 @@
   }
 }
 
+TEST(UniqueElementsContainerTest, InitGeneratesSeeds) {
+  auto domain =
+      UniqueElementsContainerOf<std::unordered_multiset<int>>(Arbitrary<int>())
+          .WithSeeds({{1, 3, 3, 7}});
+
+  EXPECT_THAT(GenerateInitialValues(domain, 1000),
+              Contains(Value(domain, {1, 3, 7})));
+}
+
 TEST(ContainerCombinatorTest, UniqueElementsVectorOf) {
   for (const auto& value : GenerateValues(
            UniqueElementsVectorOf(InRange(100, 1000)).WithMaxSize(5))) {
@@ -410,11 +453,27 @@
                                                   IsInClosedRange(0, 127)))));
 }
 
+TEST(TupleOf, InitGeneratesSeeds) {
+  auto domain = TupleOf(Arbitrary<int>(), Arbitrary<std::string>())
+                    .WithSeeds({{42, "hello"}});
+
+  EXPECT_THAT(GenerateInitialValues(domain, 1000),
+              Contains(Value(domain, {42, "hello"})));
+}
+
 TEST(PairOf, GeneratesValidValues) {
   auto values = MutateUntilFoundN(PairOf(InRange(-5, 5), AsciiChar()), 100);
   EXPECT_THAT(values, Each(FieldsAre(_, FieldsAre(IsInClosedRange(-5, 5),
                                                   IsInClosedRange(0, 127)))));
 }
 
+TEST(PairOf, InitGeneratesSeeds) {
+  auto domain = PairOf(Arbitrary<int>(), Arbitrary<std::string>())
+                    .WithSeeds({{42, "hello"}});
+
+  EXPECT_THAT(GenerateInitialValues(domain, 1000),
+              Contains(Value(domain, {42, "hello"})));
+}
+
 }  // namespace
 }  // namespace fuzztest
diff --git a/domain_tests/domain_testing.h b/domain_tests/domain_testing.h
index 78760d9..798279b 100644
--- a/domain_tests/domain_testing.h
+++ b/domain_tests/domain_testing.h
@@ -38,6 +38,7 @@
 #include "absl/random/random.h"
 #include "absl/status/status.h"
 #include "absl/strings/str_cat.h"
+#include "./fuzztest/internal/logging.h"
 #include "./fuzztest/internal/meta.h"
 #include "./fuzztest/internal/serialization.h"
 #include "./fuzztest/internal/test_protobuf.pb.h"
@@ -120,11 +121,28 @@
       : corpus_value(other.corpus_value),
         user_value(domain.GetValue(corpus_value)) {}
 
+  Value(const Domain& domain, T user_value)
+      : corpus_value([&]() {
+          auto corpus_value = domain.FromValue(user_value);
+          FUZZTEST_INTERNAL_CHECK_PRECONDITION(corpus_value.has_value(),
+                                               "Invalid user_value!");
+          return *corpus_value;
+        }()),
+        user_value(std::move(user_value)) {}
+
   void Mutate(Domain& domain, absl::BitGenRef prng, bool only_shrink) {
     domain.Mutate(corpus_value, prng, only_shrink);
     user_value = domain.GetValue(corpus_value);
   }
 
+  void RandomizeByRepeatedMutation(Domain& domain, absl::BitGenRef prng) {
+    static constexpr int kMutations = 1000;
+    for (int i = 0; i < kMutations; ++i) {
+      domain.Mutate(corpus_value, prng, /*only_shrink=*/false);
+    }
+    user_value = domain.GetValue(corpus_value);
+  }
+
   // Make the Value hashable/comparable to put them in sets/maps.
   template <typename H>
   friend H AbslHashValue(H state, const Value& self) {
@@ -246,6 +264,17 @@
   return values;
 }
 
+template <typename Domain>
+auto GenerateInitialValues(Domain domain, int n) {
+  std::vector<Value<Domain>> values;
+  absl::BitGen bitgen;
+  values.reserve(n);
+  for (int i = 0; i < n; ++i) {
+    values.push_back(Value(domain, bitgen));
+  }
+  return values;
+}
+
 template <typename Values, typename Pred>
 void CheckValues(const Values& values, Pred pred) {
   for (const auto& value : values) {
diff --git a/domain_tests/in_regexp_domain_test.cc b/domain_tests/in_regexp_domain_test.cc
index 8e2df23..a139a34 100644
--- a/domain_tests/in_regexp_domain_test.cc
+++ b/domain_tests/in_regexp_domain_test.cc
@@ -23,6 +23,7 @@
 #include "gtest/gtest.h"
 #include "absl/container/flat_hash_set.h"
 #include "absl/random/random.h"
+#include "absl/types/span.h"
 #include "./fuzztest/domain.h"
 #include "./domain_tests/domain_testing.h"
 #include "./fuzztest/internal/logging.h"
@@ -32,6 +33,7 @@
 namespace fuzztest {
 namespace {
 
+using ::testing::Contains;
 using ::testing::Optional;
 using ::testing::ResultOf;
 using ::testing::StrEq;
@@ -67,6 +69,13 @@
   }
 }
 
+TEST(InRegexp, InitGeneratesSeeds) {
+  auto domain = InRegexp(R"re(a\w*b)re").WithSeeds({"a_Hello_World_b"});
+
+  EXPECT_THAT(GenerateInitialValues(domain, 1000),
+              Contains(Value(domain, "a_Hello_World_b")));
+}
+
 // TODO(changochen): Improve the tests to verify "close" mutation.
 TEST(InRegexp, MutationGeneratesDifferentValidValues) {
   absl::BitGen bitgen;
diff --git a/domain_tests/map_filter_combinator_test.cc b/domain_tests/map_filter_combinator_test.cc
index 71f69bd..7c07cc0 100644
--- a/domain_tests/map_filter_combinator_test.cc
+++ b/domain_tests/map_filter_combinator_test.cc
@@ -14,23 +14,19 @@
 
 // Tests of domains Map, FlatMap, and Filter.
 
-#include <bitset>
-#include <cctype>
-#include <deque>
-#include <iterator>
-#include <list>
+#include <cstddef>
 #include <optional>
-#include <set>
 #include <string>
-#include <unordered_set>
-#include <utility>
-#include <variant>
+#include <string_view>
+#include <tuple>
 #include <vector>
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "absl/algorithm/container.h"
+#include "absl/container/flat_hash_set.h"
 #include "absl/random/random.h"
+#include "absl/types/span.h"
 #include "./fuzztest/domain.h"
 #include "./domain_tests/domain_testing.h"
 #include "./fuzztest/internal/type_support.h"
@@ -38,6 +34,7 @@
 namespace fuzztest {
 namespace {
 
+using ::testing::Contains;
 using ::testing::Each;
 using ::testing::Eq;
 using ::testing::IsEmpty;
@@ -210,5 +207,19 @@
   VerifyRoundTripThroughConversion(value, domain);
 }
 
+TEST(Filter, InitGeneratesSeeds) {
+  auto domain = Filter([](int i) { return i % 2 == 0; }, Arbitrary<int>())
+                    .WithSeeds({42});
+
+  EXPECT_THAT(GenerateInitialValues(domain, 1000), Contains(Value(domain, 42)));
+}
+
+TEST(Filter, WithSeedsFailsWhenConversionFromUserValueFails) {
+  EXPECT_DEATH_IF_SUPPORTED(
+      Filter([](int i) { return i % 2 == 0; }, Arbitrary<int>())
+          .WithSeeds({41}),
+      "Invalid seed value");
+}
+
 }  // namespace
 }  // namespace fuzztest
diff --git a/domain_tests/misc_domains_test.cc b/domain_tests/misc_domains_test.cc
index 48756cc..4721379 100644
--- a/domain_tests/misc_domains_test.cc
+++ b/domain_tests/misc_domains_test.cc
@@ -15,15 +15,8 @@
 // Tests of various domains that don't fit naturally into the other test files
 // in this directory: BitFlagCombinationOf and OneOf.
 
-#include <bitset>
-#include <cctype>
-#include <deque>
-#include <iterator>
-#include <list>
-#include <optional>
-#include <set>
+#include <cstdlib>
 #include <string>
-#include <unordered_set>
 #include <utility>
 #include <variant>
 #include <vector>
@@ -33,16 +26,27 @@
 #include "absl/container/flat_hash_set.h"
 #include "absl/numeric/int128.h"
 #include "absl/random/random.h"
+#include "absl/types/span.h"
 #include "./fuzztest/domain.h"
 #include "./domain_tests/domain_testing.h"
 #include "./fuzztest/internal/meta.h"
+#include "./fuzztest/internal/type_support.h"
 
 namespace fuzztest {
 namespace {
 
+using ::testing::AllOf;
+using ::testing::Contains;
 using ::testing::UnorderedElementsAre;
 using ::testing::UnorderedElementsAreArray;
 
+TEST(BitFlagCombinationOf, InitGeneratesSeeds) {
+  auto domain = BitFlagCombinationOf({1, 4, 16, 32}).WithSeeds({5, 17});
+
+  EXPECT_THAT(GenerateInitialValues(domain, 1000),
+              AllOf(Contains(Value(domain, 5)), Contains(Value(domain, 17))));
+}
+
 TEST(BitFlagCombinationOf, Ints) {
   auto domain = BitFlagCombinationOf({1, 4, 16, 32});
   auto seen = MutateUntilFoundN(domain, 16);
@@ -88,6 +92,13 @@
       "BitFlagCombinationOf requires flags to be mutually exclusive.");
 }
 
+TEST(OneOf, InitGeneratesSeeds) {
+  auto domain = OneOf(Negative<int>(), Positive<int>()).WithSeeds({-42, 42});
+
+  EXPECT_THAT(GenerateInitialValues(domain, 1000),
+              AllOf(Contains(Value(domain, -42)), Contains(Value(domain, 42))));
+}
+
 TEST(OneOf, AllSubDomainsArePickedEventually) {
   absl::BitGen bitgen;
   std::vector<int> vals;
diff --git a/domain_tests/numeric_domains_test.cc b/domain_tests/numeric_domains_test.cc
index db5bc93..4a70819 100644
--- a/domain_tests/numeric_domains_test.cc
+++ b/domain_tests/numeric_domains_test.cc
@@ -16,30 +16,32 @@
 // type, but also Positive, Negative, NonZero, NonPositive, NonNegative, and
 // InRange.
 
-#include <bitset>
-#include <cctype>
+#include <cmath>
 #include <cstdint>
-#include <deque>
-#include <iterator>
 #include <limits>
-#include <list>
 #include <optional>
-#include <set>
 #include <string>
-#include <unordered_set>
-#include <utility>
-#include <variant>
+#include <type_traits>
 #include <vector>
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
+#include "absl/container/flat_hash_set.h"
+#include "absl/numeric/int128.h"
+#include "absl/random/random.h"
+#include "absl/status/status.h"
+#include "absl/strings/str_cat.h"
 #include "absl/strings/substitute.h"
+#include "absl/types/span.h"
 #include "./fuzztest/domain.h"
 #include "./domain_tests/domain_testing.h"
+#include "./fuzztest/internal/serialization.h"
 
 namespace fuzztest {
 namespace {
 
+using ::testing::AllOf;
+using ::testing::Contains;
 using ::testing::Each;
 using ::testing::Field;
 using ::testing::Ge;
@@ -84,6 +86,15 @@
                   .ok());
 }
 
+TYPED_TEST(NumericTest, InitGeneratesSeeds) {
+  using T = TypeParam;
+  auto domain = Arbitrary<T>().WithSeeds({T{7}, T{42}});
+
+  EXPECT_THAT(
+      GenerateInitialValues(domain, 1000),
+      AllOf(Contains(Value(domain, T{7})), Contains(Value(domain, T{42}))));
+}
+
 TYPED_TEST(NumericTest, Positive) {
   using T = TypeParam;
   Domain<T> domain = Positive<T>();
@@ -228,6 +239,15 @@
                   .ok());
 }
 
+TEST(InRange, InitGeneratesSeeds) {
+  auto domain =
+      InRange(std::numeric_limits<int>::min(), std::numeric_limits<int>::max())
+          .WithSeeds({7, 42});
+
+  EXPECT_THAT(GenerateInitialValues(domain, 1000),
+              AllOf(Contains(Value(domain, 7)), Contains(Value(domain, 42))));
+}
+
 TEST(InRange, FailsWithInfiniteRange) {
   EXPECT_DEATH_IF_SUPPORTED(InRange(std::numeric_limits<double>::lowest(),
                                     std::numeric_limits<double>::max()),
diff --git a/domain_tests/specific_value_domains_test.cc b/domain_tests/specific_value_domains_test.cc
index fd4d6b8..9d32795 100644
--- a/domain_tests/specific_value_domains_test.cc
+++ b/domain_tests/specific_value_domains_test.cc
@@ -15,20 +15,21 @@
 // Tests of ElementOf and Just, which are domains that yield values from an
 // explicitly specified set of values.
 
-#include <cmath>
-#include <variant>
 #include <vector>
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "absl/container/flat_hash_set.h"
 #include "absl/random/random.h"
+#include "absl/types/span.h"
 #include "./fuzztest/domain.h"
 #include "./domain_tests/domain_testing.h"
 
 namespace fuzztest {
 namespace {
 
+using ::testing::Contains;
+using ::testing::Ge;
 using ::testing::SizeIs;
 using ::testing::UnorderedElementsAreArray;
 
@@ -50,6 +51,17 @@
   }
 }
 
+TEST(ElementOfTest, InitGeneratesSeeds) {
+  Domain<int> domain = ElementOf({0, 1}).WithSeeds({0});
+
+  EXPECT_THAT(GenerateInitialValues(domain, 1000),
+              Contains(Value(domain, 0))
+                  // Since there are only two possible values, the seed will
+                  // surely appear at least once. To make the test meaningful,
+                  // we expect to see it much more often than the other value.
+                  .Times(Ge(650)));
+}
+
 TEST(ElementOfTest, InvalidInputReportsErrors) {
   EXPECT_DEATH_IF_SUPPORTED(ElementOf<int>({}),
                             "ElementOf requires a non empty list.");
diff --git a/domain_tests/string_domains_test.cc b/domain_tests/string_domains_test.cc
index 191598c..0cd9271 100644
--- a/domain_tests/string_domains_test.cc
+++ b/domain_tests/string_domains_test.cc
@@ -17,6 +17,7 @@
 #include <cctype>
 #include <deque>
 #include <string>
+#include <string_view>
 #include <vector>
 
 #include "gmock/gmock.h"
@@ -30,6 +31,7 @@
 namespace {
 
 using ::testing::AllOf;
+using ::testing::Contains;
 using ::testing::Ge;
 using ::testing::Gt;
 using ::testing::Lt;
@@ -67,6 +69,16 @@
   EXPECT_THAT(unique, SizeIs(Gt(10)));
 }
 
+TYPED_TEST(HandleTypeTest, InitGeneratesSeeds) {
+  auto domain = Arbitrary<TypeParam>();
+  absl::BitGen bitgen;
+  auto seed = Value(domain, bitgen);
+  seed.RandomizeByRepeatedMutation(domain, bitgen);
+  domain.WithSeeds({seed.user_value});
+
+  EXPECT_THAT(GenerateInitialValues(domain, 1000), Contains(seed));
+}
+
 TEST(Domain, Forwarding) {
   absl::BitGen bitgen;
 
diff --git a/fuzztest/internal/domains/aggregate_of_impl.h b/fuzztest/internal/domains/aggregate_of_impl.h
index 674ad39..f30a75a 100644
--- a/fuzztest/internal/domains/aggregate_of_impl.h
+++ b/fuzztest/internal/domains/aggregate_of_impl.h
@@ -58,6 +58,7 @@
       : inner_(std::move(inner)...) {}
 
   corpus_type Init(absl::BitGenRef prng) {
+    if (auto seed = this->MaybeGetRandomSeed(prng)) return *seed;
     return std::apply(
         [&](auto&... inner) { return corpus_type{inner.Init(prng)...}; },
         inner_);
diff --git a/fuzztest/internal/domains/arbitrary_impl.h b/fuzztest/internal/domains/arbitrary_impl.h
index e10187c..94f4f1e 100644
--- a/fuzztest/internal/domains/arbitrary_impl.h
+++ b/fuzztest/internal/domains/arbitrary_impl.h
@@ -81,6 +81,7 @@
 class ArbitraryImpl<bool> : public DomainBase<ArbitraryImpl<bool>> {
  public:
   value_type Init(absl::BitGenRef prng) {
+    if (auto seed = MaybeGetRandomSeed(prng)) return *seed;
     return static_cast<bool>(absl::Uniform(prng, 0, 2));
   }
 
@@ -110,6 +111,7 @@
                          IntegerDictionary<T>, bool>;
 
   value_type Init(absl::BitGenRef prng) {
+    if (auto seed = this->MaybeGetRandomSeed(prng)) return *seed;
     const auto choose_from_all = [&] {
       return absl::Uniform(absl::IntervalClosedClosed, prng,
                            std::numeric_limits<T>::min(),
@@ -193,6 +195,7 @@
   using typename ArbitraryImpl::DomainBase::value_type;
 
   value_type Init(absl::BitGenRef prng) {
+    if (auto seed = this->MaybeGetRandomSeed(prng)) return *seed;
     const T special[] = {
         T{0.0}, T{-0.0}, T{1.0}, T{-1.0}, std::numeric_limits<T>::max(),
         std::numeric_limits<T>::infinity(), -std::numeric_limits<T>::infinity(),
@@ -263,7 +266,10 @@
   using typename ArbitraryImpl::DomainBase::corpus_type;
   using typename ArbitraryImpl::DomainBase::value_type;
 
-  corpus_type Init(absl::BitGenRef prng) { return inner_.Init(prng); }
+  corpus_type Init(absl::BitGenRef prng) {
+    if (auto seed = this->MaybeGetRandomSeed(prng)) return *seed;
+    return inner_.Init(prng);
+  }
 
   void Mutate(corpus_type& val, absl::BitGenRef prng, bool only_shrink) {
     inner_.Mutate(val, prng, only_shrink);
diff --git a/fuzztest/internal/domains/bit_flag_combination_of_impl.h b/fuzztest/internal/domains/bit_flag_combination_of_impl.h
index f7ec199..7ad70e2 100644
--- a/fuzztest/internal/domains/bit_flag_combination_of_impl.h
+++ b/fuzztest/internal/domains/bit_flag_combination_of_impl.h
@@ -50,7 +50,10 @@
     }
   }
 
-  value_type Init(absl::BitGenRef) { return value_type{}; }
+  value_type Init(absl::BitGenRef prng) {
+    if (auto seed = this->MaybeGetRandomSeed(prng)) return *seed;
+    return value_type{};
+  }
 
   void Mutate(value_type& val, absl::BitGenRef prng, bool only_shrink) {
     T to_switch = flags_[ChooseOffset(flags_.size(), prng)];
diff --git a/fuzztest/internal/domains/container_of_impl.h b/fuzztest/internal/domains/container_of_impl.h
index f09e965..a80788f 100644
--- a/fuzztest/internal/domains/container_of_impl.h
+++ b/fuzztest/internal/domains/container_of_impl.h
@@ -381,6 +381,7 @@
       : Base(std::move(inner)) {}
 
   corpus_type Init(absl::BitGenRef prng) {
+    if (auto seed = this->MaybeGetRandomSeed(prng)) return *seed;
     const int size = this->ChooseRandomSize(prng);
 
     corpus_type val;
@@ -486,6 +487,7 @@
       : Base(std::move(inner)) {}
 
   corpus_type Init(absl::BitGenRef prng) {
+    if (auto seed = this->MaybeGetRandomSeed(prng)) return *seed;
     const int size = this->ChooseRandomSize(prng);
     corpus_type val;
     while (val.size() < size) {
diff --git a/fuzztest/internal/domains/domain_base.h b/fuzztest/internal/domains/domain_base.h
index e8bdb2d..4730a64 100644
--- a/fuzztest/internal/domains/domain_base.h
+++ b/fuzztest/internal/domains/domain_base.h
@@ -16,16 +16,23 @@
 #define FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_DOMAIN_BASE_H_
 
 #include <cstdint>
+#include <cstdio>
+#include <cstdlib>
+#include <iostream>
 #include <memory>
 #include <optional>
 #include <type_traits>
+#include <vector>
 
 #include "absl/random/bit_gen_ref.h"
+#include "absl/random/distributions.h"
 #include "absl/strings/str_format.h"
+#include "absl/types/span.h"
 #include "./fuzztest/internal/any.h"
 #include "./fuzztest/internal/logging.h"
 #include "./fuzztest/internal/meta.h"
 #include "./fuzztest/internal/serialization.h"
+#include "./fuzztest/internal/table_of_recent_compares.h"
 #include "./fuzztest/internal/type_support.h"
 
 namespace fuzztest {
@@ -217,9 +224,42 @@
     return 0;
   }
 
+  // Stores `seeds` to be occasionally sampled from during value initialization.
+  std::enable_if_t<std::is_copy_constructible_v<CorpusType>, Derived&>
+  WithSeeds(absl::Span<const ValueType> seeds) {
+    seeds_.clear();
+    seeds_.reserve(seeds.size());
+    for (const ValueType& seed : seeds) {
+      std::optional<CorpusType> corpus_seed = derived().FromValue(seed);
+      if (!corpus_seed.has_value()) {
+        // This may run during fuzz test registration (i.e., global variable
+        // initialization), so we can't use `GetStderr()`.
+        absl::FPrintF(stderr, "[!] Invalid seed value:\n\n{");
+        AutodetectTypePrinter<ValueType>().PrintUserValue(
+            seed, &std::cerr, PrintMode::kHumanReadable);
+        absl::FPrintF(stderr, "}\n");
+        std::exit(1);
+      }
+      seeds_.push_back(*std::move(corpus_seed));
+    }
+    return derived();
+  }
+
+ protected:
+  // `Derived::Init()` can use this to sample seeds for this domain.
+  std::optional<CorpusType> MaybeGetRandomSeed(absl::BitGenRef prng) const {
+    static constexpr double kProbabilityToReturnSeed = 0.5;
+    if (seeds_.empty() || !absl::Bernoulli(prng, kProbabilityToReturnSeed)) {
+      return std::nullopt;
+    }
+    return seeds_[ChooseOffset(seeds_.size(), prng)];
+  }
+
  private:
   Derived& derived() { return static_cast<Derived&>(*this); }
   const Derived& derived() const { return static_cast<const Derived&>(*this); }
+
+  std::vector<CorpusType> seeds_;
 };
 
 }  //  namespace fuzztest::internal
diff --git a/fuzztest/internal/domains/element_of_impl.h b/fuzztest/internal/domains/element_of_impl.h
index bf53844..d1dbfe4 100644
--- a/fuzztest/internal/domains/element_of_impl.h
+++ b/fuzztest/internal/domains/element_of_impl.h
@@ -45,6 +45,7 @@
   }
 
   corpus_type Init(absl::BitGenRef prng) {
+    if (auto seed = this->MaybeGetRandomSeed(prng)) return *seed;
     return corpus_type{absl::Uniform<size_t>(prng, 0, values_.size())};
   }
 
diff --git a/fuzztest/internal/domains/filter_impl.h b/fuzztest/internal/domains/filter_impl.h
index ee13a1a..b38d0cd 100644
--- a/fuzztest/internal/domains/filter_impl.h
+++ b/fuzztest/internal/domains/filter_impl.h
@@ -40,6 +40,7 @@
       : predicate_(std::move(predicate)), inner_(std::move(inner)) {}
 
   corpus_type Init(absl::BitGenRef prng) {
+    if (auto seed = this->MaybeGetRandomSeed(prng)) return *seed;
     while (true) {
       auto v = inner_.Init(prng);
       if (RunFilter(v)) return v;
diff --git a/fuzztest/internal/domains/flat_map_impl.h b/fuzztest/internal/domains/flat_map_impl.h
index 0ebdc6c..e697659 100644
--- a/fuzztest/internal/domains/flat_map_impl.h
+++ b/fuzztest/internal/domains/flat_map_impl.h
@@ -54,6 +54,7 @@
       : mapper_(std::move(mapper)), inner_(std::move(inner)...) {}
 
   corpus_type Init(absl::BitGenRef prng) {
+    if (auto seed = this->MaybeGetRandomSeed(prng)) return *seed;
     auto inner_corpus = std::apply(
         [&](auto&... inner) { return std::make_tuple(inner.Init(prng)...); },
         inner_);
diff --git a/fuzztest/internal/domains/in_grammar_impl.h b/fuzztest/internal/domains/in_grammar_impl.h
index 7e1e4a9..41bc197 100644
--- a/fuzztest/internal/domains/in_grammar_impl.h
+++ b/fuzztest/internal/domains/in_grammar_impl.h
@@ -639,7 +639,10 @@
   using typename InGrammarImpl::DomainBase::corpus_type;
   using typename InGrammarImpl::DomainBase::value_type;
 
-  ASTNode Init(absl::BitGenRef prng) { return TopDomain::Init(prng); }
+  ASTNode Init(absl::BitGenRef prng) {
+    if (auto seed = this->MaybeGetRandomSeed(prng)) return *seed;
+    return TopDomain::Init(prng);
+  }
 
   void Mutate(ASTNode& val, absl::BitGenRef prng, bool only_shrink) {
     if (only_shrink && absl::Bernoulli(prng, 0.5) &&
diff --git a/fuzztest/internal/domains/in_range_impl.h b/fuzztest/internal/domains/in_range_impl.h
index 9757131..8e4b33d 100644
--- a/fuzztest/internal/domains/in_range_impl.h
+++ b/fuzztest/internal/domains/in_range_impl.h
@@ -83,6 +83,7 @@
   }
 
   value_type Init(absl::BitGenRef prng) {
+    if (auto seed = this->MaybeGetRandomSeed(prng)) return *seed;
     // TODO(sbenzaquen): Add more interesting points in the range.
     const T special[] = {min_, max_};
     return ChooseOneOr(special, prng, [&] {
diff --git a/fuzztest/internal/domains/in_regexp_impl.h b/fuzztest/internal/domains/in_regexp_impl.h
index 2790e79..dcdfc9f 100644
--- a/fuzztest/internal/domains/in_regexp_impl.h
+++ b/fuzztest/internal/domains/in_regexp_impl.h
@@ -42,6 +42,7 @@
       : dfa_(RegexpDFA::Create(regex_str)) {}
 
   DFAPath Init(absl::BitGenRef prng) {
+    if (auto seed = MaybeGetRandomSeed(prng)) return *seed;
     std::optional<DFAPath> path =
         dfa_.StringToDFAPath(dfa_.GenerateString(prng));
     FUZZTEST_INTERNAL_CHECK_PRECONDITION(path.has_value(),
diff --git a/fuzztest/internal/domains/map_impl.h b/fuzztest/internal/domains/map_impl.h
index ca2958d..8579ac4 100644
--- a/fuzztest/internal/domains/map_impl.h
+++ b/fuzztest/internal/domains/map_impl.h
@@ -48,6 +48,7 @@
         map_function_name_(map_function_name) {}
 
   corpus_type Init(absl::BitGenRef prng) {
+    if (auto seed = this->MaybeGetRandomSeed(prng)) return *seed;
     return std::apply(
         [&](auto&... inner) { return corpus_type(inner.Init(prng)...); },
         inner_);
diff --git a/fuzztest/internal/domains/one_of_impl.h b/fuzztest/internal/domains/one_of_impl.h
index 973734e..b568e71 100644
--- a/fuzztest/internal/domains/one_of_impl.h
+++ b/fuzztest/internal/domains/one_of_impl.h
@@ -50,6 +50,7 @@
   explicit OneOfImpl(Inner... domains) : domains_(std::move(domains)...) {}
 
   corpus_type Init(absl::BitGenRef prng) {
+    if (auto seed = this->MaybeGetRandomSeed(prng)) return *seed;
     // TODO(b/191368509): Consider the cardinality of the subdomains to weight
     // them.
     return Switch<kNumDomains>(
diff --git a/fuzztest/internal/domains/optional_of_impl.h b/fuzztest/internal/domains/optional_of_impl.h
index 8651f74..f67f7a9 100644
--- a/fuzztest/internal/domains/optional_of_impl.h
+++ b/fuzztest/internal/domains/optional_of_impl.h
@@ -51,6 +51,7 @@
       : inner_(std::move(inner)), policy_(OptionalPolicy::kWithNull) {}
 
   corpus_type Init(absl::BitGenRef prng) {
+    if (auto seed = this->MaybeGetRandomSeed(prng)) return *seed;
     if (policy_ == OptionalPolicy::kAlwaysNull ||
         // 1/2 chance of returning an empty to avoid initialization with large
         // entities for recursive data structures. See
diff --git a/fuzztest/internal/domains/protobuf_domain_impl.h b/fuzztest/internal/domains/protobuf_domain_impl.h
index db40285..36bf6d2 100644
--- a/fuzztest/internal/domains/protobuf_domain_impl.h
+++ b/fuzztest/internal/domains/protobuf_domain_impl.h
@@ -498,6 +498,7 @@
   }
 
   corpus_type Init(absl::BitGenRef prng) {
+    if (auto seed = this->MaybeGetRandomSeed(prng)) return *seed;
     FUZZTEST_INTERNAL_CHECK(
         !customized_fields_.empty() || !IsNonTerminatingRecursive(),
         "Cannot set recursive fields by default.");
@@ -1426,7 +1427,10 @@
   using typename ProtobufDomainImpl::DomainBase::value_type;
   using FieldDescriptor = ProtobufFieldDescriptor<typename T::Message>;
 
-  corpus_type Init(absl::BitGenRef prng) { return inner_.Init(prng); }
+  corpus_type Init(absl::BitGenRef prng) {
+    if (auto seed = this->MaybeGetRandomSeed(prng)) return *seed;
+    return inner_.Init(prng);
+  }
 
   uint64_t CountNumberOfFields(const corpus_type& val) {
     return inner_.CountNumberOfFields(val);
@@ -1873,6 +1877,7 @@
   using typename ArbitraryImpl::DomainBase::value_type;
 
   value_type Init(absl::BitGenRef prng) {
+    if (auto seed = this->MaybeGetRandomSeed(prng)) return *seed;
     const int index = absl::Uniform(prng, 0, descriptor()->value_count());
     return static_cast<T>(descriptor()->value(index)->number());
   }
diff --git a/fuzztest/internal/domains/smart_pointer_of_impl.h b/fuzztest/internal/domains/smart_pointer_of_impl.h
index 61993e3..d7503a3 100644
--- a/fuzztest/internal/domains/smart_pointer_of_impl.h
+++ b/fuzztest/internal/domains/smart_pointer_of_impl.h
@@ -50,7 +50,8 @@
   explicit SmartPointerOfImpl(InnerFn fn) : inner_(fn) {}
   explicit SmartPointerOfImpl(Inner inner) : inner_(std::move(inner)) {}
 
-  corpus_type Init(absl::BitGenRef) {
+  corpus_type Init(absl::BitGenRef prng) {
+    if (auto seed = this->MaybeGetRandomSeed(prng)) return *seed;
     // Init will always have an empty smart pointer to reduce nesting.
     // Otherwise it is very easy to get a stack overflow during Init() when
     // there is recursion in the domains.
diff --git a/fuzztest/internal/domains/unique_elements_container_of_impl.h b/fuzztest/internal/domains/unique_elements_container_of_impl.h
index c4789f7..f446334 100644
--- a/fuzztest/internal/domains/unique_elements_container_of_impl.h
+++ b/fuzztest/internal/domains/unique_elements_container_of_impl.h
@@ -56,7 +56,10 @@
   // All of these methods delegate at least partially to the unique_domain_
   // member.
 
-  corpus_type Init(absl::BitGenRef prng) { return unique_domain_.Init(prng); }
+  corpus_type Init(absl::BitGenRef prng) {
+    if (auto seed = this->MaybeGetRandomSeed(prng)) return *seed;
+    return unique_domain_.Init(prng);
+  }
 
   void Mutate(corpus_type& val, absl::BitGenRef prng, bool only_shrink) {
     unique_domain_.Mutate(val, prng, only_shrink);
diff --git a/fuzztest/internal/domains/variant_of_impl.h b/fuzztest/internal/domains/variant_of_impl.h
index c747f68..ca34998 100644
--- a/fuzztest/internal/domains/variant_of_impl.h
+++ b/fuzztest/internal/domains/variant_of_impl.h
@@ -46,6 +46,7 @@
       : inner_(std::move(inner)...) {}
 
   corpus_type Init(absl::BitGenRef prng) {
+    if (auto seed = this->MaybeGetRandomSeed(prng)) return *seed;
     return Switch<sizeof...(Inner)>(
         absl::Uniform(prng, size_t{}, sizeof...(Inner)), [&](auto I) {
           return corpus_type(std::in_place_index<I>,