No public description

PiperOrigin-RevId: 955890719
diff --git a/domain_tests/BUILD b/domain_tests/BUILD
index 0dfd652..c4ecc87 100644
--- a/domain_tests/BUILD
+++ b/domain_tests/BUILD
@@ -30,12 +30,10 @@
         "@abseil-cpp//absl/random",
         "@abseil-cpp//absl/random:bit_gen_ref",
         "@abseil-cpp//absl/status",
-        "@abseil-cpp//absl/types:optional",
-        "@abseil-cpp//absl/types:span",
-        "@abseil-cpp//absl/types:variant",
         "@com_google_fuzztest//fuzztest:domain_core",
         "@com_google_fuzztest//fuzztest/internal:serialization",
         "@com_google_fuzztest//fuzztest/internal:type_support",
+        "@com_google_fuzztest//fuzztest/internal/domains:core_domains_impl",
         "@googletest//:gtest_main",
     ],
 )
@@ -137,6 +135,7 @@
         "@abseil-cpp//absl/random",
         "@com_google_fuzztest//fuzztest:domain_core",
         "@com_google_fuzztest//fuzztest/internal:table_of_recent_compares",
+        "@com_google_fuzztest//fuzztest/internal/domains:core_domains_impl",
         "@googletest//:gtest_main",
     ],
 )
@@ -153,8 +152,10 @@
         "@abseil-cpp//absl/random",
         "@abseil-cpp//absl/random:bit_gen_ref",
         "@abseil-cpp//absl/status",
+        "@abseil-cpp//absl/status:statusor",
         "@abseil-cpp//absl/strings",
         "@com_google_fuzztest//common:logging",
+        "@com_google_fuzztest//common:status_macros",
         "@com_google_fuzztest//fuzztest/internal:logging",
         "@com_google_fuzztest//fuzztest/internal:meta",
         "@com_google_fuzztest//fuzztest/internal:serialization",
@@ -199,7 +200,9 @@
         ":domain_testing",
         "@abseil-cpp//absl/algorithm:container",
         "@abseil-cpp//absl/random",
+        "@abseil-cpp//absl/status",
         "@com_google_fuzztest//fuzztest:domain_core",
+        "@com_google_fuzztest//fuzztest/internal/domains:core_domains_impl",
         "@googletest//:gtest_main",
     ],
 )
@@ -256,6 +259,7 @@
         ":domain_testing",
         "@abseil-cpp//absl/random",
         "@com_google_fuzztest//fuzztest:domain_core",
+        "@com_google_fuzztest//fuzztest/internal/domains:core_domains_impl",
         "@googletest//:gtest_main",
     ],
 )
diff --git a/domain_tests/CMakeLists.txt b/domain_tests/CMakeLists.txt
index be59c47..2b09961 100644
--- a/domain_tests/CMakeLists.txt
+++ b/domain_tests/CMakeLists.txt
@@ -8,10 +8,8 @@
     absl::flat_hash_set
     absl::random_bit_gen_ref
     absl::random_random
-    absl::optional
-    absl::span
     absl::status
-    absl::variant
+    fuzztest::core_domains_impl
     fuzztest::domain_core
     fuzztest::serialization
     fuzztest::type_support
@@ -122,6 +120,8 @@
     absl::flat_hash_map
     absl::flat_hash_set
     absl::random_random
+    absl::status
+    fuzztest::core_domains_impl
     fuzztest::domain_core
     fuzztest::table_of_recent_compares
     GTest::gmock_main
@@ -138,9 +138,11 @@
     absl::random_random
     absl::random_bit_gen_ref
     absl::status
+    absl::statusor
     absl::strings
     fuzztest::domain_core
     fuzztest::common_logging
+    fuzztest::status_macros
     fuzztest::meta
     fuzztest::serialization
     fuzztest::test_protobuf_cc_proto
@@ -188,6 +190,7 @@
     fuzztest::domain_testing
     absl::algorithm_container
     absl::random_random
+    fuzztest::core_domains_impl
     fuzztest::domain_core
     GTest::gmock_main
 )
@@ -247,6 +250,7 @@
   DEPS
     fuzztest::domain_testing
     absl::random_random
+    fuzztest::core_domains_impl
     fuzztest::domain_core
     GTest::gmock_main
 )
@@ -281,3 +285,5 @@
     fuzztest::table_of_recent_compares
     GTest::gmock_main
 )
+
+
diff --git a/domain_tests/aggregate_combinators_test.cc b/domain_tests/aggregate_combinators_test.cc
index 5ee180d..c5e87ba 100644
--- a/domain_tests/aggregate_combinators_test.cc
+++ b/domain_tests/aggregate_combinators_test.cc
@@ -30,6 +30,7 @@
 #include "absl/status/status.h"
 #include "./fuzztest/domain_core.h"
 #include "./domain_tests/domain_testing.h"
+#include "./fuzztest/internal/domains/traversal_context.h"
 #include "./fuzztest/internal/serialization.h"
 #include "./fuzztest/internal/type_support.h"
 
@@ -490,5 +491,63 @@
   EXPECT_TRUE(optional_corpus_tuple.has_value());
 }
 
+TEST(StructOf, InitWithTraversalCtxUpdatesInitBudget) {
+  struct Foo {
+    int a;
+    double b;
+  };
+  auto domain = StructOf<Foo>(Arbitrary<int>(), Arbitrary<double>());
+
+  absl::BitGen prng;
+  domain_implementor::TraversalState state;
+  state.init_budget = 10;
+
+  auto val =
+      Value<decltype(domain)>::BuildWithTraversalCtx(domain, prng, state);
+
+  ASSERT_OK(val.status());
+  // 1 (root) + 2 (fields: int, double) = 3 decrements
+  EXPECT_EQ(state.init_budget, 7);
+}
+
+TEST(StructOf, InitWithTraversalCtxPropagatesFailureFromInnerDomain) {
+  struct Foo {
+    int a;
+    std::vector<int> v;
+  };
+  // Inner container cannot be empty.
+  auto domain = StructOf<Foo>(Arbitrary<int>(),
+                              VectorOf(Arbitrary<int>()).WithMinSize(1));
+
+  absl::BitGen prng;
+  domain_implementor::TraversalState state;
+  // The parent init decrements to 0 and the inner vector init decrements to -1
+  // and fails due to the min size constraint.
+  state.depth_budget = 1;
+
+  const auto val =
+      Value<decltype(domain)>::BuildWithTraversalCtx(domain, prng, state);
+
+  EXPECT_FALSE(val.ok());
+  EXPECT_FALSE(state.status.ok());
+  EXPECT_THAT(state.status.message(),
+              testing::HasSubstr("Traversal budget exceeded"));
+}
+
+TEST(StructOf, InitWithTraversalCtxHandlesPreExistingFailure) {
+  auto domain = StructOf<MyStruct>(Arbitrary<int>(), Arbitrary<std::string>());
+
+  absl::BitGen prng;
+  domain_implementor::TraversalState state;
+  state.status = absl::CancelledError("Pre-existing failure");
+
+  const auto val =
+      Value<decltype(domain)>::BuildWithTraversalCtx(domain, prng, state);
+
+  EXPECT_FALSE(val.ok());
+  EXPECT_FALSE(state.status.ok());
+  EXPECT_EQ(state.status.message(), "Pre-existing failure");
+}
+
 }  // namespace
 }  // namespace fuzztest
diff --git a/domain_tests/container_test.cc b/domain_tests/container_test.cc
index c599fa4..98c6dea 100644
--- a/domain_tests/container_test.cc
+++ b/domain_tests/container_test.cc
@@ -34,6 +34,7 @@
 #include "absl/random/random.h"
 #include "./fuzztest/domain_core.h"
 #include "./domain_tests/domain_testing.h"
+#include "./fuzztest/internal/domains/traversal_context.h"
 #include "./fuzztest/internal/table_of_recent_compares.h"
 
 namespace fuzztest {
@@ -356,8 +357,7 @@
       * 1.0 / 4                   // to use cmp tables
       * 1.0 / 2                   // to pick the memcmp table
       * 1.0 / 2                   // to pick one of the entries
-      * 1.0 / 2                   // to apply replacement
-      ;
+      * 1.0 / 2;                  // to apply replacement
   for (int i = 0; i < 1 * IterationsToHitAll(/*num_cases=*/2, hit_probability);
        ++i) {
     std::vector<uint8_t> mutant = {10, 11, 12, 13};
@@ -369,5 +369,106 @@
   EXPECT_THAT(mutants, Not(Contains(std::vector<uint8_t>{129, 129, 129, 129})));
 }
 
+TEST(ContainerTest,
+     SequenceInitWithTraversalCtxDepthExhaustedWithMinSizeFails) {
+  auto domain = VectorOf(Arbitrary<int>()).WithMinSize(3);
+
+  absl::BitGen prng;
+  domain_implementor::TraversalState state;
+  state.depth_budget = 0;
+
+  const auto val =
+      Value<decltype(domain)>::BuildWithTraversalCtx(domain, prng, state);
+
+  EXPECT_FALSE(val.ok());
+  EXPECT_FALSE(state.status.ok());
+  EXPECT_THAT(state.status.message(),
+              testing::HasSubstr("Traversal budget exceeded"));
+}
+
+TEST(ContainerTest,
+     SequenceInitWithTraversalCtxDepthExhaustedNoMinSizeSucceeds) {
+  auto domain = VectorOf(Arbitrary<int>());
+
+  absl::BitGen prng;
+  domain_implementor::TraversalState state;
+  state.depth_budget = 0;
+
+  const auto val =
+      Value<decltype(domain)>::BuildWithTraversalCtx(domain, prng, state);
+
+  ASSERT_OK(val.status());
+  EXPECT_TRUE(val->user_value.empty());
+  EXPECT_TRUE(state.status.ok());
+}
+
+TEST(ContainerTest, SequenceInitWithTraversalCtxUpdatesInitBudget) {
+  auto domain = VectorOf(Arbitrary<int>()).WithSize(3);
+
+  absl::BitGen prng;
+  domain_implementor::TraversalState state;
+  state.init_budget = 10;
+
+  const auto val =
+      Value<decltype(domain)>::BuildWithTraversalCtx(domain, prng, state);
+
+  ASSERT_OK(val.status());
+  EXPECT_EQ(val->user_value.size(), 3);
+  // 1 (root) + 3 (elements) = 4 decrements
+  EXPECT_EQ(state.init_budget, 6);
+}
+
+TEST(ContainerTest,
+     SequenceInitWithTraversalCtxInitBudgetExhaustedWithMinSizeFails) {
+  auto domain = VectorOf(Arbitrary<int>()).WithMinSize(3);
+
+  absl::BitGen prng;
+  domain_implementor::TraversalState state;
+  state.init_budget = 0;  // Enter() will decrement to -1
+
+  const auto val =
+      Value<decltype(domain)>::BuildWithTraversalCtx(domain, prng, state);
+
+  EXPECT_FALSE(val.ok());
+  EXPECT_FALSE(state.status.ok());
+}
+
+TEST(ContainerTest,
+     SequenceInitWithTraversalCtxInitBudgetExhaustedNoMinSizeSucceeds) {
+  auto domain = VectorOf(Arbitrary<int>());
+
+  absl::BitGen prng;
+  domain_implementor::TraversalState state;
+  state.init_budget = 0;
+
+  const auto val =
+      Value<decltype(domain)>::BuildWithTraversalCtx(domain, prng, state);
+
+  ASSERT_OK(val.status());
+  EXPECT_TRUE(val->user_value.empty());
+  EXPECT_TRUE(state.status.ok());
+}
+
+TEST(ContainerTest,
+     SequenceInitWithTraversalCtxPropagatesFailureFromInnerExhaustion) {
+  // Both the parent and inner vectors must not be empty.
+  auto domain =
+      VectorOf(VectorOf(Arbitrary<int>()).WithMinSize(1)).WithMinSize(1);
+
+  absl::BitGen prng;
+  domain_implementor::TraversalState state;
+  // The parent init decrements to 0 and the inner init decrements to -1
+  // and fails due to the min size constraint.
+  state.depth_budget = 1;
+
+  const auto val =
+      Value<decltype(domain)>::BuildWithTraversalCtx(domain, prng, state);
+
+  EXPECT_FALSE(val.ok());
+  EXPECT_FALSE(state.status.ok());
+  EXPECT_THAT(state.status.message(),
+              testing::HasSubstr("Traversal budget exceeded"));
+}
+
 }  // namespace
 }  // namespace fuzztest
diff --git a/domain_tests/domain_testing.h b/domain_tests/domain_testing.h
index ca2aa3b..61b6e61 100644
--- a/domain_tests/domain_testing.h
+++ b/domain_tests/domain_testing.h
@@ -33,10 +33,13 @@
 #include "absl/random/bit_gen_ref.h"
 #include "absl/random/random.h"
 #include "absl/status/status.h"
+#include "absl/status/statusor.h"
 #include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
+#include "./common/fuzztest_status_macros.h"
 #include "./common/logging.h"
 #include "./fuzztest/internal/domains/mutation_metadata.h"
+#include "./fuzztest/internal/domains/traversal_context.h"
 #include "./fuzztest/internal/logging.h"
 #include "./fuzztest/internal/meta.h"
 #include "./fuzztest/internal/serialization.h"
@@ -81,12 +84,12 @@
   template <typename T>
   size_t operator()(const T& v) const {
     if constexpr (internal::Requires<T>(
-                      [](auto v) -> decltype(v && v.get()) {})) {
+                      [](auto v) -> decltype(v&& v.get()) {})) {
       // Smart pointers.
       return v ? absl::HashOf(*v) : 0;
     } else if constexpr (internal::Requires<T>(
-                             [](auto v) -> decltype(std::isnan(
-                                            *std::optional(v))) {})) {
+                             [](auto v) -> decltype(std::isnan(*std::optional(
+                                            v))) {})) {
       auto o = std::optional(v);
       return !o || std::isnan(*o) ? 0 : absl::Hash<T>{}(*o);
     } else if constexpr (internal::Requires<T>(
@@ -108,12 +111,12 @@
       differencer.set_field_comparator(&cmp);
       return differencer.Compare(a, b);
     } else if constexpr (internal::Requires<T>(
-                             [](auto v) -> decltype(v && v.get()) {})) {
+                             [](auto v) -> decltype(v&& v.get()) {})) {
       // Smart pointers.
       return a ? b && *a == *b : !b;
     } else if constexpr (internal::Requires<T>(
-                             [](auto v) -> decltype(std::isnan(
-                                            *std::optional(v))) {})) {
+                             [](auto v) -> decltype(std::isnan(*std::optional(
+                                            v))) {})) {
       auto oa = std::optional(a), ob = std::optional(b);
       return a == b || (oa && ob && std::isnan(*oa) && std::isnan(*ob));
     } else {
@@ -127,6 +130,7 @@
 
 // The Value class keeps the corpus and value types together throughout tests to
 // simplify their access and mutation.
+// TODO(b/535145936): Update to a class and make the values private.
 template <typename Domain>
 struct Value {
   using T = internal::value_type_t<Domain>;
@@ -152,6 +156,17 @@
         }()),
         user_value(std::move(user_value)) {}
 
+  static absl::StatusOr<Value> BuildWithTraversalCtx(
+      Domain& domain, absl::BitGenRef prng,
+      domain_implementor::TraversalState& state) {
+    FUZZTEST_ASSIGN_OR_RETURN_IF_NOT_OK(
+        auto corpus,
+        domain.InitWithTraversalCtx(
+            prng, domain_implementor::InitTraversalContext<Domain>(state)));
+    auto user = domain.GetValue(corpus);
+    return Value{std::move(corpus), std::move(user)};
+  }
+
   void Mutate(Domain& domain, absl::BitGenRef prng,
               const domain_implementor::MutationMetadata& metadata,
               bool only_shrink) {
@@ -222,6 +237,9 @@
   }
 
  private:
+  Value(internal::corpus_type_t<Domain> corpus, T user)
+      : corpus_value(std::move(corpus)), user_value(std::move(user)) {}
+
   // We don't test the printers here, just that we return one.
   // The printers themselves are tested in type_support_test.cc
   using Printer = decltype(std::declval<const Domain&>().GetPrinter());
diff --git a/domain_tests/map_filter_combinator_test.cc b/domain_tests/map_filter_combinator_test.cc
index ebf66ed..82c80f9 100644
--- a/domain_tests/map_filter_combinator_test.cc
+++ b/domain_tests/map_filter_combinator_test.cc
@@ -26,8 +26,10 @@
 #include "gtest/gtest.h"
 #include "absl/algorithm/container.h"
 #include "absl/random/random.h"
+#include "absl/status/status.h"
 #include "./fuzztest/domain_core.h"
 #include "./domain_tests/domain_testing.h"
+#include "./fuzztest/internal/domains/traversal_context.h"
 
 namespace fuzztest {
 namespace {
@@ -408,5 +410,72 @@
           HasSubstr("Invalid corpus value for the inner domain in Filter()")));
 }
 
+TEST(Filter, InitWithTraversalCtxConsumesBudgetOnPredicateFailure) {
+  int attempts = 0;
+  // Fails 5 times, then succeeds.
+  auto domain =
+      Filter([&attempts](int i) { return ++attempts > 5; }, Arbitrary<int>());
+
+  absl::BitGen prng;
+  domain_implementor::TraversalState state;
+  state.depth_budget = 100;
+  state.init_budget = 10;
+
+  const auto val =
+      Value<decltype(domain)>::BuildWithTraversalCtx(domain, prng, state);
+
+  ASSERT_OK(val.status());
+  EXPECT_TRUE(state.status.ok());
+  // Start: 10
+  // Filter wrapper enter: -1 (9)
+  // 5 failed attempts of Arbitrary: 5 * -1 (4)
+  // 1 successful attempt of Arbitrary: -1 (3)
+  EXPECT_EQ(state.init_budget, 3);
+  EXPECT_EQ(state.depth_budget, 100);
+}
+
+TEST(Filter, InitWithTraversalCtxReturnsEarlyOnPreExistingFailure) {
+  auto domain = Filter(
+      [](int i) {
+        ADD_FAILURE() << "Predicate should not be called";
+        return false;
+      },
+      Arbitrary<int>());
+
+  absl::BitGen prng;
+  domain_implementor::TraversalState state;
+  state.status = absl::CancelledError("Pre-existing failure");
+
+  const auto val =
+      Value<decltype(domain)>::BuildWithTraversalCtx(domain, prng, state);
+
+  EXPECT_FALSE(val.ok());
+  EXPECT_FALSE(state.status.ok());
+  EXPECT_EQ(state.status.message(), "Pre-existing failure");
+}
+
+TEST(Filter, InitWithTraversalCtxPropagatesFailureFromInnerDomain) {
+  auto domain = Filter(
+      [](const std::vector<int>& v) {
+        ADD_FAILURE() << "Predicate should not be called";
+        return false;
+      },
+      VectorOf(Arbitrary<int>()).WithMinSize(3));
+
+  absl::BitGen prng;
+  domain_implementor::TraversalState state;
+  // This causes the inner VectorOf initialization to fail due to budget
+  // exhaustion.
+  state.init_budget = 0;
+
+  const auto val =
+      Value<decltype(domain)>::BuildWithTraversalCtx(domain, prng, state);
+
+  EXPECT_FALSE(val.ok());
+  EXPECT_FALSE(state.status.ok());
+  EXPECT_THAT(state.status.message(),
+              testing::HasSubstr("Traversal budget exceeded"));
+}
+
 }  // namespace
 }  // namespace fuzztest
diff --git a/domain_tests/recursive_domains_test.cc b/domain_tests/recursive_domains_test.cc
index aa5a956..b2bba27 100644
--- a/domain_tests/recursive_domains_test.cc
+++ b/domain_tests/recursive_domains_test.cc
@@ -18,14 +18,20 @@
 #include <utility>
 #include <vector>
 
+#include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "absl/random/random.h"
 #include "./fuzztest/domain_core.h"
 #include "./domain_tests/domain_testing.h"
+#include "./fuzztest/internal/domains/traversal_context.h"
 
 namespace fuzztest {
 namespace {
 
+using ::testing::HasSubstr;
+using ::testing::IsEmpty;
+using ::testing::Not;
+
 struct Tree {
   int value;
   std::vector<Tree> children;
@@ -105,5 +111,71 @@
       "Finalize\\(\\) has been called with an unknown name: typo");
 }
 
+TEST(DomainBuilder, RecursiveDomainReachesDepthLimit) {
+  DomainBuilder builder;
+  builder.Set<Tree>(
+      "tree", StructOf<Tree>(InRange(0, 10), ContainerOf<std::vector<Tree>>(
+                                                 builder.Get<Tree>("tree"))
+                                                 .WithSize(2)));
+  Domain<Tree> domain = std::move(builder).Finalize<Tree>("tree");
+
+  absl::BitGen bitgen;
+  domain_implementor::TraversalState state;
+  state.depth_budget = 5;
+
+  const auto tree =
+      Value<decltype(domain)>::BuildWithTraversalCtx(domain, bitgen, state);
+
+  EXPECT_FALSE(tree.ok());
+  EXPECT_FALSE(state.status.ok());
+  EXPECT_THAT(state.status.message(), HasSubstr("Traversal budget exceeded"));
+  EXPECT_THAT(state.error_trace, Not(IsEmpty()));
+}
+
+TEST(DomainBuilder, RecursiveDomainReachesInitBudgetLimit) {
+  DomainBuilder builder;
+  builder.Set<Tree>(
+      "tree", StructOf<Tree>(InRange(0, 10), ContainerOf<std::vector<Tree>>(
+                                                 builder.Get<Tree>("tree"))
+                                                 .WithSize(2)));
+  Domain<Tree> domain = std::move(builder).Finalize<Tree>("tree");
+
+  absl::BitGen bitgen;
+  domain_implementor::TraversalState state;
+  state.depth_budget = 100;
+  state.init_budget = 5;
+
+  const auto tree =
+      Value<decltype(domain)>::BuildWithTraversalCtx(domain, bitgen, state);
+
+  EXPECT_FALSE(tree.ok());
+  EXPECT_FALSE(state.status.ok());
+  EXPECT_THAT(state.status.message(), HasSubstr("Traversal budget exceeded"));
+  EXPECT_THAT(state.error_trace, Not(IsEmpty()));
+}
+
+TEST(DomainBuilder, RecursiveDomainWithFilterReachesDepthLimit) {
+  DomainBuilder builder;
+  builder.Set<Tree>(
+      "tree",
+      Filter([](const Tree& t) { return t.value % 2 == 0; },
+             StructOf<Tree>(InRange(0, 10), ContainerOf<std::vector<Tree>>(
+                                                builder.Get<Tree>("tree"))
+                                                .WithSize(2))));
+  Domain<Tree> domain = std::move(builder).Finalize<Tree>("tree");
+
+  absl::BitGen bitgen;
+  domain_implementor::TraversalState state;
+  state.depth_budget = 5;
+
+  const auto tree =
+      Value<decltype(domain)>::BuildWithTraversalCtx(domain, bitgen, state);
+
+  EXPECT_FALSE(tree.ok());
+  EXPECT_FALSE(state.status.ok());
+  EXPECT_THAT(state.status.message(), HasSubstr("Traversal budget exceeded"));
+  EXPECT_THAT(state.error_trace, Not(IsEmpty()));
+}
+
 }  // namespace
 }  // namespace fuzztest
diff --git a/fuzztest/domain_core.h b/fuzztest/domain_core.h
index f0a6a3d..4da59f3 100644
--- a/fuzztest/domain_core.h
+++ b/fuzztest/domain_core.h
@@ -62,6 +62,7 @@
 #include "./fuzztest/internal/domains/optional_of_impl.h"
 #include "./fuzztest/internal/domains/overlap_of_impl.h"
 #include "./fuzztest/internal/domains/smart_pointer_of_impl.h"
+#include "./fuzztest/internal/domains/traversal_context.h"
 #include "./fuzztest/internal/domains/unique_elements_container_of_impl.h"
 #include "./fuzztest/internal/domains/utf.h"
 #include "./fuzztest/internal/domains/variant_of_impl.h"
@@ -160,6 +161,14 @@
       return GetInnerDomain().Init(prng);
     }
 
+    absl::StatusOr<corpus_type> InitWithTraversalCtx(
+        absl::BitGenRef prng,
+        domain_implementor::InitTraversalContext<IndirectDomain> ctx) {
+      using InnerCtx = domain_implementor::InitTraversalContext<Domain<T>>;
+      return GetInnerDomain().InitWithTraversalCtx(prng,
+                                                   InnerCtx::Passthrough(ctx));
+    }
+
     void Mutate(corpus_type& val, absl::BitGenRef prng,
                 const domain_implementor::MutationMetadata& metadata,
                 bool only_shrink) {
@@ -218,6 +227,13 @@
 
     corpus_type Init(absl::BitGenRef prng) { return inner_.Init(prng); }
 
+    absl::StatusOr<corpus_type> InitWithTraversalCtx(
+        absl::BitGenRef prng,
+        domain_implementor::InitTraversalContext<OwningDomain> ctx) {
+      using InnerCtx = domain_implementor::InitTraversalContext<Domain<T>>;
+      return inner_.InitWithTraversalCtx(prng, InnerCtx::Passthrough(ctx));
+    }
+
     void Mutate(corpus_type& val, absl::BitGenRef prng,
                 const domain_implementor::MutationMetadata& metadata,
                 bool only_shrink) {
diff --git a/fuzztest/internal/domains/BUILD b/fuzztest/internal/domains/BUILD
index 5682ffb..5d571dd 100644
--- a/fuzztest/internal/domains/BUILD
+++ b/fuzztest/internal/domains/BUILD
@@ -13,6 +13,7 @@
 # limitations under the License.
 
 load("@rules_cc//cc:cc_library.bzl", "cc_library")
+load("@rules_cc//cc:cc_test.bzl", "cc_test")
 
 package(default_visibility = ["@com_google_fuzztest//:__subpackages__"])
 
@@ -52,6 +53,7 @@
         "serialization_helpers.h",
         "smart_pointer_of_impl.h",
         "special_values.h",
+        "traversal_context.h",
         "unique_elements_container_of_impl.h",
         "value_mutation_helpers.h",
         "variant_of_impl.h",
@@ -72,6 +74,7 @@
         "@abseil-cpp//absl/time",
         "@abseil-cpp//absl/types:span",
         "@com_google_fuzztest//common:logging",
+        "@com_google_fuzztest//common:status_macros",
         "@com_google_fuzztest//fuzztest:fuzzing_bit_gen",
         "@com_google_fuzztest//fuzztest/internal:any",
         "@com_google_fuzztest//fuzztest/internal:enum_reflection",
@@ -97,9 +100,11 @@
         "@abseil-cpp//absl/random:bit_gen_ref",
         "@abseil-cpp//absl/random:distributions",
         "@abseil-cpp//absl/status",
+        "@abseil-cpp//absl/status:statusor",
         "@abseil-cpp//absl/strings",
         "@abseil-cpp//absl/strings:str_format",
         "@com_google_fuzztest//common:logging",
+        "@com_google_fuzztest//common:status_macros",
         "@com_google_fuzztest//fuzztest/internal:any",
         "@com_google_fuzztest//fuzztest/internal:logging",
         "@com_google_fuzztest//fuzztest/internal:meta",
@@ -247,3 +252,13 @@
         "@com_google_fuzztest//fuzztest/internal:serialization",
     ],
 )
+
+cc_test(
+    name = "traversal_context_test",
+    srcs = ["traversal_context_test.cc"],
+    deps = [
+        ":core_domains_impl",
+        "@abseil-cpp//absl/status",
+        "@googletest//:gtest_main",
+    ],
+)
diff --git a/fuzztest/internal/domains/CMakeLists.txt b/fuzztest/internal/domains/CMakeLists.txt
index e9702ff..6fe4833 100644
--- a/fuzztest/internal/domains/CMakeLists.txt
+++ b/fuzztest/internal/domains/CMakeLists.txt
@@ -52,6 +52,7 @@
     "serialization_helpers.h"
     "smart_pointer_of_impl.h"
     "special_values.h"
+    "traversal_context.h"
     "unique_elements_container_of_impl.h"
     "value_mutation_helpers.h"
     "variant_of_impl.h"
@@ -71,6 +72,7 @@
     absl::time
     absl::span
     fuzztest::common_logging
+    fuzztest::status_macros
     fuzztest::fuzzing_bit_gen
     fuzztest::any
     fuzztest::logging
@@ -110,6 +112,7 @@
     fuzztest::domain_core
     fuzztest::any
     fuzztest::common_logging
+    fuzztest::status_macros
     fuzztest::meta
     fuzztest::printer
     fuzztest::serialization
@@ -235,3 +238,14 @@
     fuzztest::meta
     fuzztest::serialization
 )
+
+fuzztest_cc_test(
+  NAME
+    traversal_context_test
+  SRCS
+    "traversal_context_test.cc"
+  DEPS
+    fuzztest::core_domains_impl
+    absl::status
+    GTest::gmock_main
+)
diff --git a/fuzztest/internal/domains/aggregate_of_impl.h b/fuzztest/internal/domains/aggregate_of_impl.h
index cd70ca1..fa03b4a 100644
--- a/fuzztest/internal/domains/aggregate_of_impl.h
+++ b/fuzztest/internal/domains/aggregate_of_impl.h
@@ -24,8 +24,12 @@
 
 #include "absl/random/bit_gen_ref.h"
 #include "absl/random/distributions.h"
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "./common/fuzztest_status_macros.h"
 #include "./fuzztest/internal/domains/domain_base.h"
 #include "./fuzztest/internal/domains/serialization_helpers.h"
+#include "./fuzztest/internal/domains/traversal_context.h"
 #include "./fuzztest/internal/meta.h"
 #include "./fuzztest/internal/serialization.h"
 #include "./fuzztest/internal/status.h"
@@ -64,6 +68,32 @@
         inner_);
   }
 
+  absl::StatusOr<corpus_type> InitWithTraversalCtx(
+      absl::BitGenRef prng,
+      domain_implementor::InitTraversalContext<AggregateOfImpl> ctx) {
+    FUZZTEST_RETURN_IF_NOT_OK(ctx.status());
+    if (auto seed = this->MaybeGetRandomSeed(prng)) return *std::move(seed);
+    return ApplyIndex<sizeof...(Inner)>(
+        [&](auto... Is) -> absl::StatusOr<corpus_type> {
+          std::tuple<std::optional<corpus_type_t<Inner>>...> results;
+          absl::Status status = absl::OkStatus();
+          auto init_one = [&](auto I) {
+            auto res = std::get<I>(inner_).InitWithTraversalCtx(prng, ctx);
+            if (!res.ok()) {
+              status = std::move(res).status();
+              return false;
+            }
+            std::get<I>(results) = *std::move(res);
+            return true;
+          };
+          if (!(init_one(Is) && ...)) {
+            return status;
+          }
+
+          return corpus_type{*std::move(std::get<Is>(results))...};
+        });
+  }
+
   void Mutate(corpus_type& val, absl::BitGenRef prng,
               const domain_implementor::MutationMetadata& metadata,
               bool only_shrink) {
diff --git a/fuzztest/internal/domains/container_of_impl.h b/fuzztest/internal/domains/container_of_impl.h
index 7a8cd45..312f1f4 100644
--- a/fuzztest/internal/domains/container_of_impl.h
+++ b/fuzztest/internal/domains/container_of_impl.h
@@ -29,11 +29,14 @@
 #include "absl/random/bit_gen_ref.h"
 #include "absl/random/distributions.h"
 #include "absl/status/status.h"
+#include "absl/status/statusor.h"
 #include "absl/strings/str_format.h"
 #include "absl/types/span.h"
+#include "./common/fuzztest_status_macros.h"
 #include "./common/logging.h"
 #include "./fuzztest/internal/domains/container_mutation_helpers.h"
 #include "./fuzztest/internal/domains/domain_base.h"
+#include "./fuzztest/internal/domains/traversal_context.h"
 #include "./fuzztest/internal/logging.h"
 #include "./fuzztest/internal/meta.h"
 #include "./fuzztest/internal/serialization.h"
@@ -583,6 +586,34 @@
     return val;
   }
 
+  absl::StatusOr<corpus_type> InitWithTraversalCtx(
+      absl::BitGenRef prng,
+      domain_implementor::InitTraversalContext<Derived> ctx) {
+    FUZZTEST_RETURN_IF_NOT_OK(ctx.status());
+    if (ctx.IsResourceExhausted()) {
+      if (this->min_size() > 0) {
+        ctx.FailWithBudgetExceeded();
+        return ctx.status();
+      }
+      return corpus_type{};
+    }
+
+    if (auto seed = this->MaybeGetRandomSeed(prng)) return *std::move(seed);
+    const size_t size = this->ChooseRandomInitialSize(prng);
+    corpus_type val;
+    while (val.size() < size) {
+      auto elem = this->inner_.InitWithTraversalCtx(prng, ctx);
+      if (!elem.ok()) {
+        if (val.size() >= this->min_size()) {
+          return val;
+        }
+        return std::move(elem).status();
+      }
+      val.insert(val.end(), *std::move(elem));
+    }
+    return val;
+  }
+
   uint64_t CountNumberOfFields(corpus_type& val) {
     uint64_t total_weight = 0;
     for (auto& i : val) {
diff --git a/fuzztest/internal/domains/domain.h b/fuzztest/internal/domains/domain.h
index 85425e2..c9aaeed 100644
--- a/fuzztest/internal/domains/domain.h
+++ b/fuzztest/internal/domains/domain.h
@@ -30,6 +30,7 @@
 #include "absl/strings/string_view.h"
 #include "./fuzztest/internal/domains/domain_base.h"
 #include "./fuzztest/internal/domains/domain_type_erasure.h"  // IWYU pragma: export
+#include "./fuzztest/internal/domains/traversal_context.h"
 #include "./fuzztest/internal/printer.h"
 #include "./fuzztest/internal/serialization.h"
 #include "./fuzztest/internal/table_of_recent_compares.h"
@@ -165,6 +166,14 @@
   // values).
   corpus_type Init(absl::BitGenRef prng) { return inner_->UntypedInit(prng); }
 
+  absl::StatusOr<corpus_type> InitWithTraversalCtx(
+      absl::BitGenRef prng,
+      domain_implementor::InitTraversalContext<Domain> ctx) {
+    return inner_->UntypedInitWithTraversalCtx(
+        prng, domain_implementor::InitTraversalContext<
+                  internal::UntypedDomainConcept>::Passthrough(ctx));
+  }
+
   // Mutate() makes a relatively small modification on `val` of `corpus_type`.
   //
   // Used during coverage-guided fuzzing. When `only_shrink` is true,
@@ -333,6 +342,14 @@
 
   corpus_type Init(absl::BitGenRef prng) { return inner_->UntypedInit(prng); }
 
+  absl::StatusOr<corpus_type> InitWithTraversalCtx(
+      absl::BitGenRef prng,
+      domain_implementor::InitTraversalContext<UntypedDomain> ctx) {
+    return inner_->UntypedInitWithTraversalCtx(
+        prng, domain_implementor::InitTraversalContext<
+                  internal::UntypedDomainConcept>::Passthrough(ctx));
+  }
+
   void Mutate(corpus_type& corpus_value, absl::BitGenRef prng,
               const domain_implementor::MutationMetadata& metadata,
               bool only_shrink) {
diff --git a/fuzztest/internal/domains/domain_base.h b/fuzztest/internal/domains/domain_base.h
index 704421b..267985c 100644
--- a/fuzztest/internal/domains/domain_base.h
+++ b/fuzztest/internal/domains/domain_base.h
@@ -29,9 +29,12 @@
 #include "absl/random/bit_gen_ref.h"
 #include "absl/random/distributions.h"
 #include "absl/status/status.h"
+#include "absl/status/statusor.h"
 #include "absl/strings/str_format.h"
+#include "./common/fuzztest_status_macros.h"
 #include "./fuzztest/internal/domains/domain_type_erasure.h"
 #include "./fuzztest/internal/domains/mutation_metadata.h"  // IWYU pragma: export
+#include "./fuzztest/internal/domains/traversal_context.h"
 #include "./fuzztest/internal/meta.h"
 #include "./fuzztest/internal/printer.h"
 #include "./fuzztest/internal/serialization.h"
@@ -134,6 +137,12 @@
     return derived().GetValue(derived().GetRandomCorpusValue(prng));
   }
 
+  absl::StatusOr<corpus_type> InitWithTraversalCtx(
+      absl::BitGenRef prng, InitTraversalContext<Derived> ctx) {
+    FUZZTEST_RETURN_IF_NOT_OK(ctx.status());
+    return derived().Init(prng);
+  }
+
   // Default GetValue and FromValue functions for !has_custom_corpus_type
   // domains.
   ValueType GetValue(const ValueType& v) const {
diff --git a/fuzztest/internal/domains/domain_type_erasure.h b/fuzztest/internal/domains/domain_type_erasure.h
index 442af9d..20b1ad4 100644
--- a/fuzztest/internal/domains/domain_type_erasure.h
+++ b/fuzztest/internal/domains/domain_type_erasure.h
@@ -32,10 +32,13 @@
 #include "absl/functional/function_ref.h"
 #include "absl/random/bit_gen_ref.h"
 #include "absl/status/status.h"
+#include "absl/status/statusor.h"
 #include "absl/strings/string_view.h"
+#include "./common/fuzztest_status_macros.h"
 #include "./common/logging.h"
 #include "./fuzztest/internal/any.h"
 #include "./fuzztest/internal/domains/mutation_metadata.h"
+#include "./fuzztest/internal/domains/traversal_context.h"
 #include "./fuzztest/internal/logging.h"
 #include "./fuzztest/internal/meta.h"
 #include "./fuzztest/internal/printer.h"
@@ -58,6 +61,9 @@
 
   virtual std::unique_ptr<UntypedDomainConcept> UntypedClone() const = 0;
   virtual GenericDomainCorpusType UntypedInit(absl::BitGenRef) = 0;
+  virtual absl::StatusOr<GenericDomainCorpusType> UntypedInitWithTraversalCtx(
+      absl::BitGenRef,
+      domain_implementor::InitTraversalContext<UntypedDomainConcept>) = 0;
   virtual void UntypedMutate(
       GenericDomainCorpusType& val, absl::BitGenRef prng,
       const domain_implementor::MutationMetadata& metadata,
@@ -140,6 +146,19 @@
                                    domain_.Init(prng));
   }
 
+  absl::StatusOr<GenericDomainCorpusType> UntypedInitWithTraversalCtx(
+      absl::BitGenRef prng,
+      domain_implementor::InitTraversalContext<UntypedDomainConcept> ctx)
+      final {
+    FUZZTEST_ASSIGN_OR_RETURN_IF_NOT_OK(
+        auto res,
+        domain_.InitWithTraversalCtx(
+            prng,
+            domain_implementor::InitTraversalContext<D>::Passthrough(ctx)));
+    return GenericDomainCorpusType(std::in_place_type<CorpusType>,
+                                   std::move(res));
+  }
+
   void UntypedMutate(GenericDomainCorpusType& val, absl::BitGenRef prng,
                      const domain_implementor::MutationMetadata& metadata,
                      bool only_shrink) final {
diff --git a/fuzztest/internal/domains/filter_impl.h b/fuzztest/internal/domains/filter_impl.h
index f37b8f9..ae8a71a 100644
--- a/fuzztest/internal/domains/filter_impl.h
+++ b/fuzztest/internal/domains/filter_impl.h
@@ -21,11 +21,13 @@
 
 #include "absl/random/bit_gen_ref.h"
 #include "absl/status/status.h"
+#include "absl/status/statusor.h"
 #include "absl/strings/str_format.h"
+#include "./common/fuzztest_status_macros.h"
 #include "./common/logging.h"
 #include "./fuzztest/internal/domains/domain.h"
 #include "./fuzztest/internal/domains/domain_base.h"
-#include "./fuzztest/internal/logging.h"
+#include "./fuzztest/internal/domains/traversal_context.h"
 #include "./fuzztest/internal/serialization.h"
 
 namespace fuzztest::internal {
@@ -50,6 +52,19 @@
     }
   }
 
+  absl::StatusOr<corpus_type> InitWithTraversalCtx(
+      absl::BitGenRef prng,
+      domain_implementor::InitTraversalContext<FilterImpl> ctx) {
+    FUZZTEST_RETURN_IF_NOT_OK(ctx.status());
+    if (auto seed = this->MaybeGetRandomSeed(prng)) return *std::move(seed);
+
+    while (true) {
+      FUZZTEST_ASSIGN_OR_RETURN_IF_NOT_OK(
+          auto v, inner_.InitWithTraversalCtx(prng, ctx));
+      if (RunFilter(v)) return v;
+    }
+  }
+
   void Mutate(corpus_type& val, absl::BitGenRef prng,
               const domain_implementor::MutationMetadata& metadata,
               bool only_shrink) {
diff --git a/fuzztest/internal/domains/traversal_context.h b/fuzztest/internal/domains/traversal_context.h
new file mode 100644
index 0000000..edaff80
--- /dev/null
+++ b/fuzztest/internal/domains/traversal_context.h
@@ -0,0 +1,149 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_TRAVERSAL_CONTEXT_H_
+#define FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_TRAVERSAL_CONTEXT_H_
+
+#include <limits>
+#include <string>
+#include <vector>
+
+#include "absl/status/status.h"
+#include "absl/strings/str_format.h"
+#include "./common/logging.h"
+#include "./fuzztest/internal/type_support.h"
+
+namespace fuzztest::domain_implementor {
+
+struct TraversalState {
+  // The maximum node depth during domain initialization and mutation.
+  int depth_budget = 100;
+  // The maximum number of added nodes during domain initialization.
+  int init_budget = 1000;
+  absl::Status status = absl::OkStatus();
+  std::vector<std::string> error_trace;
+};
+
+template <typename DomainType>
+class TraversalContext {
+ public:
+  struct PassthroughTag {};
+  TraversalContext(TraversalState& state, PassthroughTag) : state_{state} {}
+
+  explicit TraversalContext(TraversalState& state) : state_{state} { Enter(); }
+
+  template <typename OtherDomain>
+  TraversalContext(const TraversalContext<OtherDomain>& other)
+      : state_{other.state()} {
+    Enter();
+  }
+
+  TraversalContext(const TraversalContext& other) : state_{other.state_} {
+    Enter();
+  }
+
+  ~TraversalContext() { Exit(); }
+
+  bool IsResourceExhausted() const {
+    return state_.depth_budget < 0 || state_.init_budget < 0;
+  }
+
+  bool IsFailed() const { return !state_.status.ok(); }
+
+  void FailWithBudgetExceeded() {
+    if (state_.status.ok()) {
+      state_.status = absl::ResourceExhaustedError(
+          absl::StrFormat("Traversal budget exceeded at %s",
+                          fuzztest::internal::GetTypeName<DomainType>()));
+    }
+  }
+
+  TraversalState& state() const { return state_; }
+  absl::Status status() const { return state_.status; }
+
+ protected:
+  void Enter() {
+    entered_ = true;
+    enter_ok_ = state_.status.ok();
+    FUZZTEST_CHECK_GT(state_.depth_budget, std::numeric_limits<int>::min());
+    state_.depth_budget--;
+  }
+
+  void Exit() {
+    if (entered_) {
+      state_.depth_budget++;
+    }
+    if (enter_ok_ && !state_.status.ok()) {
+      state_.error_trace.push_back(
+          std::string(fuzztest::internal::GetTypeName<DomainType>()));
+    }
+  }
+
+  TraversalState& state_;
+  bool enter_ok_ = false;
+  bool entered_ = false;
+};
+
+template <typename DomainType>
+class InitTraversalContext : public TraversalContext<DomainType> {
+ public:
+  using PassthroughTag =
+      typename InitTraversalContext::TraversalContext::PassthroughTag;
+  using InitTraversalContext::TraversalContext::TraversalContext;
+
+  template <typename OtherDomain>
+  static InitTraversalContext Passthrough(
+      const InitTraversalContext<OtherDomain>& other) {
+    return InitTraversalContext(other.state(), PassthroughTag{});
+  }
+
+  template <typename OtherDomain>
+  static InitTraversalContext Passthrough(
+      const TraversalContext<OtherDomain>& other) {
+    return InitTraversalContext(other.state(), PassthroughTag{});
+  }
+
+  explicit InitTraversalContext(TraversalState& state)
+      : TraversalContext<DomainType>{state} {
+    Decrement();
+  }
+
+  template <typename OtherDomain>
+  InitTraversalContext(const InitTraversalContext<OtherDomain>& other)
+      : TraversalContext<DomainType>{other} {
+    Decrement();
+  }
+
+  template <typename OtherDomain>
+  InitTraversalContext(const TraversalContext<OtherDomain>& other)
+      : TraversalContext<DomainType>{other} {
+    Decrement();
+  }
+
+  InitTraversalContext(const InitTraversalContext& other)
+      : TraversalContext<DomainType>{other} {
+    Decrement();
+  }
+
+ private:
+  void Decrement() {
+    if (this->state_.init_budget >= 0) {
+      --this->state_.init_budget;
+    }
+  }
+};
+
+}  // namespace fuzztest::domain_implementor
+
+#endif  // FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_TRAVERSAL_CONTEXT_H_
diff --git a/fuzztest/internal/domains/traversal_context_test.cc b/fuzztest/internal/domains/traversal_context_test.cc
new file mode 100644
index 0000000..e2c737d
--- /dev/null
+++ b/fuzztest/internal/domains/traversal_context_test.cc
@@ -0,0 +1,282 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "./fuzztest/internal/domains/traversal_context.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/status/status.h"
+
+namespace fuzztest::domain_implementor {
+namespace {
+
+struct TestDomain {};
+struct AnotherTestDomain {};
+
+TEST(TraversalContextTest, DepthTrackingDecrementsAndRestores) {
+  TraversalState state;
+  state.depth_budget = 1;
+
+  EXPECT_TRUE(state.status.ok());
+  EXPECT_EQ(state.depth_budget, 1);
+
+  {
+    TraversalContext<TestDomain> ctx1(state);
+    EXPECT_EQ(state.depth_budget, 0);
+    EXPECT_FALSE(ctx1.IsResourceExhausted());
+
+    {
+      TraversalContext<TestDomain> ctx2(ctx1);
+      EXPECT_EQ(state.depth_budget, -1);
+      EXPECT_TRUE(ctx2.IsResourceExhausted());
+    }
+    EXPECT_EQ(state.depth_budget, 0);
+  }
+  EXPECT_EQ(state.depth_budget, 1);
+}
+
+TEST(TraversalContextTest, InitBudgetTrackingDecrementsAndDoesNotRestore) {
+  TraversalState state;
+  state.init_budget = 1;
+
+  {
+    InitTraversalContext<TestDomain> ctx1(state);
+    EXPECT_EQ(state.init_budget, 0);
+    EXPECT_FALSE(ctx1.IsResourceExhausted());
+
+    {
+      InitTraversalContext<TestDomain> ctx2(ctx1);
+      EXPECT_EQ(state.init_budget, -1);
+      EXPECT_TRUE(ctx2.IsResourceExhausted());
+    }
+  }
+
+  EXPECT_EQ(state.init_budget, -1);
+}
+
+TEST(TraversalContextTest, MixedTraversalContextsInitBudgetTracking) {
+  TraversalState state;
+  state.init_budget = 2;
+
+  {
+    InitTraversalContext<TestDomain> ctx1(state);  // Init: decrements to 1
+    EXPECT_EQ(state.init_budget, 1);
+
+    {
+      TraversalContext<TestDomain> ctx2(ctx1);  // Mutate: does NOT decrement
+      EXPECT_EQ(state.init_budget, 1);
+
+      {
+        InitTraversalContext<TestDomain> ctx3(ctx2);  // Init: decrements to 0
+        EXPECT_EQ(state.init_budget, 0);
+      }
+    }
+  }
+  EXPECT_EQ(state.init_budget, 0);
+}
+
+TEST(TraversalContextTest, ExhaustedContextFailsOnlyOnExplicitFail) {
+  TraversalState state;
+  state.depth_budget = 0;  // Next enter will exhaust it
+
+  InitTraversalContext<TestDomain> ctx(state);  // depth -1
+  EXPECT_TRUE(ctx.IsResourceExhausted());
+  EXPECT_FALSE(ctx.IsFailed());
+
+  // We can choose to fail:
+  ctx.FailWithBudgetExceeded();
+  EXPECT_TRUE(ctx.IsFailed());
+  EXPECT_FALSE(state.status.ok());
+  EXPECT_THAT(state.status.ToString(),
+              testing::HasSubstr("Traversal budget exceeded"));
+}
+
+TEST(TraversalContextTest, ExistingInitBudgetIsNotReset) {
+  TraversalState state;  // init_budget defaults to 1000
+  EXPECT_EQ(state.init_budget, 1000);
+
+  {
+    InitTraversalContext<TestDomain> ctx1(state);
+    EXPECT_EQ(state.init_budget, 999);
+
+    {
+      InitTraversalContext<AnotherTestDomain> ctx2(ctx1);
+      EXPECT_EQ(state.init_budget, 998);
+    }
+    // ctx2 destructed. No change to init_budget.
+    EXPECT_EQ(state.init_budget, 998);
+  }
+  // ctx1 destructed. No change to init_budget.
+  EXPECT_EQ(state.init_budget, 998);
+}
+
+TEST(TraversalContextTest, TransitionToInitTraversalContext) {
+  TraversalState state;  // init_budget = 1000, depth_budget = 100
+  state.depth_budget = 5;
+
+  TraversalContext<TestDomain> ctx_without_budget(state);
+  EXPECT_EQ(state.depth_budget, 4);
+  EXPECT_EQ(state.init_budget, 1000);  // Not decremented by TraversalContext
+
+  {
+    InitTraversalContext<AnotherTestDomain> ctx_with_budget(ctx_without_budget);
+    EXPECT_EQ(state.depth_budget, 3);   // Decremented by TraversalContext base
+    EXPECT_EQ(state.init_budget, 999);  // Decremented by InitTraversalContext
+  }
+  // ctx_with_budget destructed. depth_budget is restored, init_budget is NOT.
+  EXPECT_EQ(state.depth_budget, 4);
+  EXPECT_EQ(state.init_budget, 999);
+}
+
+struct ExhaustionTestParam {
+  TraversalState state;
+  bool expected_exhausted;
+  bool expected_failed;
+};
+
+class TraversalContextExhaustionTest
+    : public testing::TestWithParam<ExhaustionTestParam> {};
+
+TEST_P(TraversalContextExhaustionTest, ChecksIsResourceExhaustedAndIsFailed) {
+  const auto& param = GetParam();
+  TraversalState state = param.state;
+  InitTraversalContext<TestDomain> ctx(state);
+  EXPECT_EQ(ctx.IsResourceExhausted(), param.expected_exhausted);
+  EXPECT_EQ(ctx.IsFailed(), param.expected_failed);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    TraversalContextTests, TraversalContextExhaustionTest,
+    testing::Values(ExhaustionTestParam{TraversalState{1, 1}, false, false},
+                    ExhaustionTestParam{TraversalState{0, 1000}, true, false},
+                    ExhaustionTestParam{TraversalState{1, 0}, true, false},
+                    ExhaustionTestParam{
+                        TraversalState{1, 1, absl::CancelledError("cancelled")},
+                        false, true}));
+
+TEST(TraversalContextTest, ErrorTraceAccumulatesOnUnwinding) {
+  TraversalState state;
+  struct DomainC {};
+  struct DomainB {};
+  struct DomainA {};
+
+  {
+    TraversalContext<DomainA> ctxA(state);
+    {
+      TraversalContext<DomainB> ctxB(ctxA);
+      {
+        TraversalContext<DomainC> ctxC(ctxB);
+        ctxC.FailWithBudgetExceeded();
+      }
+    }
+  }
+
+  EXPECT_FALSE(state.status.ok());
+  EXPECT_THAT(state.error_trace,
+              testing::ElementsAre(testing::HasSubstr("DomainC"),
+                                   testing::HasSubstr("DomainB"),
+                                   testing::HasSubstr("DomainA")));
+}
+
+TEST(TraversalContextTest, DepthGoesBelowMinusOneAndRestores) {
+  TraversalState state;
+  state.depth_budget = 1;
+
+  {
+    // depth 0
+    TraversalContext<TestDomain> ctx1(state);
+    EXPECT_EQ(state.depth_budget, 0);
+    {
+      // depth -1
+      TraversalContext<TestDomain> ctx2(ctx1);
+      EXPECT_EQ(state.depth_budget, -1);
+      {
+        // depth goes to -2 (no longer capped)
+        TraversalContext<TestDomain> ctx3(ctx2);
+        EXPECT_EQ(state.depth_budget, -2);
+      }  // exit ctx3 -> depth becomes -1
+      EXPECT_EQ(state.depth_budget, -1);
+    }  // exit ctx2 -> depth becomes 0
+    EXPECT_EQ(state.depth_budget, 0);
+  }  // exit ctx1 -> depth becomes 1
+  EXPECT_EQ(state.depth_budget, 1);
+}
+
+TEST(TraversalContextTest, InitBudgetCappedAtMinusOne) {
+  TraversalState state;
+  state.init_budget = 1;
+
+  {
+    InitTraversalContext<TestDomain> ctx(state);
+    EXPECT_EQ(state.init_budget, 0);
+  }
+  {
+    InitTraversalContext<TestDomain> ctx(state);
+    EXPECT_EQ(state.init_budget, -1);
+  }
+  {
+    InitTraversalContext<TestDomain> ctx(state);
+    EXPECT_EQ(state.init_budget, -1);
+  }
+}
+
+TEST(TraversalContextTest, PassthroughDoesNotDecrementBudgets) {
+  TraversalState state;
+  state.depth_budget = 5;
+  state.init_budget = 10;
+
+  {
+    InitTraversalContext<TestDomain> ctx(state);
+    EXPECT_EQ(state.depth_budget, 4);
+    EXPECT_EQ(state.init_budget, 9);
+
+    {
+      auto passthrough_ctx =
+          InitTraversalContext<AnotherTestDomain>::Passthrough(ctx);
+      EXPECT_EQ(state.depth_budget, 4);  // Not decremented
+      EXPECT_EQ(state.init_budget, 9);   // Not decremented
+    }
+    // passthrough_ctx destructed. No change.
+    EXPECT_EQ(state.depth_budget, 4);
+    EXPECT_EQ(state.init_budget, 9);
+  }
+  // ctx destructed. depth_budget restored.
+  EXPECT_EQ(state.depth_budget, 5);
+  EXPECT_EQ(state.init_budget, 9);
+}
+
+TEST(TraversalContextTest, PassthroughDoesNotAddToErrorTrace) {
+  TraversalState state;
+
+  {
+    InitTraversalContext<TestDomain> ctx(state);
+
+    {
+      auto passthrough_ctx =
+          InitTraversalContext<AnotherTestDomain>::Passthrough(ctx);
+
+      passthrough_ctx.FailWithBudgetExceeded();
+      EXPECT_FALSE(state.status.ok());
+    }
+    // passthrough_ctx destructed. It should not add "AnotherTestDomain" to
+    // error_trace.
+    EXPECT_TRUE(state.error_trace.empty());
+  }
+  // ctx destructed. It was active (enter_ok_ is true).
+  // So it should add "TestDomain" to error_trace because status is now not ok.
+  EXPECT_THAT(state.error_trace, testing::ElementsAre("TestDomain"));
+}
+
+}  // namespace
+}  // namespace fuzztest::domain_implementor